package usda.weru.weps.reports.query;

import de.schlichtherle.truezip.file.TFile;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import org.apache.log4j.Logger;
import usda.weru.mcrew.ManageData;
import usda.weru.util.Util;
import java.io.BufferedReader;
import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.text.ParseException;
import org.openide.util.Exceptions;
import javax.swing.JOptionPane;
import static usda.weru.weps.reports.query.OutputResultSet.COLUMN_ENDDATE;
import static usda.weru.weps.reports.query.OutputResultSet.COLUMN_STARTDATE;
import java.util.GregorianCalendar;

/**
 *
 * @author joelevin
 */
public class CropRotationResultSet extends WepsResultSet {

    private static final Logger LOGGER = Logger.getLogger(CropRotationResultSet.class);

    private boolean flag = true;

    private boolean checkFlag() {	// check if rain and irrig data are split (true if so)
        return this.flag;
    }

    private void setFlag(boolean flag) {
        this.flag = flag;
    }

    private boolean exe_found = true;

    private boolean checkEXE() {
        return exe_found;
    }

    private void setEXE(boolean b) {
        exe_found = b;
    }

    private boolean noCIReport = true;

    private boolean noCIReport() {
        return noCIReport;
    }

    private void setnoCIReport(boolean b) {
        noCIReport = b;
    }

    private int rot_nums;

    public static final String NAME = "crop_rotation";

    public static final String COLUMN_STARTDATE = "startdate";

    public static final String COLUMN_ENDDATE = "enddate";

    public static final String COLUMN_CROP = "crop";

    public static final String COLUMN_CROPNUMBER = "cropnumber";
    private static final String SQL = "SELECT \"runid\", \"manage\" FROM weps('runs') ORDER BY \"runid\";";
    private final WepsConnection c_con;
    private boolean c_filled;

    public static final String COLUMN_DAYS = "days";

    public static int num_rotation_years = 0;

    private Set_CI_DataStruct ci;
    private boolean ran = true;
    private String runName;

    public CropRotationResultSet(WepsConnection con, String name) throws SQLException {
        runName = name;
        rot_nums = 0;
        ci = new Set_CI_DataStruct();
        c_con = con;
        //added to query to tie back to runs when multiple are int the connection
        addColumn(RunsResultSet.COLUMN_RUNID, Types.INTEGER, 10, 0);
        //Columns expected in the file
        addColumn(COLUMN_CROPNUMBER, Types.INTEGER, 10, 0);
        addColumn(COLUMN_CROP, Types.VARCHAR, 50, 0);
        addColumn(COLUMN_STARTDATE, Types.DATE, 0, 0);
        addColumn(COLUMN_ENDDATE, Types.DATE, 0, 0);
        addColumn(COLUMN_DAYS, Types.INTEGER, 0, 0);
    }

    @Override
    public String getName() {
        return NAME;
    }

    protected boolean isUSUnits() {
        return Util.USUnits.equals(c_con.getUnits());
    }

    private void getRotationYears(TFile runDir) throws FileNotFoundException {
        //managefile
        //get rot year, only read once;
        BufferedReader man_f = null;

        String manFileName = "mandate.out";
        TFile manfile = new TFile(runDir, manFileName);
        man_f = new BufferedReader(new TFileReader(manfile));
        String man_line1 = null;
        try {
            man_line1 = getLine(man_f);
        } catch (IOException ex) {
            LOGGER.error("Error reading getLine(man_f).");
        }
        if (man_line1 != null) {
            String[] line1 = man_line1.split(" ", -1);
            rot_nums = Integer.parseInt(line1[0]);
        }
    }

    static boolean lockMessage = true;

    void setLockMessage(boolean bool) {
        lockMessage = bool;
    }

    boolean getLockMessage() {
        return lockMessage;
    }

    static boolean lockMessage2 = true;

    void setLockMessage2(boolean bool) {
        lockMessage2 = bool;
    }

    boolean getLockMessage2() {
        return lockMessage2;
    }

    static boolean lockMessage3 = true;

    void setLockMessage3(boolean bool) {
        lockMessage3 = bool;
    }

    boolean getLockMessage3() {
        return lockMessage3;
    }

    static ArrayList<String> currentRanFiles = new ArrayList<>();
    static ArrayList<String> currentRanFiles2 = new ArrayList<>();
    static ArrayList<String> currentRanFiles3 = new ArrayList<>();

    @Override
    public synchronized void fill() throws SQLException {

        if (currentRanFiles.contains(runName)) {
        } else {
            currentRanFiles.add(runName);
            setLockMessage(true);
        }

        if (currentRanFiles2.contains(runName)) {
        } else {
            currentRanFiles2.add(runName);
            setLockMessage2(true);
        }

        if (currentRanFiles3.contains(runName)) {
        } else {
            currentRanFiles3.add(runName);
            setLockMessage3(true);
        }

        CropIntervalDataStruct ci_ds = null;

        if (c_filled) {
            return;
        }
        //if last operation is a multi harvest, wrap and add 1st date to end multi harvest secotion of harvestX
        TFile[] files = c_con.getRunFiles();
        SimpleDateFormat terminateDateFormat = new SimpleDateFormat("dd/MM/yyyy");
        ArrayList<String[]> season_file = new ArrayList<>();
        String[] first_delim;
        ArrayList<String[]> data;
        ArrayList<Integer> used_index;
        ArrayList<Integer> per_used_index = null;
        //SimpleDateFormat simpDate = new SimpleDateFormat("MM-dd-yyyy");
        //System.out.println("CropIntervalResultSet");
        for (TFile file : files) {
            int row_id = 0;
            TFile runDir = file;
            //Season File
            String seasonFileName = "season.out";
            TFile seasonFile = new TFile(runDir, seasonFileName);
            //Hydobal File
            String hydrobalFileName = "hydrobal.out";
            TFile hydrobalFile = new TFile(runDir, hydrobalFileName);
            //Harvest file
            String harvestFileName = isUSUnits() ? "harvest_en.out" : "harvest_si.out";
            TFile harvestFile = new TFile(runDir, harvestFileName);
            //managefile
            //get rot year, only read once;
            if (checkFlag()) {
                try {
                    setFlag(false);
                    getRotationYears(runDir);
                } catch (FileNotFoundException ex) {
                    LOGGER.error("Error reading mandate.out");
                }
            }
            if (seasonFile.exists() && hydrobalFile.exists() && harvestFile.exists()) {
                BufferedReader season_f = null;
                BufferedReader hydobal_f = null;
                BufferedReader harvest_f = null;

                try {
                    season_f = new BufferedReader(new TFileReader(seasonFile));
                    hydobal_f = new BufferedReader(new TFileReader(hydrobalFile));
                    harvest_f = new BufferedReader(new TFileReader(harvestFile));
                    int cycleNumber = 0;
                    String season_l;
                    String hydro_l;
                    String harvest_l;
                    while (((season_l = getLine(season_f)) != null) && ((hydro_l = getLine(hydobal_f)) != null) && ((harvest_l = getLine(harvest_f)) != null)) {
                        String currentDate = null;
                        //each line is a new cycle
                        cycleNumber++;
                        // season.out fileString 
                        String[] season_line_parts = season_l.split("\\|", -1);
                        season_file.add(season_line_parts);
                        // hydrobal.out file
                        String[] hydrobal_line_parts = hydro_l.split("\\|", -1);
                        //if rain irrig not split old file, exit
                        String[] rainIrrigSplit = hydrobal_line_parts[10].split("\\,", -1);
                        if (!rainIrrigSplit[1].equals("irrigation")) {
                            if (getLockMessage()) {
                                if (ran) {
                                    ran = false;
                                    JOptionPane.showMessageDialog(null, "Older output data incompatible with new Crop Rotation Reports.\n"
                                            + "Crop Interval Data will not be generated.", "Crop Rotation Report", JOptionPane.WARNING_MESSAGE);
                                    setLockMessage(false);
                                    return;
                                }
                                setLockMessage(false);
                            }
                            return;
                        }

                        try {
                            //init new object to determine crop intervals for rum summary reports
                            ci_ds = new CropIntervalDataStruct();
                            ci.getCropIntervals(ci_ds, harvest_l, hydrobal_line_parts, season_line_parts, rot_nums);
                        } catch (Exception e) {
                            if (getLockMessage2()) {
                                if (checkEXE()) {
                                    JOptionPane.showMessageDialog(null, "Error Generating Crop Interval" + "\n" + "Sub Reports for Run Summary Report",
                                            "Run Summary Sub Report(s)", JOptionPane.WARNING_MESSAGE);
                                    setLockMessage2(false);
                                    setEXE(false);
                                }
                                setLockMessage2(false);
                            }
                            LOGGER.error(e.getMessage(), e);
                            //setMutex();
                            return;
                        }
                    }
                } catch (IOException ioe) {
                    LOGGER.error("Error reading season.out file or hydrobal.out file: " + seasonFile.getAbsolutePath()
                            + hydrobalFile.getAbsolutePath(), ioe);
                } finally {
                    if (season_f != null) {
                        try {
                            season_f.close();
                        } catch (IOException e) {
                            LOGGER.error("Error closing season.out file: " + seasonFile.getAbsolutePath(), e);
                        }
                    }
                    if (hydobal_f != null) {
                        try {
                            hydobal_f.close();
                        } catch (IOException e) {
                            LOGGER.error("Error closing hydrobal.out file: " + hydrobalFile.getAbsolutePath(), e);
                        }
                    }
                    if (harvest_f != null) {
                        try {
                            harvest_f.close();
                        } catch (IOException e) {
                            LOGGER.error("Error closing hydrobal.out file: " + harvestFile.getAbsolutePath(), e);
                        }
                    }
                }
            }
            PreparedStatement statement = null;
            statement = c_con.prepareStatement(SQL);
            ResultSet rs = statement.executeQuery();
            per_used_index = new ArrayList<>();
            while (rs.next()) {
                //get the current row data
                int runId = rs.getInt(RunsResultSet.COLUMN_RUNID);
                String path = rs.getString(RunsResultSet.COLUMN_MANAGE);

                ManageData man = new ManageData();
                int result = man.readDataFile(path);
                num_rotation_years = man.getRotationYears();

                Calendar cal1 = Calendar.getInstance();
                Calendar cal2 = Calendar.getInstance();
                //if ci_ds object is null, no data was found no object was created
                //meaning... no crop interval / crop information available for run summary report
                if (ci_ds != null) {
                    int plant_ops = ci_ds.operationCount();
                    for (int i = 0; i < plant_ops; i++) {
                        try {
                            int s_out = 0;
                            //System.out.println(dateStrings);
                            s_out = getDateRange(ci_ds, terminateDateFormat, season_file.get(i), s_out, i, per_used_index);
                            //System.out.println(season_file.get(i)[s_out + 1].trim());
                            String[] date1 = ci_ds.getPreOpDates(i).trim().split("/");
                            String[] date2 = null;
                            if (ci_ds.getPostOpDate(i).equals("H")) {
                                int harX = ci_ds.getMultiHarvestDateInd().get(i).size() - 1;
                                date2 = ci_ds.getMultiHarvestDates().get(i).get(harX).trim().split("/");
                            } else {
                                date2 = ci_ds.getPostOpDate(i).trim().split("/");
                            }
                            String crop = "";
                            if (s_out < season_file.get(i).length) {
                                crop = season_file.get(i)[s_out + 1].trim();
                            }
                            cal1.set(Integer.parseInt(date1[2].trim()), Integer.parseInt(date1[1].trim()) - 1, Integer.parseInt(date1[0].trim()));
                            cal2.set(Integer.parseInt(date2[2].trim()), Integer.parseInt(date2[1].trim()) - 1, Integer.parseInt(date2[0].trim()));
                            addRow(ci_ds, runId, i, crop, new Date(cal1.getTimeInMillis()), new Date(cal2.getTimeInMillis()), terminateDateFormat);
                        } catch (ParseException ex) {
                            LOGGER.error("Error Adding Row in CropRotationResultSet.java.");
                        }
                    }
                }
                //if ci_ds object is null, no data was reported from 
                //season.out, harvest.out, hydrobal.out
                //no data is available to report for run summary interval reports
                if (ci_ds == null) {
                    if(getLockMessage3()){
                        JOptionPane.showMessageDialog(null, "No Crop Interval (No Crop or Cover Crop Data Available)" + "\n" 
                        + "to report for current run.", "Crop Interval Sub-Report (Run Summary)", JOptionPane.WARNING_MESSAGE);
                        setLockMessage3(false);
                    }
                }
            }

            if (statement != null) {
                try {
                    statement.close();
                } catch (Exception e) {
                    LOGGER.warn("Unable to close sql statement.", e);
                }
            }
        }
        c_filled = true;
        //setMutex();
    }

    private Object[] addRow(CropIntervalDataStruct ci_ds, int runId, int cropNumber, String crop, Date startDate, Date endDate,
            SimpleDateFormat terminateDateFormat) throws SQLException, ParseException {
        //System.out.println("create row..." + cropNumber);
        Object[] row = createNewRow(true);
        setRowValue(row, RunsResultSet.COLUMN_RUNID, runId);
        setRowValue(row, COLUMN_CROPNUMBER, cropNumber);
        setRowValue(row, COLUMN_CROP, crop);
        setRowValue(row, COLUMN_STARTDATE, startDate);
        setRowValue(row, COLUMN_ENDDATE, endDate);
        //check if start year and end year are the same 
        //if so we need to calculate the full year rotation
        if (startDate.equals(endDate)) {
            int dur = 365;
            dur *= rot_nums;
            setRowValue(row, COLUMN_DAYS, dur);
        } else {
            int sday = convertToJulian365(startDate);
            int eday = convertToJulian365(endDate);
            if (sday > eday) {
                int num_rotation_years = getYear(startDate);
                int dur = ((num_rotation_years * 365) - sday + eday);
                setRowValue(row, COLUMN_DAYS, dur);

            } else {
                int dur = (eday - sday);
                setRowValue(row, COLUMN_DAYS, dur);
            }
        }
        return row;
    }

    public static int convertToJulian365(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int year = calendar.get(Calendar.YEAR);
        double julian = Math.floor(year * 365) + get365(date);
        return (int) julian;
    }

    public static int get365(Date currDate) {
        GregorianCalendar start = new GregorianCalendar();
        Calendar cal = Calendar.getInstance();
        cal.setTime(currDate);
        int day = cal.get(Calendar.DAY_OF_MONTH);
        start.set(GregorianCalendar.DAY_OF_MONTH, day);
        int month = cal.get(Calendar.MONTH);
        start.set(GregorianCalendar.MONTH, month);
        start.set(GregorianCalendar.YEAR, 1901);
        int numDays = start.get(GregorianCalendar.DAY_OF_YEAR);
        //System.out.println("***" + numDays);
        return numDays;
    }

    public static int getYear(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int year = calendar.get(Calendar.YEAR);
        return year;
    }

    //Skip comments and blank lines
    private String getLine(BufferedReader in) throws IOException {
        String temp;
        while ((temp = in.readLine()) != null) {
            temp = temp.trim();
            if (temp.length() == 0) {
                continue;
            }
            if (temp.charAt(0) != '#') {
                return temp;
            }
        }
        return null;
    }

    private int getDateRange(CropIntervalDataStruct ci_ds, DateFormat terminateDateFormat, String[] season_line_parts,
            int s_out, int cc_size, ArrayList<Integer> used_index) throws SQLException {
        //System.out.println("cc_size: " + cc_size);
        String season_date = null;
        String dateString = null;
        while (s_out < season_line_parts.length) {
            //find location in season.out to pull date from 
            //if multiple harvest crop we are in trouble for now
            if (ci.isValidDate(season_line_parts[s_out])) {
                String setYear[] = season_line_parts[s_out].split("/", -1);
                //System.out.println("setYears: " + Arrays.toString(setYear));
                //this date appears at index N of harvestX_ind;
                String getYear[] = ci_ds.getPostPlantSets().get(cc_size).get(0).split("/", -1);
                //System.out.println("getYear[]" + Arrays.toString(getYear));
                if (ci_ds.getPostPlantSets().get(cc_size).get(0).equals("un_init")) {
                    break;
                }
                setYear[2] = getYear[2].trim();
                String update_date = (setYear[0].trim() + "/" + setYear[1].trim() + "/" + setYear[2].trim());
                //System.out.println("update_date: " +update_date + "; comparing :" + dateStrings.get(cc_size).get(0) + " to : " + season_line_parts[s_out] + " at " + s_out);
                try {
                    java.util.Date sDate = terminateDateFormat.parse(update_date);
                    java.util.Date hDate = terminateDateFormat.parse(ci_ds.getPostPlantSets().get(cc_size).get(0));

                    //as soon as data for season.out is found, s_out returns index
                    if (sDate.equals(hDate) && !used_index.contains(s_out)) {
                        //System.out.println("date matched...");
                        //System.out.println("returning: " + s_out);
                        season_date = season_line_parts[s_out];
                        used_index.add(s_out);
                        break;
                    }
                } catch (ParseException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
            s_out++;
        }
        return s_out;
    }
}
