/* 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";

/**
 * Initialize the Calendar and generate nodes for week headers and days, and
 * attach event listeners.
 *
 * @param {Object} options
 *        {
 *          {Number} calViewSize: Number of days to appear on a calendar view
 *          {Function} getDayString: Transform day number to string
 *          {Function} getWeekHeaderString: Transform day of week number to string
 *          {Function} setSelection: Set selection for dateKeeper
 *        }
 * @param {Object} context
 *        {
 *          {DOMElement} weekHeader
 *          {DOMElement} daysView
 *        }
 */
function Calendar(options, context) {
  const DAYS_IN_A_WEEK = 7;

  this.context = context;
  this.state = {
    days: [],
    weekHeaders: [],
    setSelection: options.setSelection,
    getDayString: options.getDayString,
    getWeekHeaderString: options.getWeekHeaderString
  };
  this.elements = {
    weekHeaders: this._generateNodes(DAYS_IN_A_WEEK, context.weekHeader),
    daysView: this._generateNodes(options.calViewSize, context.daysView)
  };

  this._attachEventListeners();
}

{
  Calendar.prototype = {

    /**
     * Set new properties and render them.
     *
     * @param {Object} props
     *        {
     *          {Boolean} isVisible: Whether or not the calendar is in view
     *          {Array<Object>} days: Data for days
     *          {
     *            {Date} dateObj
     *            {Number} content
     *            {Array<String>} classNames
     *            {Boolean} enabled
     *          }
     *          {Array<Object>} weekHeaders: Data for weekHeaders
     *          {
     *            {Number} content
     *            {Array<String>} classNames
     *          }
     *        }
     */
    setProps(props) {
      if (props.isVisible) {
        // Transform the days and weekHeaders array for rendering
        const days = props.days.map(({ dateObj, content, classNames, enabled }) => {
          return {
          dateObj,
          textContent: this.state.getDayString(content),
          className: classNames.join(" "),
          enabled
          };
        });
        const weekHeaders = props.weekHeaders.map(({ content, classNames }) => {
          return {
            textContent: this.state.getWeekHeaderString(content),
            className: classNames.join(" ")
          };
        });
        // Update the DOM nodes states
        this._render({
          elements: this.elements.daysView,
          items: days,
          prevState: this.state.days
        });
        this._render({
          elements: this.elements.weekHeaders,
          items: weekHeaders,
          prevState: this.state.weekHeaders,
        });
        // Update the state to current
        this.state.days = days;
        this.state.weekHeaders = weekHeaders;
      }
    },

    /**
     * Render the items onto the DOM nodes
     * @param  {Object}
     *         {
     *           {Array<DOMElement>} elements
     *           {Array<Object>} items
     *           {Array<Object>} prevState: state of items from last render
     *         }
     */
    _render({ elements, items, prevState }) {
      for (let i = 0, l = items.length; i < l; i++) {
        let el = elements[i];

        // Check if state from last render has changed, if so, update the elements
        if (!prevState[i] || prevState[i].textContent != items[i].textContent) {
          el.textContent = items[i].textContent;
        }
        if (!prevState[i] || prevState[i].className != items[i].className) {
          el.className = items[i].className;
        }
      }
    },

    /**
     * Generate DOM nodes
     *
     * @param  {Number} size: Number of nodes to generate
     * @param  {DOMElement} context: Element to append the nodes to
     * @return {Array<DOMElement>}
     */
    _generateNodes(size, context) {
      let frag = document.createDocumentFragment();
      let refs = [];

      for (let i = 0; i < size; i++) {
        let el = document.createElement("div");
        el.dataset.id = i;
        refs.push(el);
        frag.appendChild(el);
      }
      context.appendChild(frag);

      return refs;
    },

    /**
     * Handle events
     * @param  {DOMEvent} event
     */
    handleEvent(event) {
      switch (event.type) {
        case "click": {
          if (event.target.parentNode == this.context.daysView) {
            let targetId = event.target.dataset.id;
            let targetObj = this.state.days[targetId];
            if (targetObj.enabled) {
              this.state.setSelection(targetObj.dateObj);
            }
          }
          break;
        }
      }
    },

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