/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

function DatePicker(context) {
  this.context = context;
  this._attachEventListeners();
}

{
  const CAL_VIEW_SIZE = 42;

  DatePicker.prototype = {
    /**
     * Initializes the date picker. Set the default states and properties.
     * @param  {Object} props
     *         {
     *           {Number} year [optional]
     *           {Number} month [optional]
     *           {Number} date [optional]
     *           {Number} min
     *           {Number} max
     *           {Number} step
     *           {Number} stepBase
     *           {Number} firstDayOfWeek
     *           {Array<Number>} weekends
     *           {Array<String>} monthStrings
     *           {Array<String>} weekdayStrings
     *           {String} locale [optional]: User preferred locale
     *         }
     */
    init(props = {}) {
      this.props = props;
      this._setDefaultState();
      this._createComponents();
      this._update();
      document.dispatchEvent(new CustomEvent("PickerReady"));
    },

    /*
     * Set initial date picker states.
     */
    _setDefaultState() {
      const { year, month, day, min, max, step, stepBase, firstDayOfWeek, weekends,
              monthStrings, weekdayStrings, locale } = this.props;
      const dateKeeper = new DateKeeper({
        year, month, day, min, max, step, stepBase, firstDayOfWeek, weekends,
        calViewSize: CAL_VIEW_SIZE
      });

      this.state = {
        dateKeeper,
        locale,
        isMonthPickerVisible: false,
        getDayString: day => day ? new Intl.NumberFormat(locale).format(day) : "",
        getWeekHeaderString: weekday => weekdayStrings[weekday],
        getMonthString: month => monthStrings[month],
        setSelection: date => {
          dateKeeper.setSelection({
            year: date.getUTCFullYear(),
            month: date.getUTCMonth(),
            day: date.getUTCDate(),
          });
          this._update();
          this._dispatchState();
          this._closePopup();
        },
        setYear: year => {
          dateKeeper.setYear(year);
          dateKeeper.setSelection({
            year,
            month: dateKeeper.selection.month,
            day: dateKeeper.selection.day,
          });
          this._update();
          this._dispatchState();
        },
        setMonth: month => {
          dateKeeper.setMonth(month);
          dateKeeper.setSelection({
            year: dateKeeper.selection.year,
            month,
            day: dateKeeper.selection.day,
          });
          this._update();
          this._dispatchState();
        },
        toggleMonthPicker: () => {
          this.state.isMonthPickerVisible = !this.state.isMonthPickerVisible;
          this._update();
        }
      };
    },

    /**
     * Initalize the date picker components.
     */
    _createComponents() {
      this.components = {
        calendar: new Calendar({
          calViewSize: CAL_VIEW_SIZE,
          locale: this.state.locale,
          setSelection: this.state.setSelection,
          getDayString: this.state.getDayString,
          getWeekHeaderString: this.state.getWeekHeaderString
        }, {
          weekHeader: this.context.weekHeader,
          daysView: this.context.daysView
        }),
        monthYear: new MonthYear({
          setYear: this.state.setYear,
          setMonth: this.state.setMonth,
          getMonthString: this.state.getMonthString,
          locale: this.state.locale
        }, {
          monthYear: this.context.monthYear,
          monthYearView: this.context.monthYearView
        })
      };
    },

    /**
     * Update date picker and its components.
     */
    _update(options = {}) {
      const { dateKeeper, isMonthPickerVisible } = this.state;

      if (isMonthPickerVisible) {
        this.state.months = dateKeeper.getMonths();
        this.state.years = dateKeeper.getYears();
      } else {
        this.state.days = dateKeeper.getDays();
      }

      this.components.monthYear.setProps({
        isVisible: isMonthPickerVisible,
        dateObj: dateKeeper.state.dateObj,
        months: this.state.months,
        years: this.state.years,
        toggleMonthPicker: this.state.toggleMonthPicker,
        noSmoothScroll: options.noSmoothScroll
      });
      this.components.calendar.setProps({
        isVisible: !isMonthPickerVisible,
        days: this.state.days,
        weekHeaders: dateKeeper.state.weekHeaders
      });

      isMonthPickerVisible ?
        this.context.monthYearView.classList.remove("hidden") :
        this.context.monthYearView.classList.add("hidden");
    },

    /**
     * Use postMessage to close the picker.
     */
    _closePopup() {
      window.postMessage({
        name: "ClosePopup"
      }, "*");
    },

    /**
     * Use postMessage to pass the state of picker to the panel.
     */
    _dispatchState() {
      const { year, month, day } = this.state.dateKeeper.selection;
      // The panel is listening to window for postMessage event, so we
      // do postMessage to itself to send data to input boxes.
      window.postMessage({
        name: "PickerPopupChanged",
        detail: {
          year,
          month,
          day,
        }
      }, "*");
    },

    /**
     * Attach event listeners
     */
    _attachEventListeners() {
      window.addEventListener("message", this);
      document.addEventListener("mouseup", this, { passive: true });
      document.addEventListener("mousedown", this);
    },

    /**
     * Handle events.
     *
     * @param  {Event} event
     */
    handleEvent(event) {
      switch (event.type) {
        case "message": {
          this.handleMessage(event);
          break;
        }
        case "mousedown": {
          // Use preventDefault to keep focus on input boxes
          event.preventDefault();
          event.target.setCapture();

          if (event.target == this.context.buttonLeft) {
            event.target.classList.add("active");
            this.state.dateKeeper.setMonthByOffset(-1);
            this._update();
          } else if (event.target == this.context.buttonRight) {
            event.target.classList.add("active");
            this.state.dateKeeper.setMonthByOffset(1);
            this._update();
          }
          break;
        }
        case "mouseup": {
          if (event.target == this.context.buttonLeft || event.target == this.context.buttonRight) {
            event.target.classList.remove("active");
          }

        }
      }
    },

    /**
     * Handle postMessage events.
     *
     * @param {Event} event
     */
    handleMessage(event) {
      switch (event.data.name) {
        case "PickerSetValue": {
          this.set(event.data.detail);
          break;
        }
        case "PickerInit": {
          this.init(event.data.detail);
          break;
        }
      }
    },

    /**
     * Set the date state and update the components with the new state.
     *
     * @param {Object} dateState
     *        {
     *          {Number} year [optional]
     *          {Number} month [optional]
     *          {Number} date [optional]
     *        }
     */
    set({ year, month, day }) {
      const { dateKeeper } = this.state;

      dateKeeper.setCalendarMonth({
        year, month
      });
      dateKeeper.setSelection({
        year, month, day
      });
      this._update({ noSmoothScroll: true });
    }
  };

  /**
   * MonthYear is a component that handles the month & year spinners
   *
   * @param {Object} options
   *        {
   *          {String} locale
   *          {Function} setYear
   *          {Function} setMonth
   *          {Function} getMonthString
   *        }
   * @param {DOMElement} context
   */
  function MonthYear(options, context) {
    const spinnerSize = 5;
    const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric",
                                                                 timeZone: "UTC" }).format;
    const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric",
                                                                 month: "long",
                                                                 timeZone: "UTC" }).format;
    this.context = context;
    this.state = { dateFormat };
    this.props = {};
    this.components = {
      month: new Spinner({
        setValue: month => {
          this.state.isMonthSet = true;
          options.setMonth(month);
        },
        getDisplayString: options.getMonthString,
        viewportSize: spinnerSize
      }, context.monthYearView),
      year: new Spinner({
        setValue: year => {
          this.state.isYearSet = true;
          options.setYear(year);
        },
        getDisplayString: year => yearFormat(new Date(new Date(0).setUTCFullYear(year))),
        viewportSize: spinnerSize
      }, context.monthYearView)
    };

    this._attachEventListeners();
  }

  MonthYear.prototype = {

    /**
     * Set new properties and pass them to components
     *
     * @param {Object} props
     *        {
     *          {Boolean} isVisible
     *          {Date} dateObj
     *          {Array<Object>} months
     *          {Array<Object>} years
     *          {Function} toggleMonthPicker
     *         }
     */
    setProps(props) {
      this.context.monthYear.textContent = this.state.dateFormat(props.dateObj);

      if (props.isVisible) {
        this.context.monthYear.classList.add("active");
        this.components.month.setState({
          value: props.dateObj.getUTCMonth(),
          items: props.months,
          isInfiniteScroll: true,
          isValueSet: this.state.isMonthSet,
          smoothScroll: !(this.state.firstOpened || props.noSmoothScroll)
        });
        this.components.year.setState({
          value: props.dateObj.getUTCFullYear(),
          items: props.years,
          isInfiniteScroll: false,
          isValueSet: this.state.isYearSet,
          smoothScroll: !(this.state.firstOpened || props.noSmoothScroll)
        });
        this.state.firstOpened = false;
      } else {
        this.context.monthYear.classList.remove("active");
        this.state.isMonthSet = false;
        this.state.isYearSet = false;
        this.state.firstOpened = true;
      }

      this.props = Object.assign(this.props, props);
    },

    /**
     * Handle events
     * @param  {DOMEvent} event
     */
    handleEvent(event) {
      switch (event.type) {
        case "click": {
          this.props.toggleMonthPicker();
          break;
        }
      }
    },

    /**
     * Attach event listener to monthYear button
     */
    _attachEventListeners() {
      this.context.monthYear.addEventListener("click", this);
    }
  };
}