/*
 * ICESOFT COMMERCIAL SOURCE CODE LICENSE V 1.1
 *
 * The contents of this file are subject to the ICEsoft Commercial Source
 * Code License Agreement V1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the
 * License at
 * http://www.icesoft.com/license/commercial-source-v1.1.html
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations under
 * the License.
 *
 * Copyright 2009-2015 ICEsoft Technologies Canada, Corp. All Rights Reserved.
 */
package com.icesoft.faces.facelets.component.schedule.month;

import com.icesoft.faces.facelets.component.names.BeanNames;
import com.icesoft.faces.facelets.component.schedule.common.CalendarEventBean;
import com.icesoft.faces.facelets.component.util.CalendarViewUtil;
import com.icesoft.faces.facelets.component.util.Util;

import java.io.Serializable;
import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * The month model is responsible for taking the CalendarEvent and assembling a
 * UI data structure to show month related views.
 * <p/>
 * A flat month array is used to represent a calendar month with padding on
 * each end for the previous and next month.  Each element in the month array
 * contains a DaySlot. The Day slot represent the current day but can contain
 * any number of CalendarEvent for the given day.
 *
 * @author ICEfaces Technologies Inc.
 */
public class MonthModel implements Serializable {

    private MonthSlot[] month;

    private Calendar currentMonth;

    private int maxSlotSize = 3;

    /**
     * Create a new instance of the MonthModel set to the  users
     * year, and date at present. Intended to construct the bean used to
     * show the small one month calendar bean.
     */
    public MonthModel() {
        this(((CalendarViewUtil) Util.getManagedBean(BeanNames.CALENDAR_VIEW_UTIL)).getLocalizedCalendar().get(Calendar.YEAR),
                ((CalendarViewUtil) Util.getManagedBean(BeanNames.CALENDAR_VIEW_UTIL)).getLocalizedCalendar().get(Calendar.MONTH));
    }

    /**
     * Creates a new instance of the MonthModel which show the specified
     * year and month.  This constructor is meant to be used by the different
     * calendar view types, mainly Month and Year views.
     *
     * @param currentYear  calendar year to build month from
     * @param currentMonth month to the build on.
     */
    public MonthModel(final int currentYear, final int currentMonth) {

        // create a new calendar reference for this month
        this.currentMonth = new GregorianCalendar(currentYear, currentMonth, 1);

        // fill in slots for current month
        Calendar minCal = new GregorianCalendar(currentYear, currentMonth, 1);
        int numberOfDays = minCal.getActualMaximum(Calendar.DAY_OF_MONTH);
        Calendar maxCal = new GregorianCalendar(currentYear, currentMonth, numberOfDays);

        // find start index
        int startXIndex = minCal.get(Calendar.DAY_OF_WEEK);
        // find end index.
        int endYIndex = maxCal.getActualMaximum(Calendar.WEEK_OF_MONTH);

        // build out know ranges for current month, we aren't going to use
        // index zero to keep the math a little simpler.
        int slotSize = (6 * 7) + 1;
        month = new MonthSlot[slotSize];
        int dateSlotCursor = 1;

        CalendarViewUtil cvu = (CalendarViewUtil) Util.getManagedBean(BeanNames.CALENDAR_VIEW_UTIL);
        GregorianCalendar rightNowCalendar = new GregorianCalendar(cvu.getScheduleTimeZone());

        for (int i = startXIndex, max = startXIndex + numberOfDays; i < max; i++) {
            month[i] = new MonthSlot(currentYear, currentMonth,
                    dateSlotCursor++, this.currentMonth.get(Calendar.MONTH) == rightNowCalendar.get(Calendar.MONTH));
        }

        // fill in slots for previous month
        int previousMonth = currentMonth - 1;
        Calendar prevCal = new GregorianCalendar(currentYear, previousMonth, 1);
        dateSlotCursor = prevCal.getActualMaximum(Calendar.DAY_OF_MONTH);
        for (int i = startXIndex - 1; i > 0; i--) {
            month[i] = new MonthSlot(currentYear, previousMonth, dateSlotCursor--);
        }

        // fill in slots for next month
        dateSlotCursor = 1;
        for (int i = startXIndex + numberOfDays, max = month.length; i < max; i++) {
            month[i] = new MonthSlot(currentYear, previousMonth, dateSlotCursor++);
        }

        // add spacers to fill out calendar boxes.
        topUpMonthSpacerSlots(maxSlotSize);
    }

    /**
     * Utility to add a default number of empty slots to the events.  We
     * want to keep the slot count the same so the UI layout is balanced.
     *
     * @param numberOfSlots total number of slots to have in each days event
     *                      slots.
     */
    private void topUpMonthSpacerSlots(int numberOfSlots) {
        for (MonthSlot slot : month) {
            // first slot is always empty
            if (slot != null) {
                if (slot.getCalEvents().size() < numberOfSlots) {
                    for (int i = 0, max = numberOfSlots - slot.getCalEvents().size();
                         i < max; i++) {
                        slot.getCalEvents().add(new CalendarEventBean());
                    }
                }
            }
        }
    }

    /**
     * Utility to update the total number of slot spacers if a new
     * event has been added to the month and more specifically a day slot.
     *
     * @param monthSlot slot to check size of.
     */
    private void topUpMonthSpacerSlots(MonthSlot monthSlot) {
        int currentSlotSize = monthSlot.getCalEvents().size();
        // if we are bigger then the max then we have to do the update.
        if (currentSlotSize > maxSlotSize) {
            maxSlotSize = currentSlotSize;
            topUpMonthSpacerSlots(maxSlotSize);
        }
    }

    /**
     * Adds a new Event to the Month Model.  This method does a lot of the
     * lifting for the class.  Because an event can span more then one day this
     * method is responsible for putting events into each of the days that it
     * covers.  The makes UI construction a lot easier.
     *
     * @param calEvent calEvent to add to the month.
     */
    public void addEvent(CalendarEventBean calEvent, Calendar dateCursor) {
        //first check to see if the event is withing the currentYear.
        int year = dateCursor.get(Calendar.YEAR);
        Calendar startDate = calEvent.getStart();
        Calendar endDate = calEvent.getEnd();
        /* iPCK-537 */
        int yearStart = startDate.get(Calendar.YEAR);
        int yearEnd = startDate.get(Calendar.YEAR);
        if (year < yearStart || year > yearEnd){
  //   System.out.println(" this event startDate="+yearStart+" Yearend ="+yearEnd+" is NOT>>>within dateCursor year="+year);
            return;
        }
        // check if current months matchup before adding
        int monthIndex = currentMonth.get(Calendar.MONTH);

//   System.out.println(" monthModel adding event for start date= "+startDate+" end date="+endDate+" is within year="+year);
        if (monthIndex != startDate.get(Calendar.MONTH) &&
                monthIndex != endDate.get(Calendar.MONTH)) {
            return;
        }

        // find out if event spans more then a day.
        int startDayOfMonth = startDate.get(Calendar.DAY_OF_MONTH);
        int endDayOfMonth = endDate.get(Calendar.DAY_OF_MONTH);

        int startMonth = startDate.get(Calendar.MONTH);
        int endMonth = endDate.get(Calendar.MONTH);

        // start index of first day in month.
        Calendar minCal = new GregorianCalendar(
                currentMonth.get(Calendar.YEAR), currentMonth.get(Calendar.MONTH), 1);
        int startXIndex = minCal.get(Calendar.DAY_OF_WEEK);

        // if startDayOfMonth and endDay are the same then we just add the event
        if (startDayOfMonth == endDayOfMonth &&
                startMonth == endMonth) {
            // find index and insert event into correct slot.
            int slotIndex = startXIndex + startDate.get(Calendar.DAY_OF_MONTH) - 1;
            month[slotIndex].addEvent(new CalendarEventBean(calEvent),
                    month[slotIndex].getFirstFreeSlot());
            // update spacer slots
            topUpMonthSpacerSlots(month[slotIndex]);

        }// otherwise we have to insert the event into more than one slot.
        else {
            // correct of  month start and end, event could span a month,
            // but we're only interested in the current.
            int currenMonth = currentMonth.get(Calendar.MONTH);

            // calendar days of multi-day event that fall into this month
            int startDay;
            int endDay;
            // start is in previous month, then we go with first day
            if (startMonth < currenMonth) {
                startDay = 1;
            } else {
                startDay = startDate.get(Calendar.DAY_OF_MONTH);
            }
            // end is next month, then we go with last day in this month
            if (endMonth > currenMonth) {
                endDay = currentMonth.getActualMaximum(Calendar.DAY_OF_MONTH);
            } else {
                endDay = endDate.get(Calendar.DAY_OF_MONTH);
            }

            int startSlotIndex = startXIndex + startDay - 1;
            int endSlotIndex = startXIndex + endDay - 1;

            // finally iterate over the range of dates that we need to add
            // calEvents too.
            int slot = month[startSlotIndex].getFirstFreeSlot();
            for (int i = startSlotIndex; i <= endSlotIndex; i++) {
                month[i].addEvent(new CalendarEventBean(calEvent),
                        slot);
                // update spacer slots
                topUpMonthSpacerSlots(month[i]);
            }
        }

    }

    /**
     * Gets array of months slots which contains key UI flags and calendar
     * event information for the given day.
     *
     * @return slots that make up the month,
     */
    public MonthSlot[] getSlot() {
        return month;
    }

    /**
     * Number of slots in in the month.
     *
     * @return size of months array
     */
    public int getSize() {
        return month.length;
    }

    public Calendar getCurrentMonth() {
        return currentMonth;
    }

}