/*
 * RunWrapper.java
 *
 * Created on January 26, 2006, 4:52 PM
 *
 */
package usda.weru.wmrm;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileReader;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import usda.weru.weps.*;

import java.util.*;
import org.apache.logging.log4j.Level;
import static org.apache.logging.log4j.LogManager.getLogger;
import org.apache.logging.log4j.Logger;
import usda.weru.util.ReportContext;

/**
 * Wraps the RunFileData class in order to provide access and formatting to run
 * data.
 *
 * @author Joseph Levin
 */
public class RunWrapper extends RunFileData {

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

    public enum DataTag {

        RunName("RunName"),
        RunLocation("RunLocation"),
        ClientName("ClientName"),
        FarmNo("FarmNo"),
        TractNo("TractNo"),
        FieldNo("FieldNo"),
        ManagementName("ManagementName"),
        SoilName("SoilName"),
        FieldSize("FieldSize"),
        XLength("xLength"),
        YLength("yLength"),
        Orientation("orientation"),
        Shape("shape");
        
        private final String[] c_tags;

        DataTag(String... tags) {
            c_tags = tags;
        }

        public boolean accept(String tag) {
            if (tag == null) {
                return false;
            }
            for (String validTag : c_tags) {
                if (tag.equalsIgnoreCase(validTag)) {
                    return true;
                }
            }
            return false;
        }

        public String getFirstTag() {
            if (c_tags.length > 0) {
                return c_tags[0];
            } else {
                return null;
            }
        }
    }
    /**
     * Run top level directory.
     */
    private final TFile c_runDirectory;
    private TFile c_soilFile;
    private RunDataAge c_age;
    List<String> c_headers;
    /**
     * Stores the annual totals for the run data against a key string of the
     * header name.
     */
    private Map<String, Double> c_outputData = null;

    /**
     * Creates a new instance of RunWrapper
     *
     * @param pathName The run file directory.
     */
    public RunWrapper(String pathName) {
        super(true);
        c_displayMessages = false;
        c_runDirectory = new TFile(pathName);
        updateData();
    }

    public RunWrapper(String pathName, boolean bypassUpdate) {
        super(true);
        c_displayMessages = false;
        c_runDirectory = new TFile(pathName);
        updateData(bypassUpdate);

    }

    /**
     * Looks up the DataTag enum object for the given string.
     *
     * @param key String value to find a DataTag for.
     * @return DataTag enum or null value if no tag is found.
     */
    private DataTag getDataTagFromKey(String key) {
        for (DataTag tag : DataTag.values()) {
            if (tag.accept(key)) {
                return tag;
            }
        }
        return null;
    }

    /**
     * Top level method for getting a value. If a DataTag enum is found for the
     * given key then the value is returned from a wrapper method. Otherwise the
     * value is return from the output data.
     *
     * @param key String key to lookup and return a value for.
     * @return The value as an object.
     */
    public Object getValue(String key) {
        DataTag tag = getDataTagFromKey(key);
        if (tag != null) {
            return getWrapperValue(tag);
        } else {
            return getOutputValue(key);
        }
    }

    public String getToolTipText(String key) {
        DataTag tag = getDataTagFromKey(key);
        if (tag != null) {
            TFile file = null;
            switch (tag) {
                case RunLocation:
                    file = fileObject.getParentFile();
                    if (file != null) {
                        return file.getAbsolutePath();
                    } else {
                        return null;
                    }

                case SoilName:
                    file = c_soilFile;
                    if (file != null) {
                        return file.getAbsolutePath();
                    } else {
                        return null;
                    }
                case ManagementName:
                    file = new TFile(getData(RunFileData.ManageFile));
                    if (file != null) {
                        return file.getAbsolutePath();
                    } else {
                        return null;
                    }

                default:
                    return null;
            }
        } else {
            return null;
        }
    }

    public double getOutputValue(String header) {
        try {
            Double result = c_outputData.get(header);
            if (result == null) {
                return Double.NaN;
            } else {
                return result;
            }
        } catch (NullPointerException npe) {
            return Double.NaN;
        }
    }

    public Object getWrapperValue(DataTag tag) {
        if (tag == null) {
            return null;
        }
        try {
            switch (tag) {
                case RunName:
                    if (fileObject == null) {
                        return "";
                    }
                    String runName = fileObject.getParentFile().getName();
                    runName = runName.replaceFirst(RunFileData.RunSuffix, "");
                    return runName;
                case RunLocation:
                    TFile location = fileObject.getParentFile().getParentFile();
                    if (location != null) {
                        return location.getAbsolutePath();
                    } else {
                        return null;
                    }
                case ClientName:
                    return super.getData(RunFileData.UserName);
                case FarmNo:
                    return super.getData(RunFileData.FarmId);
                case TractNo:
                    return super.getData(RunFileData.TractId);
                case FieldNo:
                    return super.getData(RunFileData.FieldId);
                case ManagementName:
                    TFile managementFile = new TFile(getData(RunFileData.ManageFile));
                    String managementName = managementFile.getName();
                    managementName = managementName.replaceFirst(".man", "");
                    return managementName;
                case SoilName:
                    if (c_soilFile != null) {
                        String soilName = c_soilFile.getName();
                        soilName = soilName.replaceFirst(".ifc", "");
                        return soilName;
                    }
                    return null;
                case FieldSize:
                    double xLen = Double.parseDouble(super.getData(RunFileData.XLength));
                    double yLen = Double.parseDouble(super.getData(RunFileData.YLength));
                    return xLen * yLen;
                case XLength:
                    return Double.parseDouble(super.getData(RunFileData.XLength));
                case YLength:
                    return Double.parseDouble(super.getData(RunFileData.YLength));
                case Orientation:
                    return Double.parseDouble(super.getData(RunFileData.RegionAngle));
                case Shape:
                    return super.getData(RunFileData.Shape);
            }
        } catch (NumberFormatException e) {
            LOGGER.error("Unable to get data: " + tag);
        }
        return null;
    }

    public List<String> getDataKeys() {
        return c_headers != null ? c_headers : Collections.<String>emptyList();
    }

    public TFile getSoilFile() {
        return c_soilFile;
    }

    public void updateData() {
        updateData(false);
    }

    public void updateData(boolean bypassUpdate) {
        ReportContext.enter();
        try {

            if ((c_runDirectory.getAbsolutePath() != null) && (c_runDirectory != null) && c_runDirectory.exists()) {
                LOGGER.debug("Updating: " + c_runDirectory.getAbsolutePath());
                //clear first
                initialize();
                //System.out.println("bypassUpdate in RunWrapper is " + bypassUpdate);
                readRunFile(c_runDirectory.getAbsolutePath(), bypassUpdate);
                c_soilFile = new TFile(getData(RunFileData.SoilFile));
                c_outputData = loadOutputData(new TFile(c_runDirectory, RunFileData.WepsOutput));
                Map<String, Double> SCIValues = loadOutputDataSCI(new TFile(c_runDirectory, RunFileData.SCIOutput));
                //Map<String, Double> EnergyValues = loadOutputDataEnergy(new TFile(c_runDirectory, RunFileData.EnergyOutput));
                c_outputData.putAll(SCIValues);
                //c_outputData.putAll (EnergyValues);
//                Map<String, Double> c_outputData2 = loadOutputData(new TFile(c_runDirectory, "sci_energy.out"));
//                c_outputData.putAll(c_outputData2);
//                Map<String, Double> c_outputData2 = loadOutputData(new TFile(c_runDirectory, "sci_energy.out"));
//                c_outputData.putAll(c_outputData2);
                updateAge();
            }
        } catch (Exception e) {
            LOGGER.error("Unable to load data: " + c_runDirectory.getAbsolutePath(), e);
        } finally {
            ReportContext.exit();
        }
    }

    private Map<String, Double> loadOutputData(TFile file) {
        Map<String, Double> values = new HashMap<>();
        ArrayList<String> lines = new ArrayList<>();
        ArrayList<String> headers = new ArrayList<>();
        String inLine;
        if (!file.exists()) {
            //the data file does not exist.
            return Collections.emptyMap();
        }
        //read the file one line at a time into a vector for processing
        BufferedReader in;
        try {
            in = new BufferedReader(new TFileReader(file));
        } catch (FileNotFoundException ex) {
            LOGGER.log(Level.ERROR, ex);
            throw new RuntimeException(ex.toString());
        }
        try {
            do {
                inLine = in.readLine();
                if (inLine != null) {
                    lines.add(inLine);
                }
            } while (inLine != null);
            in.close();

        } catch (IOException ex) {
            LOGGER.log(Level.ERROR, ex);
            return values;
        }

        for (String line : lines) {
            //We have the header line.
            if (line.startsWith("key")) {
                StringTokenizer st = new StringTokenizer(line, "|");
                while (st.hasMoreTokens()) {
                    headers.add(st.nextToken().trim());
                }
                c_headers = headers;
            } //We have the totals line.
            else if (line.trim().startsWith("T")) {
                String[] st = line.split("\\|");
                if (st.length != headers.size()) {
                    continue;
                }
                for (int index = 0; index < st.length; index++) {
                    Double newValue;
                    String value = st[index].trim();
                    try {
                        newValue = Double.parseDouble(value);
                    } catch (NumberFormatException nfe) {
                        newValue = Double.valueOf(0);
                    }
                    values.put(headers.get(index), newValue);
                }
            }
        }
        return values;
    }

    private Map<String, Double> loadOutputDataSCI(TFile file) {
        Map<String, Double> values = new HashMap<>();
        ArrayList<String> lines = new ArrayList<>();
        ArrayList<String> headers = new ArrayList<>();
        String inLine;
        if (!file.exists()) {
            //the data file does not exist.
            return Collections.emptyMap();
        }
        //read the file one line at a time into a vector for processing
        BufferedReader in;
        try {
            in = new BufferedReader(new TFileReader(file));
        } catch (FileNotFoundException ex) {
            LOGGER.log(Level.ERROR, ex);
            throw new RuntimeException(ex.toString());
        }
        try {
            do {
                inLine = in.readLine();
                if (inLine != null) {
                    lines.add(inLine);
                }
            } while (inLine != null);
            in.close();

        } catch (IOException ex) {
            LOGGER.log(Level.ERROR, ex);
            return values;
        }

        for (String line : lines) {
            //We have the header line.
            if (line.trim().startsWith("#")) {
                line = line.trim();
                line = line.substring(1);
                String[] st = line.split("\\|");
                for (int i = 0; i < st.length; i++) {
                    st[i] = st[i].replaceAll("\\s", "");
                }
                headers = new ArrayList<>(Arrays.asList(st));
                c_headers = headers;
            } //We have the totals line.
            else {
                String[] st = line.split("\\|");
                if (st.length != headers.size()) {
                    continue;
                }
                for (int index = 0; index < st.length; index++) {
                    Double newValue;
                    String value = st[index].replaceAll("\\s", "");
                    try {
                        newValue = Double.parseDouble(value);
                    } catch (NumberFormatException nfe) {
                        newValue = Double.valueOf(0);
                    }
                    values.put(headers.get(index), newValue);
                }
            }
        }
        double dEnergy = values.get("diesel_energy_L/ha");
        /* values.put("diesel_energy_L/ha", dEnergy / 9.354); */
        /* Not sure where the incorrect 9.354 conversion value came from - LEW 12/7/21 */
        values.put("diesel_energy_L/ha", dEnergy);
        return values;
    }

    public TFile getDirectory() {
        return c_runDirectory;
    }

    @Override
    public String toString() {
        return getWrapperValue(DataTag.RunName).toString();
    }

    public boolean upToDate() {
        RunDataAge temp = new RunDataAge(c_runDirectory);
        return temp.equals(c_age);
    }

    public boolean exists() {
        return c_runDirectory.exists();
    }

    private void updateAge() {
        c_age = new RunDataAge(c_runDirectory);
    }

    protected static class RunDataAge {

        private long c_ageMin;
        private long c_ageMean;
        private long c_ageMax;
        private int c_n;

        public RunDataAge(TFile dir) {
            if (dir == null) {
                return;
            }
            c_ageMin = dir.lastModified();
            c_ageMax = c_ageMin;
            c_ageMean = c_ageMin;
            c_n = 1;
            TFile[] children = dir.listFiles();
            if (children != null) {
                for (TFile child : children) {
                    c_ageMin = Math.min(c_ageMin, child.lastModified());
                    c_ageMax = Math.max(c_ageMax, child.lastModified());
                    c_ageMean = ((c_ageMean * c_n++) + child.lastModified()) / c_n;
                }
            }
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final RunDataAge other = (RunDataAge) obj;
            if (this.c_ageMin != other.c_ageMin) {
                return false;
            }
            if (this.c_ageMean != other.c_ageMean) {
                return false;
            }
            if (this.c_ageMax != other.c_ageMax) {
                return false;
            }
            return this.c_n == other.c_n;
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 79 * hash + (int) (this.c_ageMin ^ (this.c_ageMin >>> 32));
            hash = 79 * hash + (int) (this.c_ageMean ^ (this.c_ageMean >>> 32));
            hash = 79 * hash + (int) (this.c_ageMax ^ (this.c_ageMax >>> 32));
            hash = 79 * hash + this.c_n;
            return hash;
        }
    }
}
