package usda.weru.weps;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Stack;
import javax.measure.Measurable;
import javax.measure.Measure;
import javax.measure.quantity.Duration;
import javax.measure.quantity.Length;
import javax.measure.unit.NonSI;
import javax.measure.unit.SI;
import org.jscience.geography.coordinates.LatLong;
import usda.weru.util.LoadingContext;
import usda.weru.weps.location.CligenStation;
import usda.weru.weps.location.ElevationMode;
import usda.weru.weps.location.Site;
import usda.weru.weps.location.StationMode;
import usda.weru.weps.location.WindgenStation;

/**
 * Reads and writes a RunFileBean in the weps.run format required for the fortran
 * science model.
 *
 * @author Joseph Levin <joelevin@weru.ksu.edu>
 */
public class RunFileFlatFormat {

    /**
     *
     */
    public static final float VERSION_0 = 0.0f;

    /**
     *
     */
    public static final float VERSION_CURRENT = VERSION_0;

    private RunFileFlatFormat() {
        c_lineStack = new Stack<String>();
    }

    /**
     *
     */
    protected Stack<String> c_lineStack;

    /**
     *
     * @return
     */
    public final static RunFileFlatFormat get() {
        return new RunFileFlatFormat();
    }

    private static RunFileFlatFormat get(float version) {
        if (version <= 0) {
            return new Version_0();
        }

        throw new IllegalStateException("Unknown version: " + String.valueOf(version));
    }

    /**
     *
     * @param in
     * @return
     * @throws IOException
     */
    protected String popLine(BufferedReader in) throws IOException {
        if (!c_lineStack.empty()) {
            return c_lineStack.pop();
        } else {
            return in.readLine();
        }
    }

    /**
     *
     * @param in
     * @param skipComments
     * @param skipBlankLines
     * @return
     * @throws IOException
     */
    protected String popLine(BufferedReader in, boolean skipComments, boolean skipBlankLines) throws IOException {
        //TODO: Create a way to back up lines, perhaps a stack would work
        String line;
        while ((line = in.readLine()) != null) {
            if (skipBlankLines && line.trim().length() == 0) {
                continue;
            }

            if (skipComments && line.startsWith("#")) {
                continue;
            }

            return line;
        }
        return null;
    }

    /**
     *
     * @param in
     * @return
     * @throws IOException
     */
    protected String peekLine(BufferedReader in) throws IOException {
        if (!c_lineStack.empty()) {
            return c_lineStack.peek();
        } else {
            String line = in.readLine();
            pushLine(line);
            return line;
        }
    }

    /**
     *
     * @param line
     */
    protected void pushLine(String line) {
        c_lineStack.push(line);
    }

    /**
     *
     * @param bean
     * @param in
     */
    public final void read(RunFileBean bean, InputStream in) {
        LoadingContext.enter();
        try {
            //create buffered reader so we can handle lines as strings

            BufferedReader reader = new BufferedReader(new InputStreamReader(in));

            String firstLine = peekLine(reader);

            float version = determineVersion(firstLine);

            RunFileFlatFormat subFormat = get(version);

            subFormat.readData(bean, reader);

            int a = 0;

        } catch (IOException ioe) {
            ioe.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            LoadingContext.exit();
        }
    }

    /**
     *
     * @param line 
     * @return version number or 0 if now version found
     * @throws IOException 
     */
    protected float determineVersion(String line) throws IOException {

        if (line == null) {
            return 0;
        }

        if (line.startsWith("#VERSION=")) {
            String versionString = line.substring(9).trim();
            try {
                float version = Float.valueOf(versionString);
                return version;
            } catch (NumberFormatException e) {
                //unable to parse the number
                throw new IOException("Unable to parse version: " + line, e);
            }
        }

        return 0f;
    }

    /**
     *
     * @param bean
     * @param reader
     * @throws IOException
     */
    protected void readData(RunFileBean bean, BufferedReader reader) throws IOException {
        throw new UnsupportedOperationException("Should be hanlded by a sub format that is versioned.");
    }

    /**
     * Location panel and site rewrite.
     */
    private static class Version_1_04 extends Version_1_03 {

    }

    /**
     * Location panel rewrite.  Few of these should be in the wild.
     */
    private static class Version_1_03 extends Version_1_02 {

    }

    /**
     * Added water rock fragments
     */
    private static class Version_1_02 extends Version_1_01 {

    }

    /**
     * Added water erosion value
     */
    private static class Version_1_01 extends Version_0 {

    }

    /**
     * Base version.
     */
    private static class Version_0 extends RunFileFlatFormat {

        @Override
        protected void readData(RunFileBean bean, BufferedReader reader) throws IOException {
            readClientName(bean, reader);
            readFarm_Tract_Field_Mode_RotationYears_CycleCount(bean, reader);
            readSite(bean, reader);
            readLatLon(bean, reader);
            readElevation(bean, reader);
            readCligenStation(bean, reader);
            readWindgenStation(bean, reader);
            readSimulationPeriodDates(bean, reader);
            readTimeSteps(bean, reader);
        }

        protected String cleanString(String s) {
            s = s != null ? s.trim() : null;

            //no point in passing around an empty string
            if (s != null && s.length() == 0) {
                s = null;
            }
            return s;
        }

        protected String popDataLine(BufferedReader reader) throws IOException {
            return popLine(reader, true, false);
        }

        protected void readClientName(RunFileBean bean, BufferedReader reader) throws IOException {
            String name = popDataLine(reader);
            name = cleanString(name);
            bean.setClientName(name);
        }

        protected void readFarm_Tract_Field_Mode_RotationYears_CycleCount(
                RunFileBean bean, BufferedReader reader) throws IOException {
            String line = popDataLine(reader);

            if (line == null) {
                return;
            }

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

            if (parts.length != 6) {
                throw new IllegalStateException("Expected 6 elements.");
            }

            bean.setClientFarm(cleanString(parts[0]));
            bean.setClientTract(cleanString(parts[1]));
            bean.setClientField(cleanString(parts[2]));

            String modeString = parts[3];
            modeString = cleanString(modeString);
            RunMode mode = RunMode.valueOfIgnoreCase(modeString);
            bean.setRunMode(mode);

            String rotationLengthString = parts[4];
            rotationLengthString = cleanString(rotationLengthString);
            Long rotationLengthLong = Long.valueOf(rotationLengthString);
            Measurable<Duration> rotationLength = Measure.valueOf(rotationLengthLong, NonSI.YEAR);
            bean.setRotationLength(rotationLength);

            String cycleCountString = parts[4];
            cycleCountString = cleanString(cycleCountString);
            Integer cycleCount = Integer.valueOf(cycleCountString);
            bean.setCycleCount(cycleCount);

        }

        /**
         sets the site to the most specific one found in 'reader'
         @param bean
         @param reader
         @throws IOException 
         */
        protected void readSite(RunFileBean bean, BufferedReader reader) throws IOException {
            String siteString = popDataLine(reader);
            siteString = cleanString(siteString);

            String[] parts = siteString.split(",", -1);

            String countyName = cleanString(parts[0]);
            String stateName = cleanString(parts[1]);

            Site.Level0 usa = Site.UNITED_STATES;
            Site.Level1 site = null;
            for (Site.Level1 state : usa.getSubDivisions()) {
                if (state.getDisplayName().equalsIgnoreCase(stateName)) {
                    site = state;
                    break;
                }
            }
            if (site != null) {
                Site.Level2 site2 = null;
                for (Site.Level2 county : site.getSubDivisions()) {
                    if (county.getDisplayName().equalsIgnoreCase(countyName)) {
                        site2 = county;
                        break;
                    }
                }
                if (site2 != null) {
                    bean.setSite(site2);
                } else {
                    bean.setSite(site);
                }

            } else {
                bean.setSite(null);
            }

        }

        protected void readLatLon(RunFileBean bean, BufferedReader reader) throws IOException {
            String latLine = popDataLine(reader);
            double lat = Double.parseDouble(latLine.trim());

            String lonLine = popDataLine(reader);
            double lon = Double.parseDouble(lonLine);

            LatLong latlon = LatLong.valueOf(lat, lon, NonSI.DEGREE_ANGLE);
            bean.setLatLong(latlon);
        }

        protected void readElevation(RunFileBean bean, BufferedReader reader) throws IOException {
            String elevationLine = popDataLine(reader);
            double elevationDouble = Double.valueOf(elevationLine);
            Measurable<Length> elevation = Measure.valueOf(elevationDouble, SI.METER);
            //TODO: Add support for other cligen modes
            bean.setElevationMode(ElevationMode.Cligen);
            bean.setElevation(elevation);
        }

        protected void readCligenStation(RunFileBean bean, BufferedReader reader) throws IOException {
            String cligenLine = popDataLine(reader);
            String[] parts = cligenLine.split("\\|", -1);
            String type = parts[3];
            if (!"0".equals(type.trim())) {
                bean.setCligenStationMode(StationMode.File);
                //not marked as a cligen station, set as null now and will load as a file later
                bean.setCligenStation(null);
                return;
            } else {
                //TODO: might need to default to something else when loading older files
                bean.setCligenStationMode(StationMode.Choice);
            }
            String name = parts[0].trim();
            String idText = parts[1].trim();
            long id = Long.valueOf(idText);
            long state = Long.valueOf(parts[2].trim());
            CligenStation station = new CligenStation(name, state, id);
            bean.setCligenStation(station);

        }

        protected void readWindgenStation(RunFileBean bean, BufferedReader reader) throws IOException {
            String windgenLine = popDataLine(reader);
            String[] parts = windgenLine.split("\\|", -1);
            String type = parts[2];
            if (!"0".equals(type.trim())) {
                bean.setWindgenStationMode(StationMode.File);
                //not marked as a cligen station, set as null now and will load as a file later
                bean.setCligenStation(null);
                return;
            } else {
                //TODO: might need to default to something else when loading older files
                bean.setWindgenStationMode(StationMode.Choice);
            }
            String name = parts[0].trim();
            String idText = parts[1].trim();
            long id = Long.valueOf(idText);
            WindgenStation station = new WindgenStation(null, id, "US", null, name);
            bean.setWindgenStation(station);
        }

        protected void readSimulationPeriodDates(RunFileBean bean, BufferedReader reader) throws IOException {
            DateFormat format = new SimpleDateFormat("dd MM yy");
            String startLine = popDataLine(reader).trim();
            String endLine = popDataLine(reader).trim();
            try {
                if (startLine.length() > 0) {
                    Date startDate = format.parse(startLine);
                    bean.setSimulationStartDate(startDate);
                }
            } catch (ParseException pe) {
                throw new IOException(pe);
            }
            try {
                if (endLine.length() > 0) {
                    Date endDate = format.parse(endLine);
                    bean.setSimulationEndDate(endDate);
                }
            } catch (ParseException pe) {
                throw new IOException(pe);
            }
        }

        protected void readTimeSteps(RunFileBean bean, BufferedReader reader) throws IOException {
            String stepLine = popDataLine(reader);
            Integer steps = Integer.valueOf(stepLine.trim());
            bean.setTimeSteps(steps);

        }

        public static void main(String[] args) throws Exception {
            File testA = new File("C:/usr/joelevin/weru/weps1/weps.install/projects/"
                    + "DisturbedLands/crimping.wpj/standing200.wjr/weps.run");

            RunFileFlatFormat format = RunFileFlatFormat.get();

            RunFileBean bean = new RunFileBean();
            format.read(bean, new FileInputStream(testA));
        }
    }
}
