package usda.weru.weps.reports.query.parse;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;

/**
     * This class will store the proper overall interval, by storing the start date, 
     * end date, and plant date.  It also monitors all subintervals attatched to the
     * crop.
     * @author jonathanhornbaker
     */
public class Interval
{
    private static final Logger LOGGER = Logger.getLogger(Interval.class);
    
    private final Date begin;
    private final Date termin;
    private final Date plant;
    private final String cropName;
    private boolean isWrap;
    private List<Interval> subInts;
    static DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");

    public Interval(Date start, Date end, Date plantDate, String name)
    {
        begin = start;
        termin = end;
        plant = plantDate;
        cropName = name;
        subInts = new ArrayList<Interval>();
        if(plantDate != null) subInts.add(new Interval(start, plant, null, null));
    }

    /**
     * An interval object is defined to be a subinterval if it has no plant date,
     * as it's parent will handle the plant date.
     * @return 
     */
    public boolean isSubInterval() { return plant == null; }

    /**
     * Asserts that the subIntervals end on the end date.  Returns true if all
     * was well, false if it had to correct.
     * 
     * Correction is done by removing subintervals with term dates after the
     * parent's term date and add a new interval stretching from the last subinterval
     * termination to the current termination date if the last interval is after.
     * @return 
     */
    public boolean finalizeSubInts()
    {
        DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");

        boolean returnCase = true;
        if(subInts.size() == 0) return returnCase; //If there are no subintervals, all subintervals match.
        Date finalTerm = subInts.get(subInts.size() - 1).termin;
        if(termin.equals(finalTerm)) return true;
        else if(isWrap)
        {
            subInts.add(new Interval(subInts.get(subInts.size() - 1).termin, termin, null, null));
            return false;
        } 
        else while(termin.before(finalTerm))
        {
            subInts.remove(subInts.size() - 1);
            finalTerm = subInts.get(subInts.size() - 1).termin;
            returnCase = false;
        }
        if(termin.after(finalTerm))
        {
            subInts.add(new Interval(subInts.get(subInts.size() - 1).termin, termin, null, null));
            returnCase = false;
        }
        return returnCase;
    }

    /**
     * Sets the boolean indicating that this interval wraps to true;
     */
    private void setWrap() { isWrap = true; }

    /**
     * Returns true if this is a multiharvest (i.e. has a preplant and multiple
     * postplants).
     * @return 
     */
    public boolean isMultiHarv()
    {
        return (subInts.size() > 2);
    }

    /**
     * Returns all the terminations associated with the plant.
     * @return 
     */
    public List<Interval> assembleMultiHarv()
    {
        ArrayList<Interval> storage = new ArrayList<Interval>();
        if(isMultiHarv())
        {
            for(int index = 1; index < subInts.size(); index ++)
            {
                storage.add(subInts.get(index));
            }
        }
        else storage.add(subInts.get(1));
        return storage;
    }
    
    /**
     * Returns the plant date of the interval;
     * @return 
     */
    public Date getPlant() { return plant; }
    
    /**
     * Returns the crop name associated with the interval.  Subintervals
     * return null.
     * @return
     */
    public String getCrop() { return cropName; }
    
    /**
     * Returns the plant date of the interval;
     * @return 
     */
    public Date getBegin() { return begin; }

    /**
     * Returns the plant date of the interval;
     * @return 
     */
    public Date getTermin() { return termin; }

    /**
     * Appends the specified subints to the (already initialized) preplant.
     * @param ints 
     */
    public void setSubIntervals(List<Interval> ints)
    {
        subInts.addAll(ints);
        finalizeSubInts();
    }
    
    /**
     * This method will take the first line of season.out and use it to calculate the
     * correct begining and ending dates for the crop interval, with the plant dates
     * thrown in for good measure.
     * 
     * Note:  this method was written for the WEPS 1.5.38 release, and thus uses
     * the interval delimeter defined by that code.  Any changes to season.out's
     * structure will require a change to this parameter.
     * @param seasonLine 
     */
    public static List<Interval> generateProperIntervals(String[] seasonLine, int numYears)
    {
        List<Interval> correctIntervals = new ArrayList<Interval>();
        try
        {
            if(seasonLine.length == 0) return correctIntervals; //There is no crop.
            if(seasonLine.length == Interval.seasonCycleLength) //There is only one crop.
            {
                correctIntervals.add(new Interval(dateFormat.parse(seasonLine[1]), 
                        dateFormat.parse(seasonLine[1]), dateFormat.parse(seasonLine[0]), 
                        seasonLine[2]));
                return correctIntervals;
            }
            int firstIndex = 0;
            Date firstTerm = null;
            Date prevTerm = null;
            Date curPlant = dateFormat.parse(seasonLine[0]);
            Date curTerm = dateFormat.parse(seasonLine[1]);
            String cropName = seasonLine[2];
            boolean unlinked = false;
            List<Interval> storage = new ArrayList<Interval>();
            for(int index = Interval.seasonCycleLength; index < seasonLine.length - 1; index += Interval.seasonCycleLength)
            {
                Date nextPlant = dateFormat.parse(seasonLine[index]);
                Date nextTerm = dateFormat.parse(seasonLine[index + 1]);
                if(curPlant.equals(nextPlant)) //This is the same plant, and thus the current interval is a subinterval.
                {
                    unlinked = true;
                    if(firstTerm == null) 
                    {
                        curTerm = nextTerm;
                        continue;
                    }  //We handle subintervals for the first plant elsewhere.
                    if(storage.size() == 0) //We need to handle both the current subinterval and the initial subinterval.
                    {
                        storage.add(new Interval(curPlant, curTerm, null, null));
                    }
                    storage.add(new Interval(curTerm, nextTerm, null, null));
                    curTerm = nextTerm;
                }
                else //We have the end of the interval.
                {
                    unlinked = true;
                    if(firstTerm == null)  //The first interval wraps.  Store it's state for later.
                    {
                        firstTerm = curTerm;
                        firstIndex = index - Interval.seasonCycleLength;
                        prevTerm = curTerm;
                        curPlant = nextPlant;
                        curTerm = nextTerm;
                        cropName = seasonLine[index + 2];
                        continue;
                    }
                    else
                    {
                        Interval inter = new Interval(prevTerm, curTerm, curPlant, cropName);
                        if(storage.size() != 0) 
                        {
                            inter.setSubIntervals(storage);
                            storage = new ArrayList<Interval>();
                        }
                        else inter.finalizeSubInts();
                        correctIntervals.add(inter);
                        prevTerm = curTerm;
                        curPlant = nextPlant;
                        curTerm = nextTerm;
                        cropName = seasonLine[index + 2];
                    }
                }
            }
            if(prevTerm == null)
            {
                storage = new ArrayList<Interval>();
                //There's only one crop.  We need to find the last manipulation to the crop.
                curPlant = dateFormat.parse(seasonLine[0]);
                try
                {
                    String[] plantTest = seasonLine[0].split("/");
                    String yearOne = plantTest[plantTest.length - 1];
                    int firstYear = Integer.parseInt(yearOne.trim());
                    if(firstYear < 1)
                    {
                        String firstPlantString = "";
                        firstYear += numYears;
                        plantTest[plantTest.length - 1] = Integer.toString(firstYear);
                        for(String item : plantTest) firstPlantString += item + "/";
                        curPlant = dateFormat.parse(firstPlantString);
                    }
                }
                catch(NumberFormatException nfe) {}
                curTerm = dateFormat.parse(seasonLine[1]);
                storage.add(new Interval(curPlant, curTerm, null, null));
                //Termination is the last date in the file.
                for(int index = Interval.seasonCycleLength; index < seasonLine.length - 1; index += Interval.seasonCycleLength)
                {
                    Date nextTerm = dateFormat.parse(seasonLine[index + 1]);
                    storage.add(new Interval(curTerm, nextTerm, null, null));
                    curTerm = nextTerm;
                }
                Interval inter = new Interval(curTerm, curTerm, curPlant, seasonLine[2]);
                if(storage.size() != 0) 
                {
                    inter.setSubIntervals(storage);
                    storage = new ArrayList<Interval>();
                }
                else inter.finalizeSubInts();
                correctIntervals.add(inter);
                curPlant = dateFormat.parse(seasonLine[0]);
            }
            else
            {
                if(unlinked)
                {
                    Date firstPlant = dateFormat.parse(seasonLine[0]);
                    try
                    {
                        String[] plantTest = seasonLine[0].split("/");
                        String yearOne = plantTest[plantTest.length - 1];
                        int firstYear = Integer.parseInt(yearOne.trim());
                        if(firstYear < 1)
                        {
                            String firstPlantString = "";
                            firstYear += numYears;
                            plantTest[plantTest.length - 1] = Integer.toString(firstYear);
                            for(String item : plantTest) firstPlantString += item + "/";
                            firstPlant = dateFormat.parse(firstPlantString);
                        }
                    }
                    catch(NumberFormatException nfe) {}
                    if(curPlant.equals(firstPlant))
                    {
                        if(storage.size() == 0) storage.add(new Interval(curPlant, curTerm, null, null));
                        for(int index = 1; index <= firstIndex; index += Interval.seasonCycleLength)
                        {
                            Date nextTerm = dateFormat.parse(seasonLine[index]);
                            storage.add(new Interval(curTerm, nextTerm, null, null));
                            curTerm = nextTerm;
                        }
                        Interval inter = new Interval(prevTerm, firstTerm, curPlant, seasonLine[2]);
                        inter.setWrap();
                        if(storage.size() != 0) 
                        {
                            inter.setSubIntervals(storage);
                            storage = new ArrayList<Interval>();
                        }
                        else inter.finalizeSubInts();
                        correctIntervals.add(inter);
                        firstIndex = -1;

                    }
                    if(firstIndex != -1)
                    {
                        Interval inter = new Interval(prevTerm, curTerm, curPlant, cropName);
                        if(storage.size() != 0) 
                        {
                            inter.setSubIntervals(storage);
                            storage = new ArrayList<Interval>();
                        }
                        else inter.finalizeSubInts();
                        correctIntervals.add(inter);
                        curPlant = firstPlant;
                    }
                }
                //We now need to handle the wrapping interval.
                if(firstIndex != -1)
                {
                    Interval inter = new Interval(curTerm, firstTerm, curPlant, seasonLine[2]);
                    for(int index = 1; index < firstIndex; index += Interval.seasonCycleLength)
                    {
                        Date nextTerm = dateFormat.parse(seasonLine[index]);
                        storage.add(new Interval(curTerm, nextTerm, null, null));
                        curTerm = nextTerm;
                    }
                    inter.setWrap();
                    if(storage.size() != 0) inter.setSubIntervals(storage);
                    else inter.finalizeSubInts();
                    correctIntervals.add(0, inter);
                }
            }
        }
        catch(ParseException pe) 
        {
            LOGGER.error("Season Cycle Length in Interval class (line 276) No longer accurate,"
                    + "Or incorrect Season file.  Intervals failed to initialize.");
        }
        return correctIntervals;
    }

    /**
     * Note:  this constant was written for the WEPS 1.5.38 release, and thus uses
     * the interval delimeter defined by that code.  Any changes to season.out's
     * structure will require a change to this parameter.
     */
    public static final int seasonCycleLength = 25;
    
    
    public static void main(String[] args)
    {
        try
        {
            String[] seasonLine = {};
                TFile seasonFile = new TFile("/home/jonathanhornbaker/Documents/WEPSFiles/Runs/alfalfa_4_test.wjr/season.out");
                int rot_nums = 4;
                if (seasonFile.exists()) 
                {
                    //Reads text from a character-input stream, 
                    //buffering characters so as to provide for the efficient reading of characters, arrays, and lines. 
                    BufferedReader season_f = new BufferedReader(new TFileReader(seasonFile));
                    String season_l = Interval.getLine(season_f);
                    seasonLine = season_l.split("\\|", -1);
                }

            List<Interval> intervals = Interval.generateProperIntervals(seasonLine, rot_nums);
            
            int hello = 5;
        }
        catch(IOException ioe) {}
    }
    
    /**
     * This method will read in a line of the input file, ignoring comment lines
     * and empty lines.  It is a basic utility function.
     * @param in
     * @return
     * @throws IOException 
     */
    public static 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;
    }
    
    public static int getLeap(Date first, Date second)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(first);
        int feb29 = 0;
        int yearFirst = cal.get(Calendar.YEAR);
        try 
        { 
            Date leap = dateFormat.parse("29/ 2/" + yearFirst); 
            if(first.after(leap)) yearFirst ++;            
        }
        catch(ParseException pe) { System.out.println("Failed to parse:  " + " 29/ 2/" + yearFirst); }
        cal = Calendar.getInstance();
        cal.setTime(second);
        int yearLast = cal.get(Calendar.YEAR);
        for(; yearFirst <= yearLast; yearFirst ++)
        {
            if((((yearFirst % 4) == 0) && ((yearFirst % 100) != 0)) 
                    || ((yearFirst % 400) == 0)) feb29 ++;
        }
        return feb29;
    }
}
