package com.github.tibolte.agendacalendarview;

import com.github.tibolte.agendacalendarview.models.CalendarEvent;
import com.github.tibolte.agendacalendarview.models.IDayItem;
import com.github.tibolte.agendacalendarview.models.IWeekItem;
import com.github.tibolte.agendacalendarview.utils.DateHelper;

import android.content.Context;
import android.util.Log;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * This class manages information about the calendar. (Events, weather info...)
 * Holds reference to the days list of the calendar.
 * As the app is using several views, we want to keep everything in one place.
 */
public class CalendarManager {

    private static final String LOG_TAG = CalendarManager.class.getSimpleName();

    private static CalendarManager mInstance;

    private Context mContext;
    private Locale mLocale;
    private Calendar mToday = Calendar.getInstance();
    private SimpleDateFormat mWeekdayFormatter;
    private SimpleDateFormat mMonthHalfNameFormat;

    /// instances of classes provided from outside
    private IDayItem mCleanDay;
    private IWeekItem mCleanWeek;

    /**
     * List of days used by the calendar
     */
    private List<IDayItem> mDays = new ArrayList<>();
    /**
     * List of weeks used by the calendar
     */
    private List<IWeekItem> mWeeks = new ArrayList<>();
    /**
     * List of events instances
     */
    private List<CalendarEvent> mEvents = new ArrayList<>();

    // region Constructors

    public CalendarManager(Context context) {
        this.mContext = context;
    }

    public static CalendarManager getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new CalendarManager(context);
        }
        return mInstance;
    }

    public static CalendarManager getInstance() {
        return mInstance;
    }

    // endregion

    // region Getters/Setters

    public Locale getLocale() {
        return mLocale;
    }

    public Context getContext() {
        return mContext;
    }

    public Calendar getToday() {
        return mToday;
    }

    public void setToday(Calendar today) {
        this.mToday = today;
    }

    public List<IWeekItem> getWeeks() {
        return mWeeks;
    }

    public List<CalendarEvent> getEvents() {
        return mEvents;
    }

    public List<IDayItem> getDays() {
        return mDays;
    }

    public SimpleDateFormat getWeekdayFormatter() {
        return mWeekdayFormatter;
    }

    public SimpleDateFormat getMonthHalfNameFormat() {
        return mMonthHalfNameFormat;
    }

    // endregion

    // region Public methods

    public void buildCal(Calendar minDate, Calendar maxDate, Locale locale, IDayItem cleanDay, IWeekItem cleanWeek) {
        if (minDate == null || maxDate == null) {
            throw new IllegalArgumentException(
                    "minDate and maxDate must be non-null.");
        }
        if (minDate.after(maxDate)) {
            throw new IllegalArgumentException(
                    "minDate must be before maxDate.");
        }
        if (locale == null) {
            throw new IllegalArgumentException("Locale is null.");
        }

        setLocale(locale);

        mDays.clear();
        mWeeks.clear();
        mEvents.clear();

        mCleanDay = cleanDay;
        mCleanWeek = cleanWeek;

        Calendar mMinCal = Calendar.getInstance(mLocale);
        Calendar mMaxCal = Calendar.getInstance(mLocale);
        Calendar mWeekCounter = Calendar.getInstance(mLocale);

        mMinCal.setTime(minDate.getTime());
        mMaxCal.setTime(maxDate.getTime());

        // maxDate is exclusive, here we bump back to the previous day, as maxDate if December 1st, 2020,
        // we don't include that month in our list
        mMaxCal.add(Calendar.MINUTE, -1);

        // Now iterate we iterate between mMinCal and mMaxCal so we build our list of weeks
        mWeekCounter.setTime(mMinCal.getTime());
        int maxMonth = mMaxCal.get(Calendar.MONTH);
        int maxYear = mMaxCal.get(Calendar.YEAR);

        int currentMonth = mWeekCounter.get(Calendar.MONTH);
        int currentYear = mWeekCounter.get(Calendar.YEAR);

        // Loop through the weeks
        while ((currentMonth <= maxMonth // Up to, including the month.
                || currentYear < maxYear) // Up to the year.
                && currentYear < maxYear + 1) { // But not > next yr.

            Date date = mWeekCounter.getTime();
            // Build our week list
            int currentWeekOfYear = mWeekCounter.get(Calendar.WEEK_OF_YEAR);

            IWeekItem weekItem = cleanWeek.copy();
            weekItem.setWeekInYear(currentWeekOfYear);
            weekItem.setYear(currentYear);
            weekItem.setDate(date);
            weekItem.setMonth(currentMonth);
            weekItem.setLabel(mMonthHalfNameFormat.format(date));
            List<IDayItem> dayItems = getDayCells(mWeekCounter); // gather days for the built week
            weekItem.setDayItems(dayItems);
            mWeeks.add(weekItem);

            //Log.d(LOG_TAG, String.format("Adding week: %s", weekItem));

            mWeekCounter.add(Calendar.WEEK_OF_YEAR, 1);

            currentMonth = mWeekCounter.get(Calendar.MONTH);
            currentYear = mWeekCounter.get(Calendar.YEAR);
        }
    }

    public void loadEvents(List<CalendarEvent> eventList, CalendarEvent noEvent) {

        for (IWeekItem weekItem : getWeeks()) {
            for (IDayItem dayItem : weekItem.getDayItems()) {
                boolean isEventForDay = false;
                boolean isShowBadgeForDay = false;
                for (CalendarEvent event : eventList) {
                    if (DateHelper.isBetweenInclusive(dayItem.getDate(), event.getStartTime(), event.getEndTime())) {
                        CalendarEvent copy = event.copy();

                        if (copy.getShowBadge()) {
                            isShowBadgeForDay = true;
                        }

                        Calendar dayInstance = Calendar.getInstance();
                        dayInstance.setTime(dayItem.getDate());
                        copy.setInstanceDay(dayInstance);
                        copy.setDayReference(dayItem);
                        copy.setWeekReference(weekItem);
                        // add instances in chronological order
                        getEvents().add(copy);
                        isEventForDay = true;
                    }
                }
                if (!isEventForDay) {
                    Calendar dayInstance = Calendar.getInstance();
                    dayInstance.setTime(dayItem.getDate());
                    CalendarEvent copy = noEvent.copy();

                    copy.setInstanceDay(dayInstance);
                    copy.setDayReference(dayItem);
                    copy.setWeekReference(weekItem);
                    copy.setLocation("");
                    copy.setTitle(getContext().getResources().getString(R.string.agenda_event_no_events));
                    copy.setPlaceholder(true);
                    getEvents().add(copy);
                }
                dayItem.setShowBadge(isShowBadgeForDay);
            }
        }
    }

    public void loadCal (Locale locale, List<IWeekItem> lWeeks, List<IDayItem> lDays, List<CalendarEvent> lEvents) {
        mWeeks = lWeeks;
        mDays = lDays;
        mEvents = lEvents;
        setLocale(locale);
    }

    // endregion

    // region Private methods

    private List<IDayItem> getDayCells(Calendar startCal) {
        Calendar cal = Calendar.getInstance(mLocale);
        cal.setTime(startCal.getTime());
        List<IDayItem> dayItems = new ArrayList<>();

        int firstDayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
        int offset = cal.getFirstDayOfWeek() - firstDayOfWeek;
        if (offset > 0) {
            offset -= 7;
        }
        cal.add(Calendar.DATE, offset);

        //Log.d(LOG_TAG, String.format("Buiding row week starting at %s", cal.getTime()));
        for (int c = 0; c < 7; c++) {
            IDayItem dayItem = mCleanDay.copy();
            dayItem.buildDayItemFromCal(cal);
            dayItems.add(dayItem);
            cal.add(Calendar.DATE, 1);
        }

        mDays.addAll(dayItems);
        return dayItems;
    }

    private void setLocale(Locale locale) {
        this.mLocale = locale;
        setToday(Calendar.getInstance(mLocale));
        mWeekdayFormatter = new SimpleDateFormat(getContext().getString(R.string.day_name_format), mLocale);
        mMonthHalfNameFormat = new SimpleDateFormat(getContext().getString(R.string.month_half_name_format), locale);
    }

    // endregion
}
