/*
 * IntervalManager.java
 *
 * Created on 26. Februar 2007, 11:58
 *
 */

package net.sourceforge.ganttproject.calendar;

import java.awt.Color;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * This class manages the handling of {@see Interval}-Objects. It holds all
 * intervals defined in projects xml file.
 *
 * @author shofmann <sebastian.hofmann@sourcepark.de>
 */
public class IntervalManager {
    
    /** The format of date-values specified in projects xml file */
    public final static String DATE_PATTERN = "yyyy-MM-dd";
    
    /** Formatter for date values */
    public final static SimpleDateFormat SIMPLE_DATE_FORMAT =
            new SimpleDateFormat(DATE_PATTERN);
    
    /** List of all known intervals of the project */
    private List<Interval> intervals;
    
    /** Set holding the locked days of all intervals */
    private SortedSet<Date> lockedDays;
    
    /** Set holding all IntervalTypes */
    private SortedSet<IntervalType> intervalTypes;
    
    /**
     * This map contains all days belonging to an interval as keys and their
     * interval as values
     */
    private SortedMap<Date, Interval> intervalsByDate;
    
    /** Creates a new instance of IntervalManager */
    public IntervalManager() {
        this.intervals = new ArrayList();
        this.lockedDays = new TreeSet();
        this.intervalsByDate = new TreeMap();
        this.intervalTypes = new TreeSet();
    }
    
    // ---------------------- Public Methods -----------------------------------
    
    /**
     * Returns a read-only view of the list of all intervals for the project.
     * @return list of all intervals for the project
     */
    public List<Interval> getUmodifiableIntervals(){
        return Collections.unmodifiableList(this.intervals);
    }
    
    /**
     * Adds a new Interval
     * @param interval the Interval to add
     */
    public void addInterval(Interval interval){
        this.intervals.add(interval);
        appendLockedDaysOfInterval(interval);
        appendIntervalsDays(interval);
        System.out.println("Added interval: "+interval.getStartDate()+" - "+interval.getEndDate());
    }
    
    /**
     * Removes all intervals
     */
    public void clearIntervals(){
        for (Interval interval : this.intervals) {
            interval.getType().getIntervals().remove(interval);
            interval.setType(null);
        }
        this.intervals.clear();
        this.lockedDays.clear();
        this.intervalsByDate.clear();
    }
    
    /**
     * Resets the Manager. Removes all IntervalTypes
     * and Intervals.
     */
    public void reset(){
        clearIntervals();
        this.intervalTypes.clear();
    }
    
    /**
     * Returns a read-only view of all IntervalTypes
     * @return Set containing all IntervalTypes
     */
    public Set<IntervalType> getUnmodifiableIntervalTypes(){
        return Collections.unmodifiableSortedSet(this.intervalTypes);
    }
    
    /**
     * Adds a new IntervalType
     * @param type the new IntervalType to add
     */
    public void addIntervalType(IntervalType type){
        this.intervalTypes.add(type);
    }
    
    /**
     * Returns an IntervalType identifed by its description.
     * If no such IntervalType exists returns null.
     * @param description The description of the IntervalType
     * @return IntervalType for description
     */
    public IntervalType getIntervalTypeByDescription(String description){
        IntervalType retVal = null;
        for (IntervalType type : this.intervalTypes) {
            if(type.getDescription().equals(description)){
                retVal = type;
                break;
            }
        }
        return retVal;
    }
    
    /**
     * Removes an IntervalType. IntervalTypes may be only removed if
     * no interval is assigned to it.
     * @param type The IntervalType to remove
     * @throws Exception in case an Intervall ist still assigned to the type
     */
    public void deleteIntervalType(IntervalType type) throws Exception{
        if(type.getIntervals().size() != 0){
            throw new Exception("Type may not be deleted as long as it is assigned to an interval");
        }
        this.intervalTypes.remove(type);
    }
    
    /**
     * Indicates wether a day is locked by an interval or not
     * @param date the day to check
     * @return true if the day is locked otherwise false
     */
    public boolean isDayLocked(Date date){
        return this.lockedDays.contains(date);
    }
    
    /**
     * Indicates wether the project has intervalls defined
     * @return true if intervals were defined and false otherwise
     */
    public boolean hasIntervals(){
        return this.intervals.size() > 0;
    }
    
    /**
     * Returns the color for a day in an interval. The color was specified
     * for the interval in the XML-Project-File. If the day does not belong to
     * an interval the method returns null.
     * @param day the day to get the color for
     * @return color of the interval the day belongs
     */
    public Color getColorForIntervalsDay(Date day){
        Calendar c = Calendar.getInstance();
        c.setTime(day);
        c.set(Calendar.HOUR, 0);
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        Color color = null;
        Interval interval = this.intervalsByDate.get(c.getTime());
        if(interval != null){
            color = interval.getType().getColor();
        }
        return color;
    }
    
    /**
     * Indicates wether a day belongs to an interval or not.
     * @param day the day to check
     * @return true if the day belongs to an interval and false otherwise
     */
    public boolean belongsToInterval(Date day){
        Calendar c = Calendar.getInstance();
        c.setTime(day);
        c.set(Calendar.HOUR, 0);
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        return this.intervalsByDate.containsKey(c.getTime());
    }
    
    // ------------------------ Private Methods --------------------------------
    
    /**
     * Appends the days locked by an interval to the set of locked days
     * @param interval the interval
     */
    private void appendLockedDaysOfInterval(Interval interval){
        if(interval.getType().isLocked()){
            Calendar start = Calendar.getInstance();
            start.setTime(interval.getStartDate());
            Calendar end = Calendar.getInstance();
            end.setTime(interval.getEndDate());
            while(start.compareTo(end) <= 0){
                this.lockedDays.add(start.getTime());
                start.add(Calendar.DAY_OF_YEAR, 1);
            }
        }
    }
    
    /**
     * Appends all days belonging to the interval to the map of days
     * @param interval the interval to add
     */
    private void appendIntervalsDays(Interval interval){
        Calendar start = Calendar.getInstance();
        start.setTime(interval.getStartDate());
        Calendar end = Calendar.getInstance();
        end.setTime(interval.getEndDate());
        while(start.compareTo(end) <= 0){
            this.intervalsByDate.put(start.getTime(), interval);
            start.add(Calendar.DAY_OF_YEAR, 1);
        }
    }
    
}
