/*
 * Created on 08.11.2004
 */
package net.sourceforge.ganttproject.time.gregorian;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import net.sourceforge.ganttproject.calendar.WeekendCalendarImpl;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.time.TimeFrame;
import net.sourceforge.ganttproject.time.TimeUnit;
import net.sourceforge.ganttproject.time.TimeUnitFunctionOfDate;
import net.sourceforge.ganttproject.time.TimeUnitGraph;
import net.sourceforge.ganttproject.time.TimeUnitPair;
import net.sourceforge.ganttproject.time.TimeUnitStack;
import net.sourceforge.ganttproject.time.WorkingTimeCalendar;

/**
 * @author bard
 */
public class GPTimeUnitStack implements TimeUnitStack {
    private TimeUnitGraph ourGraph = new TimeUnitGraph();

    public final TimeUnit HOUR;
    
    public final TimeUnit DAY;

    public final TimeUnit WEEK;

    public final TimeUnitFunctionOfDate MONTH;

    public TimeUnit QUARTER;

    public TimeUnitFunctionOfDate YEAR = null;

    private TimeUnitPair[] myPairs;

    //private TimeUnit MONTH_FROM_WEEKS;

    public final TimeUnit WEEK_AS_BOTTOM_UNIT;

    private TimeUnit myDefaultTimeUnit;

    private TimeUnit myMaximalTimeUnit;

    private final TimeUnitPair[] AVAILABLE_PAIRS;

    private List<TimeUnitStack.Listener> myListeners = new ArrayList<TimeUnitStack.Listener>();

    private final WeekendCalendarImpl myCalendar;

    /**
     * 
     */
    public GPTimeUnitStack(GanttLanguage i18n) {
        TimeUnit atom = ourGraph.createAtomTimeUnit("atom");
        HOUR = ourGraph.createTimeUnit("hour", atom, 1, 
                new FramerImpl(Calendar.HOUR_OF_DAY),
                new WorkingTimeCalendar() {
            public boolean isWorkingTime(Date intervalStart) {
                boolean result = 
                    DAY.isWorkingInterval(intervalStart) &&
                    !myCalendar.isOutOfBusinessHours(intervalStart);
                //System.err.println("[HOUR.isWorkingTime] start="+intervalStart+" result="+result);
                return result;
            }
        });
        HOUR.setTextFormatter(new HourTextFormatter());
        DAY = ourGraph.createTimeUnit("day", HOUR, 24,
                new FramerImpl(Calendar.DATE),
                new WorkingTimeCalendar() {
                    public boolean isWorkingTime(Date intervalStart) {
                        return !myCalendar.isWeekend(intervalStart) && !myCalendar.isPublicHoliDay(intervalStart);
                    }
        });
        DAY.setTextFormatter(new DayTextFormatter());
        MONTH = ourGraph.createTimeUnitFunctionOfDate("month", DAY,
                new FramerImpl(Calendar.MONTH));
        MONTH.setTextFormatter(new MonthTextFormatter());
        WEEK = ourGraph.createTimeUnit("week", DAY, 7,
                new WeekFramerImpl());
//        MONTH_FROM_WEEKS = ourGraph.createTimeUnitFunctionOfDate(
//                "month_from_weeks", WEEK, new FramerImpl(Calendar.MONTH));
        WEEK.setTextFormatter(new WeekTextFormatter(i18n.getText("week")
                + " {0}"));
        WEEK_AS_BOTTOM_UNIT = ourGraph.createTimeUnit("week", DAY,
                7, new WeekFramerImpl());
        WEEK_AS_BOTTOM_UNIT.setTextFormatter(new WeekTextFormatter("{0}"));
        YEAR = ourGraph.createTimeUnitFunctionOfDate("year", DAY,
                new FramerImpl(Calendar.YEAR));
        YEAR.setTextFormatter(new YearTextFormatter());
        AVAILABLE_PAIRS = new TimeUnitPair[] {
                new MyTimeUnitPair(DAY, HOUR),
                new MyTimeUnitPair(DAY, HOUR),
                new MyTimeUnitPair(DAY, HOUR),
                new MyTimeUnitPair(WEEK, DAY),
                new MyTimeUnitPair(WEEK, DAY), 
                new MyTimeUnitPair(MONTH, DAY),
                new MyTimeUnitPair(MONTH, WEEK_AS_BOTTOM_UNIT),
                new MyTimeUnitPair(MONTH, WEEK_AS_BOTTOM_UNIT),
                new MyTimeUnitPair(MONTH, WEEK_AS_BOTTOM_UNIT),
                new MyTimeUnitPair(MONTH, WEEK_AS_BOTTOM_UNIT),
                new MyTimeUnitPair(YEAR, WEEK_AS_BOTTOM_UNIT),
                new MyTimeUnitPair(YEAR, WEEK_AS_BOTTOM_UNIT), 
        };
        myDefaultTimeUnit = HOUR;
        myMaximalTimeUnit = YEAR;
        recalculatePairs();
        myCalendar = new WeekendCalendarImpl();
        myCalendar.setDefaultUnit(myDefaultTimeUnit);
    }

    public TimeFrame createTimeFrame(Date baseDate, TimeUnit topUnit,
            TimeUnit bottomUnit) {
        // if (topUnit instanceof TimeUnitFunctionOfDate) {
        // topUnit = ((TimeUnitFunctionOfDate)topUnit).createTimeUnit(baseDate);
        // }
        return new TimeFrameImpl(baseDate, topUnit, bottomUnit);
    }

    public String getName() {
        return "default";
    }

    public TimeUnit getDefaultTimeUnit() {
        return myDefaultTimeUnit;
    }

    public TimeUnitPair[] getTimeUnitPairs() {
        return myPairs;
    }

    
    public List<TimeUnit> getTimeUnits() {
        return Arrays.asList(new TimeUnit[] {HOUR, DAY, WEEK_AS_BOTTOM_UNIT, MONTH, YEAR});
    }


    private class MyTimeUnitPair extends TimeUnitPair {
        MyTimeUnitPair(TimeUnit topUnit, TimeUnit bottomUnit) {
            super(topUnit, bottomUnit, GPTimeUnitStack.this);
        }

        public boolean equals(Object obj) {
            if (obj instanceof TimeUnitPair) {
                TimeUnitPair rvalue = (TimeUnitPair) obj;
                return getTopTimeUnit()==rvalue.getTopTimeUnit() && getBottomTimeUnit()==rvalue.getBottomTimeUnit();
            }
            return false;
        }
        
    }

    public void setTimeUnitRange(TimeUnit minimalTimeUnit, TimeUnit maximalTimeUnit) {
        assert minimalTimeUnit!=null;
        myDefaultTimeUnit = minimalTimeUnit;
        myMaximalTimeUnit = maximalTimeUnit==null ? YEAR : maximalTimeUnit;
        recalculatePairs();
        fireStackChanged();
        myCalendar.setDefaultUnit(myDefaultTimeUnit);
    }

    private void recalculatePairs() {
        List<TimeUnitPair> newPairs = new ArrayList<TimeUnitPair>();
        int state=0;
        TimeUnit stopUnit = null;
        for (int i=0; i<AVAILABLE_PAIRS.length; i++) {
            if (AVAILABLE_PAIRS[i].getBottomTimeUnit()==myDefaultTimeUnit && state==0) {
                state = 1;
            }
            if (AVAILABLE_PAIRS[i].getTopTimeUnit()==myMaximalTimeUnit && state==1) {
                state = 2;
            }
            if (AVAILABLE_PAIRS[i].getTopTimeUnit()!=myMaximalTimeUnit && state==2) {
                state = 3;
                stopUnit = AVAILABLE_PAIRS[i].getTopTimeUnit(); 
            }
            if (AVAILABLE_PAIRS[i].getTopTimeUnit()!=stopUnit && state==3) {
                break;
            }
            if (state>0) {
                newPairs.add(AVAILABLE_PAIRS[i]);
            }
        }
        myPairs = newPairs.toArray(new TimeUnitPair[0]);
        fireStackChanged();
    }

    public void addTimeUnitStackListener(Listener listener) {
        myListeners.add(listener);
    }

    public void removeTimeUnitStackListener(Listener listener) {
        myListeners.remove(listener);
    }

    void fireStackChanged() {
        for (TimeUnitStack.Listener nextListener : myListeners) {
            nextListener.timeUnitStackChanged();
        }
        
    }

    public TimeUnit findTimeUnit(String code) {
        assert code!=null;
        code = code.trim();
        if (isHour(code)) {
            return HOUR;
        }
        if (isDay(code)) {
            return DAY;
        }
        if (isWeek(code)) {
            return WEEK_AS_BOTTOM_UNIT;
        }
        return null;
    }

    private boolean isWeek(String code) {
        return "w".equalsIgnoreCase(code);
    }

    private boolean isDay(String code) {
        return "d".equalsIgnoreCase(code);
    }

    private boolean isHour(String code) {
        return "h".equalsIgnoreCase(code);
    }

    public String encode(TimeUnit timeUnit) {
        if (timeUnit==HOUR) {
            return "h";
        }
        if (timeUnit==DAY) {
            return "d";
        }
        if (timeUnit==WEEK_AS_BOTTOM_UNIT) {
            return "w";
        }
        throw new IllegalArgumentException();
    }
    
    public DateFormat[] getDateFormats() {
        DateFormat[] result;
        if (HOUR.isConstructedFrom(getDefaultTimeUnit())) {
            result = new DateFormat[] {
                DateFormat.getDateInstance(DateFormat.MEDIUM),
                DateFormat.getDateInstance(DateFormat.SHORT),
            };
        }
        else {
            result = new DateFormat[] {
                DateFormat.getDateInstance(DateFormat.LONG),
                DateFormat.getDateInstance(DateFormat.MEDIUM),
                DateFormat.getDateInstance(DateFormat.SHORT),				
            };
        }
        return result;
    }

    public DateFormat getTimeFormat() {
        if (HOUR.isConstructedFrom(getDefaultTimeUnit())) {
            return DateFormat.getTimeInstance(DateFormat.SHORT);
        }
        return null;
    }
    
    public WeekendCalendarImpl getCalendar() {
        return myCalendar;
    }
}
