import React, {Component} from 'react';
import * as ReactNative from 'react-native';
import PropTypes from 'prop-types';
import XDate from 'xdate';

import dateutils from '../dateutils';
import {xdateToData, parseDate} from '../interface';
import styleConstructor from './style';
import Day from './day/basic';
import UnitDay from './day/period';
import MultiDotDay from './day/multi-dot';
import MultiPeriodDay from './day/multi-period';
import SingleDay from './day/custom';
import CalendarHeader from './header';
import shouldComponentUpdate from './updater';
import GestureRecognizer, {swipeDirections} from 'react-native-swipe-gestures';
import {SELECT_DATE_SLOT} from '../testIDs';
import signs, { zodiacLocale } from '../lib/zodiac'

//Fallback for react-native-web or when RN version is < 0.44
const {View, ViewPropTypes, TouchableWithoutFeedback} = ReactNative;
const viewPropTypes =
  typeof document !== 'undefined'
    ? PropTypes.shape({style: PropTypes.object})
    : ViewPropTypes || View.propTypes;
const EmptyArray = [];

/**
 * @description: Calendar component
 * @example: https://github.com/wix/react-native-calendars/blob/master/example/src/screens/calendars.js
 * @gif: https://github.com/wix/react-native-calendars/blob/master/demo/calendar.gif
 */
class Calendar extends Component {
  static displayName = 'Calendar';

  static propTypes = {
    /** Specify theme properties to override specific styles for calendar parts. Default = {} */
    theme: PropTypes.object,
    /** Collection of dates that have to be marked. Default = {} */
    markedDates: PropTypes.object,
    /** Specify style for calendar container element. Default = {} */
    style: viewPropTypes.style,
    /** Initially visible month. Default = Date() */
    current: PropTypes.any,
    /** Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined */
    minDate: PropTypes.any,
    /** Maximum date that can be selected, dates after maxDate will be grayed out. Default = undefined */
    maxDate: PropTypes.any,
    /** If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday. */
    firstDay: PropTypes.number,
    /** Date marking style [simple/period/multi-dot/multi-period]. Default = 'simple' */
    markingType: PropTypes.string,
    /** Hide month navigation arrows. Default = false */
    hideArrows: PropTypes.bool,
    /** Display loading indicator. Default = false */
    displayLoadingIndicator: PropTypes.bool,
    /** Do not show days of other months in month page. Default = false */
    hideExtraDays: PropTypes.bool,
    /** Always show six weeks on each month. Default = false */
    showSixWeeks: PropTypes.bool,
    /** Handler which gets executed on day press. Default = undefined */
    onDayPress: PropTypes.func,
    /** Handler which gets executed on day long press. Default = undefined */
    onDayLongPress: PropTypes.func,
    /** Handler which gets executed when month changes in calendar. Default = undefined */
    onMonthChange: PropTypes.func,
    /** Handler which gets executed when visible month changes in calendar. Default = undefined */
    onVisibleMonthsChange: PropTypes.func,
    /** Replace default arrows with custom ones (direction can be 'left' or 'right') */
    renderArrow: PropTypes.func,
    /** Provide custom day rendering component */
    dayComponent: PropTypes.any,
    /** Month format in calendar title. Formatting values: http://arshaw.com/xdate/#Formatting */
    monthFormat: PropTypes.string,
    /** Disables changing month when click on days of other months (when hideExtraDays is false). Default = false */
    disableMonthChange: PropTypes.bool,
    /**  Hide day names. Default = false */
    hideDayNames: PropTypes.bool,
    /** Disable days by default. Default = false */
    disabledByDefault: PropTypes.bool,
    /** Show week numbers. Default = false */
    showWeekNumbers: PropTypes.bool,
    /** Handler which gets executed when press arrow icon left. It receive a callback can go back month */
    onPressArrowLeft: PropTypes.func,
    /** Handler which gets executed when press arrow icon right. It receive a callback can go next month */
    onPressArrowRight: PropTypes.func,
    /** Disable left arrow. Default = false */
    disableArrowLeft: PropTypes.bool,
    /** Disable right arrow. Default = false */
    disableArrowRight: PropTypes.bool,
    /** Style passed to the header */
    headerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
    /** Provide aria-level for calendar heading for proper accessibility when used with web (react-native-web) */
    webAriaLevel: PropTypes.number,
    /** Apply custom disable color to selected day indexes */
    disabledDaysIndexes: PropTypes.arrayOf(PropTypes.number),
    /** Disable all touch events for disabled days. can be override with disableTouchEvent in markedDates*/
    disableAllTouchEventsForDisabledDays: PropTypes.bool,
    /** Replace default month and year title with custom one. the function receive a date as parameter. */
    renderHeader: PropTypes.any,
    /** Allow rendering of a totally custom header */
    customHeader: PropTypes.any,
    /** Enable the option to swipe between months. Default: false */
    enableSwipeMonths: PropTypes.bool
  };

  static defaultProps = {
    enableSwipeMonths: false
  };

  constructor(props) {
    super(props);

    let currentMonth = typeof props.current === 'string'
      ? new XDate(props.current)
      : props.current || XDate()

    // @todo: genericize this
    // if (props.locale === 'zodiac') {
    //   XDate.locales.zodiac = zodiacLocale
    //   XDate.defaultLocale = 'zodiac'
    //
    //   const now = props.current || new Date(Date.now())
    //   const year = now.getFullYear()
    //   const month = now.getMonth() - 1 >= 0
    //     ? now.getMonth()
    //     : 12
    //   const day = now.getDate()
    //
    //   currentMonth = new XDate(year, month, day, 0, 0, 0, true)
    // }

    this.style = styleConstructor(this.props.theme);
    this.state = { currentMonth };

    this.updateMonth = this.updateMonth.bind(this);
    this.pressDay = this.pressDay.bind(this);
    this.longPressDay = this.longPressDay.bind(this);
    this.shouldComponentUpdate = shouldComponentUpdate;
  }

  updateMonth(day, doNotTriggerListeners) {
    if (day.toString('yyyy MM') === this.state.currentMonth.toString('yyyy MM')) {
      return;
    }
    this.setState({
      currentMonth: day.clone()
    }, () => {
      if (!doNotTriggerListeners) {
        const currMont = this.state.currentMonth.clone();
        if (this.props.onMonthChange) {
          this.props.onMonthChange(xdateToData(currMont));
        }
        if (this.props.onVisibleMonthsChange) {
          this.props.onVisibleMonthsChange([xdateToData(currMont)]);
        }
      }
    });
  }

  _handleDayInteraction(date, interaction) {
    if (this.props.onDayPress) {
      this.props.onDayPress(date)
    }
    // @TODO: Why would we want this?

    // const day = parseDate(date);
    // const minDate = parseDate(this.props.minDate);
    // const maxDate = parseDate(this.props.maxDate);
    // if (!(minDate && !dateutils.isGTE(day, minDate)) && !(maxDate && !dateutils.isLTE(day, maxDate))) {
    //   const shouldUpdateMonth = this.props.disableMonthChange === undefined || !this.props.disableMonthChange;
    //   if (shouldUpdateMonth) {
    //     this.updateMonth(day);
    //   }
    //   if (interaction) {
    //     interaction(xdateToData(day));
    //   }
    // }
  }

  pressDay(date) {
    this._handleDayInteraction(date, this.props.onDayPress);
  }

  longPressDay(date) {
    this._handleDayInteraction(date, this.props.onDayLongPress);
  }

  addMonth = (count) => {
    this.updateMonth(this.state.currentMonth.clone().addMonths(count, true));
  }

  isDateNotInTheRange = (minDate, maxDate, date, locale) => {
    if (
      locale === 'zodiac'
      &&
      minDate.getMonth() === 11
      &&
      date.getMonth() === 11
      &&
      date.getFullYear() === maxDate.getFullYear()
    ) {
      date.setFullYear(date.getFullYear() - 1)

      const days = dateutils.sign(date, this.props.transits)

      return !days.find((signDate) => {
        return (
          signDate.getMonth() === date.getMonth() && signDate.getDate() === date.getDate()
        )
      })
    } else {
      return (minDate && !dateutils.isGTE(date, minDate)) || (maxDate && !dateutils.isLTE(date, maxDate));
    }
  }

  getAccessibilityLabel = (state, day) => {
    const today = XDate.locales[XDate.defaultLocale].today;
    const formatAccessibilityLabel = XDate.locales[XDate.defaultLocale].formatAccessibilityLabel;
    const isToday = state === 'today';
    const markingLabel = this.getDateMarking(day);

    if (formatAccessibilityLabel) {
      return `${isToday ? today : ''} ${day.toString(formatAccessibilityLabel)} ${markingLabel}`;
    }

    return `${isToday ? 'today' : ''} ${day.toString('dddd d MMMM yyyy')} ${markingLabel}`;
  }


  renderDay(day, id) {
    let minDate
    let maxDate
    const days = dateutils.sign(this.state.currentMonth, this.props.transits)

    if (this.props.locale === 'zodiac') {
      const days = dateutils.sign(this.state.currentMonth, this.props.transits)

      minDate = parseDate(days[0])
      maxDate = parseDate(days[days.length - 1])
    } else {
      minDate = parseDate(this.props.minDate);
      maxDate = parseDate(this.props.maxDate);
    }

    let state = '';
    if (this.props.disabledByDefault) {
      // if (day.getMonth() === 11) {
        console.log(day, 'disabled-1')
      // }
      state = 'disabled';
    } else if (this.isDateNotInTheRange(minDate, maxDate, day, this.props.locale)) {
      // if (day.getMonth() === 11) {
        // console.log('disabled-2', day, minDate, maxDate, day)
      // }
      state = 'disabled';
    } else if (this.props.locale === 'zodiac' && !dateutils.sameSign(day, this.state.currentMonth, this.props.transits)) {
      // if (day.getMonth() === 11) {
        // console.log(day, 'disabled-3', day, this.state.currentMonth)
      // }
      state = 'disabled';
    } else if (this.props.locale !== 'zodiac' && !dateutils.sameMonth(day, this.state.currentMonth)) {
      // if (day.getMonth() === 11) {
        // console.log(day, 'disabled-4')
      // }
      state = 'disabled';
    } else if (dateutils.sameDate(day, XDate(), this.props.locale, this.props.transits)) {
      state = 'today';
    }

    if (!dateutils.sameMonth(day, this.state.currentMonth) && this.props.hideExtraDays) {
      return (<View key={id} style={{flex: 1}}/>);
    }

    const DayComp = this.getDayComponent();
    const date = day.getDate();
    const dateAsObject = xdateToData(day);
    const accessibilityLabel = this.getAccessibilityLabel(state, day);

    return (
      <View style={{flex: 1, alignItems: 'center'}} key={id}>
        <DayComp
          testID={`${SELECT_DATE_SLOT}-${dateAsObject.dateString}`}
          state={state}
          theme={this.props.theme}
          onPress={this.pressDay}
          onLongPress={this.longPressDay}
          date={dateAsObject}
          marking={this.getDateMarking(day)}
          accessibilityLabel={accessibilityLabel}
          disableAllTouchEventsForDisabledDays={this.props.disableAllTouchEventsForDisabledDays}
          locale={this.props.locale}
          zodiacMonthMarkerStyle={this.props.zodiacMonthMarkerStyle}
          zodiacMonthMarkerTextStyle={this.props.zodiacMonthMarkerTextStyle}
        >
          {date}
        </DayComp>
      </View>
    );
  }

  getMarkingLabel(day) {
    let label = '';
    const marking = this.getDateMarking(day);

    if (marking.accessibilityLabel) {
      return marking.accessibilityLabel;
    }

    if (marking.selected) {
      label += 'selected ';
      if (!marking.marked) {
        label += 'You have no entries for this day ';
      }
    }
    if (marking.marked) {
      label += 'You have entries for this day ';
    }
    if (marking.startingDay) {
      label += 'period start ';
    }
    if (marking.endingDay) {
      label += 'period end ';
    }
    if (marking.disabled || marking.disableTouchEvent) {
      label += 'disabled ';
    }
    return label;
  }

  getDayComponent() {
    if (this.props.dayComponent) {
      return this.props.dayComponent;
    }

    switch (this.props.markingType) {
    case 'period':
      return UnitDay;
    case 'multi-dot':
      return MultiDotDay;
    case 'multi-period':
      return MultiPeriodDay;
    case 'custom':
      return SingleDay;
    default:
      return Day;
    }
  }

  getDateMarking(day) {
    if (!this.props.markedDates) {
      return false;
    }

    const dates = this.props.markedDates[day.toString('yyyy-MM-dd')] || EmptyArray;
    if (dates.length || dates) {
      return dates;
    } else {
      return false;
    }
  }

  onSwipe = (gestureName) => {
    const {SWIPE_UP, SWIPE_DOWN, SWIPE_LEFT, SWIPE_RIGHT} = swipeDirections;

    switch (gestureName) {
    case SWIPE_UP:
    case SWIPE_DOWN:
      break;
    case SWIPE_LEFT:
      this.onSwipeLeft();
      break;
    case SWIPE_RIGHT:
      this.onSwipeRight();
      break;
    }
  }

  onSwipeLeft = () => {
    this.header.onPressRight();
  }

  onSwipeRight = () => {
    this.header.onPressLeft();
  }

  renderWeekNumber(weekNumber) {
    return (
      <View style={{flex: 1, alignItems: 'center'}} key={`week-container-${weekNumber}`}>
        <Day
          key={`week-${weekNumber}`}
          theme={this.props.theme}
          marking={{disableTouchEvent: true}}
          state='disabled'
        >
          {weekNumber}
        </Day>
      </View>
    );
  }

  renderWeek(days, id) {
    const week = [];
    days.forEach((day, id2) => {
      week.push(this.renderDay(day, id2));
    }, this);

    if (this.props.showWeekNumbers) {
      week.unshift(this.renderWeekNumber(days[days.length - 1].getWeek()));
    }

    return (<View style={this.style.week} key={id}>{week}</View>);
  }

  render() {
    const {currentMonth} = this.state;
    const {firstDay, showSixWeeks, hideExtraDays, enableSwipeMonths, locale} = this.props;
    const shouldShowSixWeeks = showSixWeeks && !hideExtraDays;
    const signName = dateutils.getSignName(this.props.current, this.props.transits)
    const days = dateutils.page(currentMonth, firstDay, shouldShowSixWeeks, locale, this.props.transits);

    const weeks = [];
    while (days.length) {
      weeks.push(this.renderWeek(days.splice(0, 7), weeks.length));
    }

    let indicator;
    const current = parseDate(this.props.current);
    if (current) {
      const lastMonthOfDay = current.clone().addMonths(1, true).setDate(1).addDays(-1).toString('yyyy-MM-dd');
      if (this.props.displayLoadingIndicator &&
        !(this.props.markedDates && this.props.markedDates[lastMonthOfDay])) {
        indicator = true;
      }
    }

    const GestureComponent = enableSwipeMonths ? GestureRecognizer : View;
    const gestureProps = enableSwipeMonths ? {onSwipe: (direction, state) => this.onSwipe(direction, state)} : {};

    const headerProps = {
      testID: this.props.testID,
      ref: c => this.header = c,
      style: this.props.headerStyle,
      theme: this.props.theme,
      hideArrows: this.props.hideArrows,
      month: this.state.currentMonth,
      addMonth: this.addMonth,
      showIndicator: indicator,
      firstDay: this.props.firstDay,
      showSixWeeks: this.props.showSixWeeks,
      renderArrow: this.props.renderArrow,
      monthFormat: this.props.monthFormat,
      hideDayNames: this.props.hideDayNames,
      weekNumbers: this.props.showWeekNumbers,
      onPressArrowLeft: this.props.onPressArrowLeft,
      onPressArrowRight: this.props.onPressArrowRight,
      webAriaLevel: this.props.webAriaLevel,
      disableArrowLeft: this.props.disableArrowLeft,
      disableArrowRight: this.props.disableArrowRight,
      disabledDaysIndexes: this.props.disabledDaysIndexes,
      renderHeader: this.props.renderHeader,
      transits: this.props.transits,
    };
    const CustomHeader = this.props.customHeader;

    return (
      <GestureComponent {...gestureProps}>
        <View
          style={[this.style.container, this.props.style]}
          accessibilityElementsHidden={this.props.accessibilityElementsHidden} // iOS
          importantForAccessibility={this.props.importantForAccessibility} // Android
        >
          {this.props.onPressCalendar ? (
            <TouchableWithoutFeedback
              onPress={() => this.props.onPressCalendar({
                ...this.props,
                currentMonth,
              })}
            >
              <View
                style={this.style.calendarButton}
              />
            </TouchableWithoutFeedback>
          ) : null}

          { CustomHeader
            ? <CustomHeader {...headerProps}/>
            : <CalendarHeader {...headerProps}/>
          }
          <View style={this.style.monthView}>{weeks}</View>
        </View>
      </GestureComponent>
    );
  }
}

export default Calendar;
