package de.fitsample.timerecording;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * A TimeRecording acts as a container for {@link TimeRecord TimeRecord}s and
 * provides business calculations on the records. A
 * {@link PropertyChangeEvent PropertyChangeEvent}is fired if a record is
 * added.
 * 
 * @author Ralf Stuckert
 */
public class TimeRecording {

    /**
     * The name of the property event that is fired if a TimeFrame is added.
     */
    public static final String RECORDS_PROPERTY = "records";

    /**
     * The List of {@link TimeRecord TimeRecord}s.
     */
    private List recordList = new ArrayList();

    /**
     * @see #addPropertyChangeListener(PropertyChangeListener)
     * @see #removePropertyChangeListener(PropertyChangeListener)
     */
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
            this);

    /**
     * Adds a TimeRecord.
     * 
     * @param timeRecord the TimeRecord to add. If timeRecord == null an
     *            IllegalArgumentException is thrown.
     * 
     * @throws IllegalArgumentException
     */
    public void addRecord(TimeRecord timeRecord) {
        if (timeRecord == null) {
            throw new IllegalArgumentException("timeRecord must not be null");
        }
        recordList.add(timeRecord);
        propertyChangeSupport.firePropertyChange(RECORDS_PROPERTY, recordList
                .size() - 1, recordList.size());
    }

    /**
     * @return the amount of record entries.
     */
    public int size() {
        return recordList.size();
    }

    /**
     * Returns the TimeRecord with the given index.
     * 
     * @param index the index of the TimeRecord to return.
     * 
     * @return the TimeRecord.
     */
    public TimeRecord getRecord(int index) {
        return (TimeRecord) recordList.get(index);
    }

    /**
     * @return all TimeRecords contained in this TimeRecording.
     */
    public List getAllRecords() {
        return new ArrayList(recordList);
    }

    /**
     * Returns a List of TimeRecords associated with the given day.
     * 
     * @param day the day for which to return the TimeRecords.
     * 
     * @return the List of TimeRecords.
     */
    private List getRecordsForDay(int day) {
        List result = new ArrayList();
        for (Iterator iter = recordList.iterator(); iter.hasNext();) {
            TimeRecord record = (TimeRecord) iter.next();
            if (record.getDay() == day) {
                result.add(record);
            }
        }
        return result;
    }

    /**
     * Returns the recorded time for the given day.
     * 
     * @param day the day for which to return the time. If day < 0 an
     *            IllegalArgumentException is thrown.
     * 
     * @return the recorded time for the given day.
     * 
     * @throws IllegalArgumentException
     */
    public double getRecordedTimeForDay(int day) {
        List dailyRecords = getRecordsForDay(day);
        if (dailyRecords == null) {
            return 0;
        }
        double sum = 0;
        for (Iterator iter = dailyRecords.iterator(); iter.hasNext();) {
            TimeRecord timeRecord = (TimeRecord) iter.next();
            sum += timeRecord.getTimeFrame().getDuration();
        }
        return round(sum);
    }

    /**
     * Returns the recorded break for the given day.
     * 
     * @param day the day for which to return the break. If day < 0 an
     *            IllegalArgumentException is thrown.
     * 
     * @return the recorded break for the given day.
     * 
     * @throws IllegalArgumentException
     */
    public double getRecordedBreakForDay(int day) {
        List dailyRecords = getRecordsForDay(day);
        if (dailyRecords == null) {
            return 0;
        }
        if (dailyRecords.size() < 2) {
            return 0;
        }
        double sum = 0;
        for (int i = 0; i < dailyRecords.size() - 1; i++) {
            TimeFrame breakFrame = new TimeFrame(((TimeRecord) dailyRecords
                    .get(0)).getTimeFrame().getEndTime(),
                    ((TimeRecord) dailyRecords.get(1)).getTimeFrame()
                            .getStartTime());
            sum += breakFrame.getDuration();
        }
        return round(sum);
    }

    /**
     * Returns the calculated working time for the given day.
     * 
     * @param day the day for which to return the net time. If day < 0 an
     *            IllegalArgumentException is thrown.
     * 
     * @return the calculated working time for the given day.
     * 
     * @throws IllegalArgumentException
     */
    public double getWorkingTimeForDay(int day) {
        double time = getRecordedTimeForDay(day);
        double mandatoryBreak = getMandatoryBreak(time);
        double dailyBreak = getRecordedBreakForDay(day);
        if (dailyBreak < mandatoryBreak) {
            time -= (mandatoryBreak - dailyBreak);
        }
//        if (time > getMaxWorkingTimePerDay()) {
//            return getMaxWorkingTimePerDay();
//        }
        return round(time);
    }

    /**
     * Returns the mandatory break for the given time.
     * 
     * @param time the time for which to calculate the mandatory break.
     * 
     * @return the mandatory break.
     */
    public double getMandatoryBreak(double time) {
        if (time > 9) {
            if (time < 9.75) {
                return round(time - 9);
            }
            return round(0.75);
        }
        if (time > 6) {
            if (time < 6.5) {
                return round(time - 6);
            }
            return round(0.5);
        }
        return 0;
    }

    /**
     * Return the calculated working time accumulated to the given day.
     * 
     * @param day the day until to accumulate the calculated time.
     * 
     * @return the calculated working time accumulated to the given day.
     */
    public double getWorkingTimeUpToDay(int day) {
        double sum = 0;
        for (int i = 0; i <= day; i++) {
            sum += getWorkingTimeForDay(i);
        }
        return round(sum);
    }

    /**
     * @return the max working time allowed.
     */
    public double getMaxWorkingTimePerDay() {
        return 10;
    }

    /**
     * Rounds the given <code>value</code> to one fraction.
     * 
     * @param value the value to round.
     * @return the rounded value.
     */
    private double round(double value) {
        return ((long) ((value * 100) + 0.5)) / 100d;
    }

    /**
     * Adds a PropertyChangeListener.
     * 
     * @param listener
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    /**
     * Removes a PropertyChangeListener.
     * 
     * @param listener
     */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(listener);
    }

}
