package usda.weru.weps.reports.query;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import usda.weru.util.ConversionCalculator;
import usda.weru.util.Util;

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

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

    /**
     *
     */
    public static final String FILE_NAME = "ci.out";

    /**
     *
     */
    public static final String NAME = "ci";

    /**
     *
     */
    public static final String COLUMN_RUNID = "runid";

    /**
     *
     */
    public static final String COLUMN_ROT_YEARS = "rot_years";

    /**
     *
     */
    public static final String COLUMN_CYCLE = "cycle";

    /**
     *
     */
    public static final String COLUMN_YEAR = "year";

    /**
     *
     */
    public static final String COLUMN_EVENT_COUNT = "event_count";

    /**
     *
     */
    public static final String COLUMN_TOTAL = "total";

    /**
     *
     */
    public static final String COLUMN_LOW = "low";

    /**
     *
     */
    public static final String COLUMN_RUNNING = "running";

    /**
     *
     */
    public static final String COLUMN_HIGH = "high";

    private final WepsConnection c_con;
    private boolean c_filled;

    /**
     *
     * @param con
     * @throws SQLException
     */
    public ConfidenceIntervalResultSet(WepsConnection con) throws SQLException {
        c_con = con;
        addColumn(COLUMN_RUNID, Types.INTEGER, 10, 0);
        addColumn(COLUMN_ROT_YEARS, Types.INTEGER, 10, 0);
        addColumn(COLUMN_CYCLE, Types.INTEGER, 10, 0);
        addColumn(COLUMN_YEAR, Types.INTEGER, 10, 0);
        addColumn(COLUMN_EVENT_COUNT, Types.DOUBLE, 10, 3);

        addColumn(COLUMN_TOTAL, Types.DOUBLE, 10, 3);
        addColumn(COLUMN_LOW, Types.DOUBLE, 10, 3);
        addColumn(COLUMN_RUNNING, Types.DOUBLE, 10, 3);
        addColumn(COLUMN_HIGH, Types.DOUBLE, 10, 3);

    }

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

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

    /**
     *
     * @throws SQLException
     */
    @Override
    public synchronized void fill() throws SQLException {
        if (c_filled) {
            return;
        }
        TFile[] files = c_con.getRunFiles();

        for (int runIndex = 0; runIndex < files.length; runIndex++) {
            TFile runDir = files[runIndex];

            TFile ciFile = new TFile(runDir, FILE_NAME);

            if (ciFile.exists()) {
                BufferedReader in = null;
                try {
                    in = new BufferedReader(new TFileReader(ciFile));

                    //first line is the header
                    String headerLine = getLine(in);

                    if (headerLine == null || headerLine.trim().isEmpty()) {
                        LOGGER.warn("Confidence interval file is empty: " + ciFile.getAbsolutePath());
                        break;
                    }

                    // list to hold the headers in order
                    List<String> headers = new ArrayList<String>();

                    //parse the header
                    String[] headerParts = headerLine.trim().split("\\|", 0);
                    for (String rawHeader : headerParts) {
                        //trim, lower and replaces inner spaces with underbar
                        rawHeader = rawHeader.trim().toLowerCase().replace(" ", "_");
                        if (rawHeader.startsWith("low")) {
                            rawHeader = "low";
                        } else if (rawHeader.startsWith("high")) {
                            rawHeader = "high";
                        }
                        headers.add(rawHeader);
                    }

                    boolean priming = true;

                    String line;
                    while ((line = getLine(in)) != null) {
                        //each line is a new row

                        String[] parts = line.split("\\|", 0);

                        Map<String, String> rowValues = new HashMap<String, String>();
                        for (int i = 0; i < parts.length; i++) {
                            rowValues.put(headers.get(i), parts[i]);
                        }

                        Object[] row = createNewRow(true);
                        setRowValue(row, COLUMN_RUNID, runIndex);

                        if (priming && rowValues.containsKey("yrly_ave")) {
                            priming = false;
                        }

                        // parse the values out of the row
                        parseInteger(row, rowValues, "nrot_yrs", COLUMN_ROT_YEARS);
                        parseInteger(row, rowValues, "ncycles", COLUMN_CYCLE);
                        parseInteger(row, rowValues, "yr", COLUMN_YEAR);

                        //fixup the priming cycles
                        if (priming) {
                            Integer[] rowInt = new Integer[row.length];
                            for(int item = 0; item < rowInt.length; item ++)
                            {
                                if(row[item] instanceof Integer) rowInt[item] = (Integer) row[item];
                            }
                            fixupCycle(rowInt);
                        }

                        parseDouble(row, rowValues, "#events", COLUMN_EVENT_COUNT);

                        parseDouble(row, rowValues, "yr_total", COLUMN_TOTAL);
                        parseDouble(row, rowValues, "yrly_ave", COLUMN_RUNNING);
                        parseDouble(row, rowValues, "low", COLUMN_LOW);
                        parseDouble(row, rowValues, "high", COLUMN_HIGH);

                        if (isUSUnits()) {
                            fixupUnits(row, COLUMN_TOTAL);
                            fixupUnits(row, COLUMN_LOW);
                            fixupUnits(row, COLUMN_RUNNING);
                            fixupUnits(row, COLUMN_HIGH);
                        }

                    }

                } catch (IOException ioe) {
                    LOGGER.error("Error reading ci file: " + ciFile.getAbsolutePath(), ioe);
                } finally {
                    if (in != null) {
                        try {
                            in.close();
                        } catch (IOException e) {
                            LOGGER.error("Error closing ci file: " + ciFile.getAbsolutePath(), e);
                        }
                    }
                }
            }

        }

        c_filled = true;
    }

    private void fixupCycle(Integer[] row) throws SQLException {
        Integer year = getRowValue(row, COLUMN_YEAR);
        Integer rots = getRowValue(row, COLUMN_ROT_YEARS);

        int cycle = year / rots + (year % rots == 0 ? 0 : 1);
        setRowValue(row, COLUMN_CYCLE, cycle);

    }

    private void fixupUnits(Object[] row, String column) throws SQLException {
        Double value = getRowValue(castToDouble(row), column);
        if (value != null) {

            try {
                value = ConversionCalculator.convert(value, "kg/m^2", "t/ac");
                setRowValue(row, column, value);
            } catch (SQLException | ConversionCalculator.ConversionNotFoundException | ConversionCalculator.UnitNotFoundException e) {
                throw new SQLException(e);
            }
        }
    }

    private void parseInteger(Object[] row, Map<String, String> values, String header, String column) throws SQLException {
        String value = values.get(header);
        if (value == null || value.trim().isEmpty()) {
            setRowValue(row, column, null);
            return;
        }
        try {
            int i = Integer.parseInt(value.trim());
            setRowValue(row, column, i);
        } catch (NumberFormatException nfe) {
            LOGGER.error("Error parsing " + header + ".", nfe);
        }
    }

    private void parseDouble(Object[] row, Map<String, String> values, String header, String column) throws SQLException {
        String value = values.get(header);
        if (value == null || value.trim().isEmpty()) {
            setRowValue(row, column, null);
            return;
        }
        try {
            double i = Double.parseDouble(value.trim());
            setRowValue(row, column, i);
        } catch (NumberFormatException nfe) {
            LOGGER.error("Error parsing " + header + ".", nfe);
        }
    }

    //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) {
                //blank line
                continue;
            }
            if (temp.charAt(0) != '#') {
                //not a comment
                return temp;
            }
        }
        return null;
    }
    
    /**
     * We are getting errors casting an Object[] into a Double[], so I created
     * this method to do it piece by piece.  An entry is null if it is not an
     * Integer or a Double.
     * @param row
     * @return 
     */
    private Double[] castToDouble(Object[] row)
    {
        Double[] result = new Double[row.length];
        for(int index = 0; index < row.length; index ++)
        {
            if(row[index] instanceof Double) result[index] = (Double) row[index];
            else if(row[index] instanceof Integer)
            {
                int temp = (Integer) row[index];
                double val = temp;
                result[index] = val;
            }
            else result[index] = null;
        }
        return result;
    }

}
