/*
 * Created on 18.10.2004
 */
package net.sourceforge.ganttproject.calendar;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import net.sourceforge.ganttproject.GanttCalendar;
import net.sourceforge.ganttproject.GanttProject;
import net.sourceforge.ganttproject.parser.HolidayTagHandler;
import net.sourceforge.ganttproject.task.TaskLength;
import net.sourceforge.ganttproject.time.TimeUnit;
import net.sourceforge.ganttproject.time.gregorian.FramerImpl;

/**
 * @author bard
 */
public class WeekendCalendarImpl extends GPCalendarBase implements GPCalendar {

    private final Calendar myCalendar = (Calendar) Calendar.getInstance()
            .clone();

    private final FramerImpl myFramer = new FramerImpl(Calendar.DAY_OF_WEEK);

    private DayType[] myTypes = new DayType[7];

    private int myWeekendDaysCount;

    private Set publicHolidaysArray = new LinkedHashSet();

    private final Set myStableHolidays = new LinkedHashSet();
    
    private AlwaysWorkingTimeCalendarImpl myRestlessCalendar = new AlwaysWorkingTimeCalendarImpl();

    public WeekendCalendarImpl() {
        for (int i = 0; i < myTypes.length; i++) {
            myTypes[i] = GPCalendar.DayType.WORKING;
        }
        setWeekDayType(GregorianCalendar.SATURDAY, GPCalendar.DayType.WEEKEND);
        setWeekDayType(GregorianCalendar.SUNDAY, GPCalendar.DayType.WEEKEND);
    }

    public List/* <GPCalendarActivity> */getActivities(Date startDate,
            final Date endDate) {
        if (myWeekendDaysCount == 0) {
            return myRestlessCalendar.getActivities(startDate, endDate);
        }
        List result = new ArrayList();
        Date curDayStart = myFramer.adjustLeft(startDate);
        boolean isWeekendState = isNonWorkingDay(curDayStart);
        // System.err.println("getActivities(): start="+startDate+"
        // end="+endDate);
        while (curDayStart.before(endDate)) {
            // System.err.println("curDayStart="+curDayStart);
            Date changeStateDayStart = getStateChangeDate(curDayStart,
                    !isWeekendState);
            // System.err.println("changeStateDayStart="+changeStateDayStart);
            if (changeStateDayStart.before(endDate)) {
                result.add(new CalendarActivityImpl(curDayStart,
                        changeStateDayStart, !isWeekendState));
                curDayStart = changeStateDayStart;
                isWeekendState = !isWeekendState;
                continue;
            } else {
                result.add(new CalendarActivityImpl(curDayStart, endDate,
                        !isWeekendState));
                break;
            }
        }
        return result;

    }

    public boolean isWeekend(Date curDayStart) {
        myCalendar.setTime(curDayStart);
        int dayOfWeek = myCalendar.get(Calendar.DAY_OF_WEEK);
        return myTypes[dayOfWeek - 1] == GPCalendar.DayType.WEEKEND;
    }

    private Date getStateChangeDate(Date startDate, boolean changeToWeekend) {
        Date nextDayStart = myFramer.adjustRight(startDate);
        if (!(changeToWeekend ^ isNonWorkingDay(nextDayStart))) {
            return nextDayStart;
        } else {
            return getStateChangeDate(nextDayStart, changeToWeekend);
        }
    }

    private Date getStateChangeDate(Date startDate, TimeUnit timeUnit,
            boolean changeToWeekend, boolean moveRightNotLeft) {
        Date nextUnitStart = moveRightNotLeft ? timeUnit.adjustRight(startDate)
                : timeUnit.jumpLeft(startDate);
        if (!(changeToWeekend ^ isNonWorkingDay(nextUnitStart))) {
            return nextUnitStart;
        } else {
            return getStateChangeDate(nextUnitStart, timeUnit, changeToWeekend,
                    moveRightNotLeft);
        }

    }

    protected List getActivitiesForward(Date startDate, TimeUnit timeUnit,
            long unitCount) {
        List result = new ArrayList();
        Date unitStart = timeUnit.adjustLeft(startDate);
        while (unitCount > 0) {
            boolean isWeekendState = isNonWorkingDay(unitStart);
            if (isWeekendState) {
                Date workingUnitStart = getStateChangeDate(unitStart, timeUnit,
                        false, true);
                result.add(new CalendarActivityImpl(unitStart,
                        workingUnitStart, false));
                unitStart = workingUnitStart;
                continue;
            }
            Date nextUnitStart = timeUnit.adjustRight(unitStart);
            result
                    .add(new CalendarActivityImpl(unitStart, nextUnitStart,
                            true));
            unitStart = nextUnitStart;
            unitCount--;
        }
        return result;
    }

    protected List getActivitiesBackward(Date startDate, TimeUnit timeUnit,
            long unitCount) {
        List result = new LinkedList();
        Date unitStart = timeUnit.adjustLeft(startDate);
        while (unitCount > 0) {
            Date prevUnitStart = timeUnit.jumpLeft(unitStart);
            boolean isWeekendState = isNonWorkingDay(prevUnitStart);
            if (isWeekendState) {
                Date lastWorkingUnitStart = getStateChangeDate(prevUnitStart,
                        timeUnit, false, false);
                Date firstWeekendUnitStart = timeUnit
                        .adjustRight(lastWorkingUnitStart);
                Date lastWeekendUnitEnd = unitStart;
                result.add(0, new CalendarActivityImpl(firstWeekendUnitStart,
                        lastWeekendUnitEnd, false));
                unitStart = firstWeekendUnitStart;
            } else {
                result.add(0, new CalendarActivityImpl(prevUnitStart,
                        unitStart, true));
                unitCount--;
                unitStart = prevUnitStart;
            }
        }
        return result;
    }

    public void setWeekDayType(int day, DayType type) {
        if (type != myTypes[day - 1]) {
            myWeekendDaysCount += (type == DayType.WEEKEND ? 1 : -1);
        }
        myTypes[day - 1] = type;
    }

    public DayType getWeekDayType(int day) {
        return myTypes[day - 1];
    }

    public Date findClosestWorkingTime(Date time) {
        if (myWeekendDaysCount == 0) {
            return time;
        }
        if (!isNonWorkingDay(time)) {
            return time;
        }
        return getStateChangeDate(time, false);
    }

    public void setPublicHoliDayType(int month, int date) {
        setPublicHoliDayType(new GanttCalendar(1, month - 1, date).getTime());
        myStableHolidays.add(new GanttCalendar(1, month - 1, date).getTime());
    }

    public void setPublicHoliDayType(Date curDayStart) {
        publicHolidaysArray.add(curDayStart);
    }

    public boolean isPublicHoliDay(Date curDayStart) {
        boolean result = publicHolidaysArray.contains(curDayStart);
        if (!result) {
            result = myStableHolidays.contains(new GanttCalendar(1, curDayStart.getMonth(), curDayStart.getDate()).getTime()); 
        }
        return result;
    }

    public DayType getDayTypeDate(Date curDayStart) {
        myCalendar.setTime(curDayStart);
        int dayOfYear = myCalendar.get(Calendar.DAY_OF_YEAR);
        myCalendar.setTime(curDayStart);
        int dayOfWeek = myCalendar.get(Calendar.DAY_OF_WEEK);
        if ((!isPublicHoliDay(curDayStart))
                && (getWeekDayType(dayOfWeek) == GPCalendar.DayType.WORKING))
            return GPCalendar.DayType.WORKING;
        else if (isPublicHoliDay(curDayStart))
            return GPCalendar.DayType.HOLIDAY;
        else
            return GPCalendar.DayType.WEEKEND;
    }

    public boolean isNonWorkingDay(Date curDayStart) {
        if (isWeekend(curDayStart) || isPublicHoliDay(curDayStart))
            return true;
        else
            return false;
    }

    public void setPublicHolidays(URL calendar, GanttProject gp) {
        publicHolidaysArray.clear();
        if (calendar != null) {
            XMLCalendarOpen opener = new XMLCalendarOpen();

            HolidayTagHandler dependencyHandler = new HolidayTagHandler(gp);

            opener.addTagHandler(dependencyHandler);
            opener.addParsingListener(dependencyHandler);
            try {
                opener.load(calendar.openStream());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }

    public Collection getPublicHolidays() {
        return publicHolidaysArray;
    }

    public List getActivities(Date startingFrom, TaskLength period) {
        return getActivities(startingFrom, period.getTimeUnit(), period
                .getLength());
    }

}
