import React from 'react';
import ReactDOM from 'react-dom';
import { VelocityComponent } from 'velocity-react';
import Month from '../month';
import Button from '../../button';
import { datesEqual } from '../../../etc/date-helpers';
import './date-range-picker.less';

/**\

    title: DateRangePicker
    category: Atom
    description: A date range picker spanning an arbitrary number of months
    props:
        initialRangeStart[Date]: the initial date range start date. Optional
        initialRangeEnd[Date]: the initial date range end date. Optional
        year[Number]: the full year (i.e. 2016) of the latest month to show
        month[Number]: the month (i.e. 6) of the latest month to show
        startsOn[Number]: the day that the week starts on (0 = Sunday)
        onSelectDates[Function]:
            a callback for when dates are selected. the callback is invoked
            with { start, end }, where each are Dates and end may be null.
        minimumNumMonths[Number]:
            the minimum number of months to show. no matter what, all the
            months needed to show the selected range will be shown.

\**/
class DateRangePicker extends React.Component {
    static defaultProps = {
        initialRangeStart: null,
        initialRangeEnd: null,
        year: new Date().getFullYear(),
        month: new Date().getMonth() + 1,
        startsOn: 0, // 0 = sunday
        minimumNumMonths: 6
    };

    static propTypes = {
        initialRangeStart: React.PropTypes.instanceOf(Date),
        initialRangeEnd: React.PropTypes.instanceOf(Date),
        year: React.PropTypes.number,
        month: React.PropTypes.oneOf([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]),
        startsOn: React.PropTypes.oneOf([ 0, 1, 2, 3, 4, 5, 6 ]),
        onSelectDates: React.PropTypes.func,
        minimumNumMonths: React.PropTypes.number
    };

    state = {
        hoveredDate: null,
        numMonths: 6,
        pickedRangeStart: null,
        pickedRangeEnd: null,
        madeFirstSelection: false
    }

    hoverOverDate(date) {
        this.setState({ hoveredDate: date });
    }

    onMouseLeave() {
        this.setState({ hoveredDate: null });
    }

    constructor(props) {
        const { initialRangeStart, initialRangeEnd } = props;

        super(props);

        this.state.numMonths = this.getNumMonths();
        this.state.pickedRangeStart = initialRangeStart;
        this.state.pickedRangeEnd = initialRangeEnd;
    }

    componentWillReceiveProps(props) {
        this.setState({
            numMonths: this.getNumMonths(props)
        });
    }

    getNumMonths(props) {
        const {
            month,
            year,
            initialRangeStart,
            minimumNumMonths
        } = props || this.props;

        let rangeMonths = 0;

        if (initialRangeStart) {
            // extend the range until it includes the start date
            while (initialRangeStart < new Date(year, month - rangeMonths, 1)) {
                rangeMonths += 1;
            }
        }

        return Math.max(minimumNumMonths, rangeMonths);
    }

    scrollToRange() {
        const { initialRangeStart, year, month } = this.props,
            { numMonths } = this.state,
            monthsEl = ReactDOM.findDOMNode(this.monthsDiv);
        let offset = monthsEl.scrollHeight;

        /* istanbul ignore else: should never be false */
        if (initialRangeStart) {
            // find index of month containing initialRangeStart
            const idx = (function() {
                const rangeStartYear = initialRangeStart.getFullYear();
                let months = 0;

                months += Math.max(0, year - rangeStartYear - 1) * 12;
                months += (month + 12 - initialRangeStart.getMonth()) % 12;

                return Math.max(0, numMonths - months);
            })();

            offset = monthsEl.children[idx].offsetTop - 36;
        }

        monthsEl.scrollTop = offset;
    }

    fitToWindow() {
        const MAX_HEIGHT = 480,
            MIN_HEIGHT = 240,
            BOTTOM_PADDING = 70,
            resize = () => {
                const months = this.monthsDiv;

                if (months) {
                    const rect = months.getBoundingClientRect(),
                        allowance = (
                            window.innerHeight -
                            rect.top -
                            BOTTOM_PADDING),
                        nextHeight = Math.min(
                            MAX_HEIGHT,
                            Math.max(MIN_HEIGHT, allowance));

                    months.style.height = `${nextHeight}px`;
                }
            };

        resize();
        window.addEventListener('resize', resize);
    }

    componentDidMount() {
        this.scrollToRange();
        this.fitToWindow();
    }

    pickDate(date) {
        // This is when dates are picked in the calendar but not yet applied

        const {
            pickedRangeStart, pickedRangeEnd, madeFirstSelection
        } = this.state;
        let _rangeStart = null,
            _rangeEnd = null;

        if (!madeFirstSelection || !pickedRangeStart || pickedRangeEnd) {
            // start new selection
            _rangeStart = date;
            _rangeEnd = null;
        } else if (!pickedRangeEnd && pickedRangeStart) {
            _rangeStart = pickedRangeStart;

            if (datesEqual(date, pickedRangeStart)) {
                // if selecting same date as first date, do nothing.
                // make sure rangeEnd is null to indicate single date
                _rangeEnd = null;
            } else if (date < pickedRangeStart) {
                // reverse the dates
                _rangeStart = date;
                _rangeEnd = pickedRangeStart;
            } else {
                // select the end date
                _rangeEnd = date;
            }
        }

        this.setState({
            pickedRangeStart: _rangeStart,
            pickedRangeEnd: _rangeEnd,
            madeFirstSelection: true
        });
    }

    onSelectDates() {
        const { onSelectDates } = this.props,
            { pickedRangeStart, pickedRangeEnd } = this.state;

        /* istanbul ignore else: checked by proptypes */
        if (typeof onSelectDates === 'function') {
            onSelectDates({
                start: pickedRangeStart,
                end: pickedRangeEnd
            });
        }
    }

    render() {
        const {
                year,
                month,
                startsOn,
                initialRangeStart,
                initialRangeEnd
            } = this.props,
            {
                pickedRangeStart,
                pickedRangeEnd,
                numMonths,
                hoveredDate,
                madeFirstSelection
            } = this.state,
            months = [],
            weekdays = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
        let changed = false,
            styleInRange = true;

        for (let i = numMonths; i > 0; i--) {
            months.push(new Date(year, month - i, 1));
        }

        // only show apply button if picked dates are different from initial
        if (!datesEqual(initialRangeStart, pickedRangeStart) ||
            !datesEqual(initialRangeEnd, pickedRangeEnd)) {
            changed = true;
        }

        if (!madeFirstSelection && !pickedRangeEnd) {
            styleInRange = false;
        }

        return (
            <div className="date-range-picker"
                onMouseLeave={ this.onMouseLeave.bind(this) }>
                <div className="background">
                    <div className="days-of-week">
                        {
                            weekdays
                                .slice(startsOn)
                                .concat(weekdays.slice(0, startsOn))
                                .map((name, i) => (
                                    <span key={ i }>{ name[0] }</span>
                                ))
                        }
                    </div>
                    <div className="months"
                        ref={ (c) => (this.monthsDiv = c) }>
                        { months.map((firstDateOfMonth, key) =>
                            <Month key={ key }
                                year={ firstDateOfMonth.getFullYear() }
                                month={ firstDateOfMonth.getMonth() + 1 }
                                rangeStart={ pickedRangeStart }
                                rangeEnd={ pickedRangeEnd }
                                hoveredDate={ hoveredDate }
                                styleInRange={ styleInRange }
                                startsOn={ startsOn }
                                onHoverOverDate={
                                    this.hoverOverDate.bind(this)
                                }
                                onSelectDate={ this.pickDate.bind(this) } />
                        ) }
                    </div>
                </div>
                <VelocityComponent
                    animation={ changed ? 'fadeIn' : 'fadeOut' }
                    duration={ 260 }>
                    <Button click={ this.onSelectDates.bind(this, null) }>
                        Apply
                    </Button>
                </VelocityComponent>
            </div>
        );
    }
}

export default DateRangePicker;
