import React from 'react';
import Day from '../day';
import { date as formatDate } from '../../../etc/format';
import { datesEqual } from '../../../etc/date-helpers';
import './month.less';


/**\

    title: Month
    category: Atom
    description: creates a calendar month
    props:
        year[Number]: the full year (i.e. 2016)
        month[Number]: the month (i.e. 6)
        rangeStart[Date]: the start date of the selected period. optional.
        rangeEnd[Date]: the end date of the selected period. optional.
        hoveredDate[Date]: the currently hovered over date - styling
        styleInRange[Boolean]:
            whether or not to style the dates in range between rangeStart
            and hoveredDate. default true
        startsOn[Number]: the day that the week starts on (0 = Sunday)
        onHoverOverDate[Function]:
            a callback when a day is mouse entered - called with the Date
        onSelectDate[Function]:
            a callback for when a day is selected - called with the Date

\**/
class Month extends React.Component {
    static defaultProps = {
        year: null,
        month: null,
        rangeStart: null,
        rangeEnd: null,
        hoveredDate: null,
        styleInRange: true,
        startsOn: 0 // 0 = sunday
    };

    static propTypes = {
        year: React.PropTypes.number.isRequired,
        month: React.PropTypes.number.isRequired,
        rangeStart: React.PropTypes.instanceOf(Date),
        rangeEnd: React.PropTypes.instanceOf(Date),
        hoveredDate: React.PropTypes.instanceOf(Date),
        startsOn: React.PropTypes.oneOf([ 0, 1, 2, 3, 4, 5, 6 ]),
        onHoverOverDate: React.PropTypes.func,
        onSelectDate: React.PropTypes.func
    };

    onHoverOverDate(date) {
        /* istanbul ignore else: checked by proptypes */
        if (typeof this.props.onHoverOverDate === 'function') {
            this.props.onHoverOverDate(date);
        }
    }

    selectDate(date) {
        /* istanbul ignore else: checked by proptypes */
        if (typeof this.props.onSelectDate === 'function') {
            this.props.onSelectDate(date);
        }
    }

    constructor(props) {
        const { year, month } = props;

        super(props);

        /* istanbul ignore else: checked by proptypes */
        if (year && month) {
            this.firstOfMonth = new Date(year, month - 1, 1);
            this.lastOfMonth = new Date(year, month, 0);
        }
    }

    componentWillReceiveProps(props) {
        const { year, month } = props;

        /* istanbul ignore next: ignore internals */
        if (year && month) {
            this.firstOfMonth = new Date(year, month - 1, 1);
            this.lastOfMonth = new Date(year, month, 0);
        }
    }

    generateMatrix() {
        /*\
            The grid is a m x 7 matrix representing a calendar month, where
            each row is a week. The week starts on the day corresponding to
            the startsOn prop. Each cell has a Date object for each day of the
            month, and "empty" days (in prev/next month) are null.
        \*/
        const { year, month, startsOn } = this.props,
            offset = (7 - startsOn + this.firstOfMonth.getDay()) % 7;

        return Array(6).fill(null) // empty array of len 6
            .map((week, row) => {
                const _week = week || Array(7).fill(null);

                return _week.map((day, col) => {
                    const dayOfMonth = row * 7 + col + 1 - offset;

                    if (dayOfMonth <= 0 ||
                        dayOfMonth > this.lastOfMonth.getDate()) {
                        return null;
                    } else {
                        return new Date(year, month - 1, dayOfMonth);
                    }
                });
            })
            // exclude week if it's all null
            .filter((week) => week.some((day) => !!day));
    }

    renderDay(date, col) {
        const classDictionary = {},
            { rangeStart, rangeEnd, hoveredDate, styleInRange } = this.props,

            // a bunch of descriptive booleans for styling logic
            isHovered = date && datesEqual(date, hoveredDate),
            isBeforeHovered = date && hoveredDate && date < hoveredDate,
            isAfterHovered = date && hoveredDate && date > hoveredDate,
            isOnOrBeforeHovered = isHovered || isBeforeHovered,
            isOnOrAfterHovered = isHovered || isAfterHovered,

            isStart = date && datesEqual(date, rangeStart),
            isEnd = date && datesEqual(date, rangeEnd),
            isBeforeStart = date && rangeStart && date < rangeStart,
            isAfterStart = date && rangeStart && date > rangeStart,
            isBeforeEnd = date && rangeEnd && date < rangeEnd,
            isOnOrAfterStart = isStart || isAfterStart,
            isOnOrBeforeStart = isStart || isBeforeStart,
            isOnOrBeforeEnd = isEnd || isBeforeEnd,

            isFirstOfMonth = datesEqual(date, this.firstOfMonth),
            isLastOfMonth = datesEqual(date, this.lastOfMonth);

        let inRange = isOnOrAfterStart && isOnOrBeforeEnd,
            className;

        // here we handle the styling of the day based on whether it's selected
        // in range (hovered), at the left/right end of a range, etc.
        if (date) {
            classDictionary['left-sq'] = col === 0 || isFirstOfMonth;
            classDictionary['right-sq'] = col === 6 || isLastOfMonth;

            if (rangeStart && !rangeEnd && hoveredDate) {
                inRange = hoveredDate > rangeStart ?
                    isOnOrAfterStart && isOnOrBeforeHovered :
                    isOnOrBeforeStart && isOnOrAfterHovered;
            }

            classDictionary['left-round'] =
                (rangeEnd && isStart) ||
                (!rangeEnd && isHovered && isBeforeStart) ||
                (!rangeEnd && isStart && isBeforeHovered) ||
                (isHovered && isStart);

            classDictionary['right-round'] =
                isEnd ||
                (!rangeEnd && isHovered && isAfterStart) ||
                (!rangeEnd && isStart && isOnOrAfterHovered);

            classDictionary['new-selection'] = !rangeStart || !rangeEnd;
        }

        className = Object.keys(classDictionary)
            .filter((key) => classDictionary[key])
            .join(' ');

        return (
            <Day key={ col }
                date={ date }
                selected={ isStart || isEnd }
                inRange={ styleInRange && inRange }
                disabled={ !date || date > Date.now() }
                className={ className }
                onMouseEnter={ this.onHoverOverDate.bind(this) }
                onClick={ this.selectDate.bind(this) } />
        );
    }

    render() {
        const { year, month } = this.props;

        return (
            <div className="month">
                <span className="title">
                    { formatDate(new Date(year, month - 1, 1), '%B %Y') }
                </span>
                { this.generateMatrix().map((week, row) =>
                    <div key={ row }
                        className="week">
                        { week.map(this.renderDay.bind(this)) }
                    </div>
                ) }
            </div>
        );
    }
}

export default Month;
