import * as React from 'react';
import FormComponent from '../FormComponent';
import classNames from 'classnames';
import Input from '../Input';
import {isEqual, isEmpty} from 'lodash';
import {getId} from '../function';
import {DayPickerRangeController, DayPickerSingleDateController} from 'react-dates';
import 'react-dates/initialize';
import 'react-dates/lib/css/_datepicker.css';
import * as moment from 'moment';
import OutsideClickHandler from 'react-outside-click-handler';

export interface DisableDays {
    (date?: moment.Moment): boolean
}

export interface Time {
    caption: string,
    value: any,
    noon?: string
}

export interface DatePickerProps {
    rangePicker?: boolean,
    dateStart?: any,
    dateEnd?: any,
    color?: string,
    errorMessage?: string,
    numberOfMonths?: number,
    date?: any,
    weekendDisabled?: boolean,
    dateFormat: string,
    minDate?: any
    maxDate?: any,
    disableDays?: DisableDays,
    showTime?: boolean,
    timeStart?: Time,
    timeEnd?: Time,
    time?: Time,
    timeAs24?: boolean,
    timeIncludeSeconds?: boolean,
    onChange?: Function,
    disabled?: boolean,
    readOnly?: boolean,
    timeValidate?: boolean,
    uiType?: string,
    inputOption?: any,
    validate?: any[],
    datePickerFocus?: any;
}

export interface DatePickerState {
    uuid: string,
    rangePicker: boolean,
    dateStart?: any,
    dateEnd?: any,
    customStyle: any,
    date: any,
    dateFormat: string,
    datePickerFocus: any,
    minDate: any,
    maxDate: any,
    timeStart: Time,
    timeEnd: Time,
    time: Time,
    timeFormat: string,
    timeIncludeSeconds: boolean,
    timeValidate: boolean,
    show: boolean,
    inputOption: any,
    dateInputMask: any,
    color: string,
    errorMessage?: string,
    numberOfMonths?: number,
    weekendDisabled?: boolean,
    disableDays?: DisableDays,
    showTime?: boolean,
    timeAs24?: boolean,
    onChange?: Function,
    disabled?: boolean,
    readOnly? : boolean,
    uiType?:string,
    validate?: any[]
}

const START_DATE = 'startDate';
const DEFAULT_DATE_FORMAT = 'YYYY.MM.DD';
// const END_DATE = 'endDate';

export default class DatePicker extends FormComponent<DatePickerProps, DatePickerState> {
    private div = React.createRef<HTMLDivElement>();

    private selectedFromDatePicker: boolean = false;

    constructor(props: DatePickerProps) {
        super(props);


        const {time, timeStart, timeEnd, rangePicker } = props;
        const dateFormat = props.dateFormat || DEFAULT_DATE_FORMAT;

        let iOption = {};
        if (props.inputOption && !isEmpty(props.inputOption)) {
            iOption = {
                editable: props.inputOption.editable || true,
                icon: props.inputOption.icon || null,
                label: props.inputOption.label || null,
                caption: props.inputOption.caption || '',
                size: props.inputOption.size || 'medium',
                customStyle: props.inputOption.customStyle || '',
                closeAfterSelect: props.inputOption.closeAfterSelect || true,
                infoText: props.inputOption.infoText || '',
                autoFocus: props.inputOption.autoFocus || false,
                noBorder: props.inputOption.noBorder || false,
                noRadius: props.inputOption.noRadius || false,
            }
        }

        const uuid = getId(null, this.defaultId);

        this.state = {
            rangePicker: false,
            dateInputMask: this.getInputMask(rangePicker ? rangePicker : false, dateFormat),
            customStyle: '',
            color: 'black',
            timeIncludeSeconds: false,
            // weekendDisabled: false,
            // doValidate: !props.disabled || true,
            show: false,
            timeValidate: false,
            uuid: uuid,
            ...props,
            dateFormat,
            inputOption: iOption,
            timeFormat: this.props.timeIncludeSeconds ? '00:00:00' : '00:00',
            time: !time ? {
                caption: 'Start Time',
                value: '12:00:00',
                noon: 'am'
            } : time,
            timeStart: !timeStart ? {
                caption: 'Start Time',
                value: '12:00:00',
                noon: 'am'
            } : timeStart,
            timeEnd: !timeEnd ? {
                caption: 'End Time',
                value: '12:00',
                noon: 'am'
            } : timeEnd,
            date: props.date ? moment(props.date, dateFormat) : null,
            minDate: props.minDate ? moment(props.minDate, dateFormat) : null,
            maxDate: props.maxDate ? moment(props.maxDate, dateFormat) : null,
            // For RangePicker
            datePickerFocus: props.rangePicker ? START_DATE : true,
            dateStart: props.dateStart ? moment(props.dateStart, dateFormat) : null,
            dateEnd: props.dateEnd ? moment(props.dateEnd, dateFormat) : null,
        }
    }

    componentDidMount() {

        if (this.state.inputOption.autoFocus) {
            this.openDatePicker()
        }

        this.validate(this.getInputValue());
        this.updateOnChangeCallback()
    }

    getInputMask = (isRangePicker, dateFormat) => {
        return isRangePicker ? { mask: `${dateFormat} - ${dateFormat}`.replace(/[A-Z]/g, '0') } : { mask: dateFormat.replace(/[A-Z]/g, '0') }
    }

    getInputValue() {
        const {rangePicker, dateEnd, dateStart} = this.state;
        let x = this.getDateValue();
        let date_:string = '';
        let dateStart_:string = '';
        let dateEnd_:string = '';

        if (rangePicker) {
            if (dateStart && dateEnd) {
                dateEnd_ = x['dateEnd'].dateFormatted;
                dateStart_ = x['dateStart'].dateFormatted;

                date_ = dateStart_ && dateEnd_ ? dateStart_ +' - '+ dateEnd_ : ''
            } else {
                date_ = ''
            }
        } else {
            date_ = x['date'] ? x['date'].dateFormatted : ''

        }
        return date_
    }

    isDatetoString(date:any) {
        return moment(date, this.state.dateFormat).format(this.state.dateFormat)
    }

    componentWillReceiveProps(nextProps:any) {
        if (!isEqual(this.isDatetoString(nextProps.date), this.isDatetoString(this.props.date))) {
            const newDate = moment(nextProps.date, this.state.dateFormat);

            if (newDate.isValid()) {
                this.setState({
                    date: newDate
                })
            } else {
                this.setState({
                    date: null
                })
            }
        }

        if (!isEqual(this.isDatetoString(nextProps.dateStart), this.isDatetoString(this.props.dateStart))) {
            const newDate = moment(nextProps.dateStart, this.state.dateFormat);
            if (newDate.isValid()) {
                this.setState({
                    dateStart: newDate
                })
            } else {
                this.setState({
                    dateStart: null
                })
            }
        }

        if (!isEqual(this.isDatetoString(nextProps.dateEnd), this.isDatetoString(this.props.dateEnd))) {
            const newDate = moment(nextProps.dateEnd, this.state.dateFormat);
            if (newDate.isValid()) {
                this.setState({
                    dateEnd: newDate
                })
            } else {
                this.setState({
                    dateEnd: null
                })
            }
        }

        if ( !isEqual(nextProps.dateFormat, this.props.dateFormat)) {
            if (typeof nextProps.dateFormat !== 'undefined') {

                this.setState({
                    dateFormat: nextProps.dateFormat,
                    dateInputMask: this.getInputMask(nextProps.rangePicker, nextProps.dateFormat)
                })
            } else {
                this.setState({
                    dateFormat: DEFAULT_DATE_FORMAT,
                    dateInputMask: this.getInputMask(nextProps.rangePicker, DEFAULT_DATE_FORMAT)
                })
            }
        }

        if (!isEqual(nextProps.datePickerFocus, this.props.datePickerFocus)) {
            this.setState({
                datePickerFocus: nextProps.datePickerFocus
            })
        }

        if (!isEqual(nextProps.rangePicker, this.props.rangePicker)) {
            this.setState({
                rangePicker: nextProps.rangePicker,
                datePickerFocus: nextProps.rangePicker ? START_DATE : true,
                dateInputMask: this.getInputMask(nextProps.rangePicker, this.state.dateFormat)
            });
        }

        if (!isEqual(this.isDatetoString(nextProps.minDate), this.isDatetoString(this.props.minDate))) {
            this.setState({
                minDate: nextProps.minDate
            })
        }

        if (!isEqual(this.isDatetoString(nextProps.maxDate), this.isDatetoString(this.props.maxDate))) {
            this.setState({
                maxDate: nextProps.maxDate
            })
        }

        if (!isEqual(nextProps.time, this.props.time) && nextProps.time) {
            this.setState({
                time: nextProps.time
            })
        }

        if (!isEqual(nextProps.timeEnd, this.props.timeEnd) && nextProps.timeEnd) {
            this.setState({
                timeEnd: nextProps.timeEnd
            })
        }

        if (!isEqual(nextProps.timeStart, this.props.timeStart) && nextProps.timeStart) {
            this.setState({
                timeStart: nextProps.timeStart
            })
        }

        if (!isEqual(nextProps.inputOption, this.props.inputOption)) {
            this.setState({
                inputOption: nextProps.inputOption
            })
        }

        if (!isEqual(nextProps.weekendDisabled, this.props.weekendDisabled)) {
            this.setState({
                weekendDisabled: nextProps.weekendDisabled,
                show: false
            })
        }

        if (!isEqual(nextProps.timeIncludeSeconds, this.props.timeIncludeSeconds)) {
            this.setState({
                timeIncludeSeconds: nextProps.timeIncludeSeconds,
                timeFormat: nextProps.timeIncludeSeconds ? '00:00:00' : '00:00',
                show: false
            })
        }

        if (!isEqual(nextProps.timeValidate, this.props.timeValidate)) {
            this.setState({
                timeValidate: true
            })
        }
    }

    isAfterMinDate = (date: moment.Moment) => {
        const {minDate} = this.state;
        return date.isSameOrAfter(minDate, 'day');
    };

    isBeforeMaxDate = (date: moment.Moment) => {
        const {maxDate} = this.state;

        return date.isSameOrBefore(maxDate, 'day');
    };

    isWeekEnd = (date: moment.Moment) => {
        const weekDay = date.day();
        return weekDay === 6 || weekDay === 0
    };

    dateOutsideRules = (date: moment.Moment) => {
        const {minDate, maxDate} = this.state;
        const {weekendDisabled} = this.props;
        const checkMinDate = minDate ? !this.isAfterMinDate(date) : false;
        const checkMaxDate = maxDate ? !this.isBeforeMaxDate(date) : false;
        const checkWeekend = weekendDisabled ? this.isWeekEnd(date) : false;

        return checkMinDate || checkMaxDate || checkWeekend;
    };

    updateOnChangeCallback = () => {
        const {onChange} = this.props;
        this.validate(this.getInputValue());
        onChange && onChange(this.getDateValue());
    };

    getDateValue = () => {
        const {date, dateStart, dateEnd, time, timeStart, timeEnd, rangePicker, dateFormat, timeIncludeSeconds} = this.state;
        const {showTime, timeAs24} = this.props;

        const fallbackValue = [0, 0, 0];
        const timeArray = time.value ? time.value.split(':') : fallbackValue;
        const timeStartArray = time.value ? timeStart.value.split(':') : fallbackValue;
        const timeEndArray = time.value? timeEnd.value.split(':') : fallbackValue;

        let value:any = null;

        if (rangePicker) {

            if (dateStart && dateEnd) {
                value = {
                    dateStart: {
                        dateAsMoment: dateStart,
                        dateFormatted: dateStart.format(dateFormat),
                        dateAsDate: dateStart.toDate()
                    },
                    timeStart: {
                        timeAsFormatted: timeStart.value,
                        timeAsObject: {
                            hours: parseInt(timeStartArray[0] || 0),
                            minutes: parseInt(timeStartArray[1] || 0),
                            seconds: parseInt(timeStartArray[2] || 0)
                        },
                        noon: timeStart.noon
                    },
                    dateEnd: {
                        dateAsMoment: dateEnd,
                        dateFormatted: dateEnd.format(dateFormat),
                        dateAsDate: dateEnd.toDate()
                    },
                    timeEnd: {
                        timeAsFormatted: timeEnd.value,
                        timeAsObject: {
                            hours: parseInt(timeEndArray[0] || 0),
                            minutes: parseInt(timeEndArray[1] || 0),
                            seconds: parseInt(timeEndArray[2] || 0)
                        },
                        noon: timeEnd.noon
                    }
                };

                !timeIncludeSeconds ? delete value.timeStart.timeAsObject.seconds : null;
                !timeIncludeSeconds ? delete value.timeEnd.timeAsObject.seconds : null;

                !time.noon || timeAs24 ? delete value.timeStart.noon : null;
                !time.noon || timeAs24 ? delete value.timeEnd.noon : null;

                !showTime ? delete value.timeStart : null;
                !showTime ? delete value.timeEnd : null;
            } else {
                value = ''
            }

            return value;
        } else {

            if (date) {
                value = {
                    date: {
                        dateAsMoment: date,
                        dateFormatted: date.format(dateFormat),
                        dateAsDate: date.toDate()
                    },
                    time: {
                        timeAsFormatted: time.value,
                        timeAsObject: {
                            hours: parseInt(timeArray[0]),
                            minutes: parseInt(timeArray[1]),
                            seconds: parseInt(timeArray[2] || 0)
                        },
                        noon: time.noon
                    }
                }

                !timeIncludeSeconds ? delete value.time.timeAsObject.seconds : null;
                !time.noon || timeAs24 ? delete value.time.noon : null;
                !showTime ? delete value.time : null;
            } else {
                value = ''
            }

            return value;
        }
    };

    onSingleDatePickerDateChange = (date:any) => {
        this.selectedFromDatePicker = true;

        const { uiType, inputOption } = this.props;

        this.setState({
            date: date,
            show: uiType === 'input' && inputOption.closeAfterSelect ? !inputOption.closeAfterSelect : true
        }, () => {
            this.updateOnChangeCallback()
        });
    };

    onSingleDatePickerFocusChange = () => {
        this.selectedFromDatePicker = true;

        this.setState({
            datePickerFocus: true
        })
    };

    onTimeChange = (value: any, stateKey: string, time12Data: any) => {
        const { timeFormat } = this.state;
        const temp = {};

        if (value.length !== timeFormat.length) {
            return;
        }

        temp[stateKey] = {
            ...this.state[stateKey],
            value
        };

        if (time12Data) {
            temp[stateKey].noon = time12Data.dropdown;
        }

        this.setState({
            ...temp
        }, () => {
            this.updateOnChangeCallback();
        });
    };

    renderTime = (time: Time, stateKey: string) => {
        const {timeFormat, rangePicker} = this.state;
        const {numberOfMonths, timeAs24, timeValidate, timeIncludeSeconds} = this.props;
        let dd:any = {};

        if (!timeAs24) {
            dd = {
                dropdown: {
                    data: [{id: 'pm', text: 'PM'}, {id: 'am', text: 'AM'}],
                    selected: time.noon ? time.noon.toLowerCase() : 'am',
                    displayField: 'text',
                    displayValue: 'id'
                }
            }
        }

        let style:any = {};

        if (rangePicker) {

            if (numberOfMonths && numberOfMonths > 1) {
                style = {
                    width: 'calc(50% - 10px)'
                }
            } else {
                style = {
                    flexGrow: 1,
                    marginBottom: 20
                }
            }

        }

        let timeV:any = {};
        if (timeValidate) {
            timeV = {
                validate: ['required', timeAs24 ? 'time24' : 'time']
            }
        }

        const hasSeconds: boolean = time.value.match(/:/g).length === 2;


        let timeValue = !timeIncludeSeconds ? (hasSeconds ? time.value.substr(0, time.value.lastIndexOf(':')) : time.value) : time.value;

        return (
            <div className="datepicker-time-container" style={...style}>
                <Input
                    caption={time.caption}
                    type="text"
                    onChange={(val: any) => {
                        this.onTimeChange(timeAs24 ? val : val.value, stateKey, timeAs24 ? null : val);
                    }}
                    {...timeV}
                    mask={timeFormat}
                    icon="fal fa-clock"
                    value={timeValue}
                    size='small'
                    {...dd}
                />
            </div>
        )
    };

    renderRangeDate() {
        const {datePickerFocus, timeStart, timeEnd, dateStart, dateEnd, customStyle} = this.state;
        const {color, uiType, numberOfMonths, disableDays, showTime, inputOption } = this.props;

        const uiClass = classNames(`krax-date ${customStyle || ''}krax-date-${color || 'black'}`, {
            'focus': focus,
            'error': !isEmpty(this.error()),
            'input-modal': uiType === 'input'
        });


        return <OutsideClickHandler onOutsideClick={(e:any) => {
            if (uiType !== 'input') return;
            if (this.div.current && !this.div.current['contains'](e.target)) this.closeDatePicker()
        }}>
            <div className={`${uiClass}`}>
                <DayPickerRangeController
                    numberOfMonths={numberOfMonths || 1}
                    onDatesChange={({ startDate, endDate }) => {
                        this.setState({ dateStart: startDate, dateEnd: endDate }, () => {
                            this.selectedFromDatePicker = true;

                            if (startDate && endDate && datePickerFocus === 'endDate') {

                                this.setState({
                                    show: uiType === 'input' ? (typeof inputOption.closeAfterSelect !== 'undefined' ? !inputOption.closeAfterSelect : false) : true
                                }, () => {
                                    this.updateOnChangeCallback()
                                })
                            }
                        });
                    }}
                    focusedInput={datePickerFocus}
                    startDate={dateStart}
                    endDate={dateEnd}
                    // daySize={20}
                    onFocusChange={(focusedInput) => {
                        this.setState({
                            datePickerFocus: !focusedInput ? START_DATE : focusedInput
                        });
                    }}
                    noBorder
                    isOutsideRange={disableDays && disableDays || this.dateOutsideRules}
                    renderCalendarInfo={showTime? () => {
                        return (
                            <div style={{
                                padding: '10px 22px 20px 22px',
                                display: 'flex',
                                flexDirection: numberOfMonths && numberOfMonths > 1 ? 'row' : 'column',
                                justifyContent: numberOfMonths && numberOfMonths > 1 ? 'space-between' : 'center'
                            }}>
                                {this.renderTime(timeStart, 'timeStart')}
                                {this.renderTime(timeEnd, 'timeEnd')}
                            </div>)
                    } : () => ''}
                    hideKeyboardShortcutsPanel
                />
                {this.error() && uiType !== 'input' ? <div className="date-error-container">{this.error()}</div> : null}
            </div>
        </OutsideClickHandler>
    }

    renderSingleDate() {
        const {date, datePickerFocus, time, customStyle} = this.state;
        const {color, numberOfMonths, uiType, disableDays, showTime} = this.props;
        const uiClass = classNames(`krax-date ${customStyle || ''} krax-date-${color || 'black'}`, {
            'focus': focus,
            'error': !isEmpty(this.error()),
            'input-modal': uiType === 'input',
            'is-date-inline': uiType !== 'input'
        });

        return <OutsideClickHandler onOutsideClick={(e:any) => {
            if (uiType !== 'input') return;
            if (this.div.current && this.div.current['contains'] && !this.div.current['contains'](e.target)) {
                this.closeDatePicker()
            }
        }}>
            <div className={`${uiClass}`}>
                <DayPickerSingleDateController
                    onDateChange={this.onSingleDatePickerDateChange}
                    onFocusChange={this.onSingleDatePickerFocusChange}
                    focused={datePickerFocus}
                    date={date}
                    hideKeyboardShortcutsPanel
                    noBorder
                    isOutsideRange={disableDays && disableDays || this.dateOutsideRules}
                    numberOfMonths={numberOfMonths || 1}
                    calendarInfoPosition="bottom"
                    renderCalendarInfo={showTime ? () => {
                        return (
                            <div style={{ padding: '10px 22px 20px 22px'}}>
                                {this.renderTime(time, 'time')}
                            </div>)
                    } : () => ''}
                />
                {this.error() && uiType !== 'input' ? <div className="date-error-container">{this.error()}</div> : null}
            </div>
        </OutsideClickHandler>
    }

    renderInline() {
        const {rangePicker, disabled, readOnly} = this.props;

        return <div style={{position: 'relative'}}>
            <div className="disabled-mask" style={{
                position: 'absolute',
                width: '100%',
                height: '100%',
                left: 0,
                top: 0,
                zIndex: 3,
                background: 'rgba(255,255,255,.5)',
                display: !disabled ? 'none' : 'block'
            }}/>
            <div className="disabled-mask" style={{
                position: 'absolute',
                width: '100%',
                height: '100%',
                left: 0,
                top: 0,
                zIndex: 3,
                background: 'rgba(255,255,255,.2)',
                display: !readOnly ? 'none' : 'block'
            }}/>
            {!rangePicker ? this.renderSingleDate() : this.renderRangeDate()}

        </div>
    }

    openDatePicker = () => {
        this.setState({
            show: true
        });
    };

    closeDatePicker = () => {
        this.setState({
            show: false
        })
    };

    onInputChange = (dateString:any) => {
        const {dateFormat, inputOption, rangePicker, date, dateInputMask} = this.state;

        if (this.selectedFromDatePicker) {
            this.selectedFromDatePicker = false;
            return;
        }

        if (dateString.length === dateInputMask.mask.length) {
            if (rangePicker) {
                const dateStart = moment(dateString.split(' - ')[0], dateFormat);
                const dateEnd = moment(dateString.split(' - ')[1], dateFormat);
                if (dateStart.isValid() && dateEnd.isValid() && !this.dateOutsideRules(dateStart) && !this.dateOutsideRules(dateEnd)) {
                    this.setState({
                        dateStart: dateStart,
                        dateEnd: dateEnd,
                        show: !inputOption.closeAfterSelect
                    }, () => this.updateOnChangeCallback());
                }

            } else {
                const newDate = moment(dateString, dateFormat);
                if (newDate.isValid() && !this.dateOutsideRules(newDate) && newDate !== date) {
                    this.setState({
                        date: newDate,
                        show: !inputOption.closeAfterSelect
                    }, () => {
                        this.updateOnChangeCallback()
                    });
                }
            }
        } else if (!dateString.length) {
            this.setState({
                date: null,
                dateStart: null,
                dateEnd: null,
                show: !inputOption.closeAfterSelect
            }, () => {
                this.updateOnChangeCallback()
            });
        }
    };

    getRangeInputValue() {
        const {dateStart, dateEnd, dateFormat} = this.state;

        if (dateStart && dateEnd) {
            return `${dateStart.format(dateFormat)} - ${dateEnd.format(dateFormat)}`
        }

        return '';
    }

    renderInput() {

        const {show, uuid, date, inputOption, dateInputMask, dateFormat} = this.state;
        const {disabled, rangePicker} = this.props;

        return <div style={{position: 'relative'}} ref={this.div}>
            <Input
                id={uuid}
                name="Datepicker-Inputcomponent"
                caption={inputOption.caption}
                type="text"
                onChange={this.onInputChange}
                validate={['required']}
                disabled={disabled}
                readOnly={this.readOnly()}
                readOnlyInput={!inputOption.editable}
                errorMessage={this.error()}
                autoComplete="off"
                autoFocus={inputOption.autoFocus}
                onClick={() => {
                    if (!show || inputOption.autoFocus) {
                        this.openDatePicker()
                    }
                }}
                icon={inputOption.icon}
                label={inputOption.label}
                {...dateInputMask}
                value={rangePicker ? this.getRangeInputValue() : date ? moment(date, dateFormat).format(dateFormat) : '' }
                size={inputOption.size}
                customStyle={`${inputOption.customStyle} ${show ? 'is-bottom-radius-none' : ''}`}
                debounce={300}
                noBorder={inputOption.noBorder}
                noRadius={inputOption.noRadius}
                infoText={inputOption.infoText}
                customRender={() => !rangePicker ? (show ? this.renderSingleDate() : null) : (show ? this.renderRangeDate() : null)}
                inputDropdown={show}
            />
            {/*{!rangePicker ? (show ? this.renderSingleDate() : null) : (show ? this.renderRangeDate() : null)}*/}
        </div>
    }

    renderDatePicker() {
        const {uiType} = this.props;

        if (uiType === 'input') {
            return  this.renderInput();
        }

        return this.renderInline()
    }

    public render() {

        return this.renderDatePicker()
    }
}
