import ngModule from '../../module';
import FormInput from '../form-input';
import './style.scss';

/**
 * Flat calendar.
 *
 * @property {Array} defaultDate Array of events
 * @property {Function} hasEvents Determines if an events exists for a day
 * @property {Function} getClass Class to apply to specific day
 * @event selection Emitted when day or weekday is selected
 * @event move Emitted when month changes
 */
export default class Calendar extends FormInput {
    constructor($element, $scope, vnWeekDays, moment) {
        super($element, $scope);
        this.weekDays = vnWeekDays.locales;
        this.displayControls = true;
        this.moment = moment;
        this.defaultDate = Date.vnNew();
    }

    /**
     * The initial date
     *
     * @return {Date} - Default date
     */
    get defaultDate() {
        return this._defaultDate;
    }

    set defaultDate(value) {
        if (value) {
            value = new Date(value);
            value.setHours(0, 0, 0, 0);
            value.setDate(1);
        }

        this._defaultDate = value;
        this.month = value.getMonth();
        this.repaint();
    }

    /**
     * Returns first day of month from a given date
     *
     * @param {Date} date - Origin date
     * @return {Integer}
     */
    firstDay(date) {
        return new Date(
            date.getFullYear(),
            date.getMonth(),
            1
        );
    }

    lastDay() {
        return new Date(
            this.defaultDate.getFullYear(),
            this.defaultDate.getMonth() + 1,
            0
        ).getDate();
    }

    /**
     * Repaints the calendar.
     */
    repaint() {
        const firstWeekday = this.firstDay(this.defaultDate).getDay() - 1;
        this.weekdayOffset = firstWeekday >= 0 ? firstWeekday : 6;

        let dayIndex = new Date(this.defaultDate.getTime());
        dayIndex.setDate(1 - this.weekdayOffset);

        this.days = [];

        for (let i = 1; i <= 42; i++) {
            this.days.push(new Date(dayIndex.getTime()));
            dayIndex.setDate(dayIndex.getDate() + 1);
        }

        this.getWeekdays();
    }

    getWeekdays() {
        if (!this.moment) return;

        const totalSlots = this.lastDay() + this.weekdayOffset;
        const weeks = Math.ceil(totalSlots / 7);

        const dated = this.moment(this.defaultDate);
        const firstWeekNumber = dated.set('date', 1).isoWeek();

        const weekNumbers = [];
        for (let w = 0; w < weeks; w++) {
            let weekNumber = firstWeekNumber;
            if (dated.get('month') == 0 && firstWeekNumber > 1 && w > 0)
                weekNumber = 0;

            weekNumbers.push(weekNumber + w);
        }

        this.weekNumbers = weekNumbers;
    }

    /**
     * Gets CSS classes to apply to the specified day.
     *
     * @param {Date} date The date
     * @return {Object} The CSS classes to apply
     */
    getDayClasses(date) {
        let day = date.getDate();
        let wday = date.getDay();
        let month = date.getMonth();
        let year = date.getFullYear();

        const currentDay = Date.vnNew().getDate();
        const currentMonth = Date.vnNew().getMonth();
        const currentYear = Date.vnNew().getFullYear();

        let classes = {
            today: day === currentDay && month === currentMonth && year === currentYear,
            weekend: wday === 6 || wday === 0,
            previous: month < this.month,
            current: month == this.month,
            next: month > this.month,
            event: this.hasEvents({$day: date})
        };

        let userClass = this.getClass({$day: date});
        if (userClass) classes[userClass] = true;

        return classes;
    }

    /**
     * Moves to next month(s)
     */
    moveNext() {
        this.move(1);
    }

    /**
     * Moves to previous month(s)
     */
    movePrevious() {
        this.move(-1);
    }

    /**
     * Moves @direction months backwards/forwards.
     *
     * @param {Number} direction Negative to move backwards, positive forwards
     */
    move(direction) {
        let date = new Date(this.defaultDate.getTime());
        date.setMonth(date.getMonth() + direction);
        this.defaultDate = date;

        this.repaint();
        this.emit('move', {$date: date});
    }

    /*
     * Day selection event
     */
    select($event, day) {
        if (!this.editable) return;
        this.change(day);
        this.emit('selection', {
            $event: $event,
            $days: [day],
            $type: 'day'
        });

        // Repaint only if 'selection' event is not listening
        if (!this.$events || this.$events && !this.$events['selection'])
            this.repaint();
    }

    /*
     * WeekDay selection event
     */
    selectWeekDay($event, weekday) {
        if (!this.editable) return;
        let days = [];
        for (let day of this.days) {
            if (day.getDay() === weekday && day.getMonth() == this.month)
                days.push(day);
        }
        this.field = days[0];
        this.emit('selection', {
            $event: $event,
            $days: days,
            $type: 'weekday',
            $weekday: weekday
        });
        this.repaint();
    }

    hasEvents() {
        return false;
    }

    getClass() {
        return '';
    }

    repeatLast() {
        if (this.formatDay) {
            const days = this.element.querySelectorAll('.days > .day');
            for (let i = 0; i < days.length; i++) {
                this.formatDay({
                    $day: this.days[i],
                    $element: days[i]
                });
            }
        }

        if (this.formatWeek) {
            const weeks = this.element.querySelectorAll('.weeks > .day');
            for (const week of weeks) {
                this.formatWeek({
                    $element: week
                });
            }
        }
    }
}
Calendar.$inject = ['$element', '$scope', 'vnWeekDays', 'moment'];

ngModule.vnComponent('vnCalendar', {
    template: require('./index.html'),
    controller: Calendar,
    bindings: {
        defaultDate: '=?',
        hasEvents: '&?',
        getClass: '&?',
        formatDay: '&?',
        formatWeek: '&?',
        displayControls: '<?',
        hideYear: '<?',
        hideContiguous: '<?',
        hideWeeks: '<?'
    }
});