/**
 * A month / year picker component. This class is used by the 
 * {@link Ext.picker.Date Date picker} to allow browsing and selection of year and 
 * months combinations, but may also be used as a standalone component.
 *
 *     @example
 *     Ext.create({
 *         xtype: 'monthpicker',
 *         renderTo: document.body,
 *         value: new Date(),
 *         onSelect: function() {
 *             Ext.Msg.alert('Selected', this.getValue());
 *         },
 *         listeners: {
 *             okclick: 'onSelect',
 *             monthdblclick: 'onSelect',
 *             yeardblclick: 'onSelect',
 *             cancelclick: function () {
 *                 this.setValue(new Date());
 *             }
 *         }
 *     });
 */
Ext.define('Ext.picker.Month', {
    extend: 'Ext.Component',
    alias: 'widget.monthpicker',
    alternateClassName: 'Ext.MonthPicker',
    
    requires: [
        'Ext.XTemplate',
        'Ext.util.ClickRepeater',
        'Ext.Date',
        'Ext.button.Button'
    ],
    
    isMonthPicker: true,
    
    /**
     * @property focusable
     * @inheritdoc
     */
    focusable: true,
 
    /**
     * @cfg childEls
     * @inheritdoc
     */
    childEls: [
        'bodyEl', 'prevEl', 'nextEl', 'monthEl', 'yearEl', 'buttons'
    ],
 
    /* eslint-disable indent, max-len */
    /**
     * @cfg renderTpl
     * @inheritdoc
     */
    renderTpl: [
        '<div id="{id}-bodyEl" data-ref="bodyEl" class="{baseCls}-body">',
          '<div id="{id}-monthEl" data-ref="monthEl" class="{baseCls}-months">',
              '<tpl for="months">',
                  '<div class="{parent.baseCls}-item {parent.baseCls}-month">',
                      '<a style="{parent.monthStyle}" role="button" hidefocus="on" class="{parent.baseCls}-item-inner">{.}</a>',
                  '</div>',
              '</tpl>',
          '</div>',
          '<div id="{id}-yearEl" data-ref="yearEl" class="{baseCls}-years">',
              '<div class="{baseCls}-yearnav">',
                  '<div class="{baseCls}-yearnav-button-ct">',
                      '<a id="{id}-prevEl" data-ref="prevEl" class="{baseCls}-yearnav-button {baseCls}-yearnav-prev" hidefocus="on" role="button"></a>',
                  '</div>',
                  '<div class="{baseCls}-yearnav-button-ct">',
                      '<a id="{id}-nextEl" data-ref="nextEl" class="{baseCls}-yearnav-button {baseCls}-yearnav-next" hidefocus="on" role="button"></a>',
                  '</div>',
              '</div>',
              '<tpl for="years">',
                  '<div class="{parent.baseCls}-item {parent.baseCls}-year">',
                      '<a hidefocus="on" class="{parent.baseCls}-item-inner" role="button">{.}</a>',
                  '</div>',
              '</tpl>',
          '</div>',
          '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
          '<tpl if="showButtons">',
              '<div id="{id}-buttons" data-ref="buttons" class="{baseCls}-buttons">{%',
                  'var me=values.$comp, okBtn=me.okBtn, cancelBtn=me.cancelBtn;',
                  'okBtn.ownerLayout = cancelBtn.ownerLayout = me.componentLayout;',
                  'okBtn.ownerCt = cancelBtn.ownerCt = me;',
                  'Ext.DomHelper.generateMarkup(okBtn.getRenderTree(), out);',
                  'Ext.DomHelper.generateMarkup(cancelBtn.getRenderTree(), out);',
              '%}</div>',
          '</tpl>',
        '</div>'
    ],
    /* eslint-enable indent, max-len */
 
    /**
     * @cfg {String} okText
     * The text to display on the OK button.
     * @locale
     */
    okText: 'OK',
 
    /**
     * @cfg {String} cancelText
     * The text to display on the Cancel button.
     * @locale
     */
    cancelText: 'Cancel',
 
    /**
     * @cfg {String} baseCls
     * The base CSS class to apply to the picker element.
     */
    baseCls: Ext.baseCSSPrefix + 'monthpicker',
 
    /**
     * @cfg {Boolean} showButtons
     * True to show ok and cancel buttons below the picker.
     */
    showButtons: true,
 
    /**
     * @property {String} selectedCls
     * The class to be added to selected items in the picker.
     * @readonly
     */
 
    /**
     * @cfg {Date/Number[]} value
     * The default value to set. See {@link #setValue}
     */
 
    /**
     * @cfg {String} footerButtonUI
     * The {@link Ext.button.Button#ui} to use for the month picker's footer buttons.
     */
    footerButtonUI: 'default',
 
    measureWidth: 35,
    measureMaxHeight: 20,
 
    // used when attached to date picker which isnt showing buttons
    smallCls: Ext.baseCSSPrefix + 'monthpicker-small',
 
    /**
     * @private
     */
    totalYears: 10,
    yearOffset: 5, // 10 years in total, 2 per row
    monthOffset: 6, // 12 months, 2 per row
    
    /**
     * @cfg alignOnScroll
     * @inheritdoc
     */
    alignOnScroll: false,
 
    /**
     * @event cancelclick
     * Fires when the cancel button is pressed.
     * @param {Ext.picker.Month} this 
     */
 
    /**
     * @event monthclick
     * Fires when a month is clicked.
     * @param {Ext.picker.Month} this 
     * @param {Array} value The current value
     */
 
    /**
     * @event monthdblclick
     * Fires when a month is clicked.
     * @param {Ext.picker.Month} this 
     * @param {Array} value The current value
     */
 
    /**
     * @event okclick
     * Fires when the ok button is pressed.
     * @param {Ext.picker.Month} this 
     * @param {Array} value The current value
     */
 
    /**
     * @event select
     * Fires when a month/year is selected.
     * @param {Ext.picker.Month} this 
     * @param {Array} value The current value
     */
 
    /**
     * @event yearclick
     * Fires when a year is clicked.
     * @param {Ext.picker.Month} this 
     * @param {Array} value The current value
     */
 
    /**
     * @event yeardblclick
     * Fires when a year is clicked.
     * @param {Ext.picker.Month} this 
     * @param {Array} value The current value
     */
 
    /**
     * @method initComponent
     * @inheritdoc
     * @private
     */
    initComponent: function() {
        var me = this;
 
        me.selectedCls = me.baseCls + '-selected';
 
        if (me.small) {
            me.addCls(me.smallCls);
        }
 
        me.setValue(me.value);
        me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
 
        if (me.showButtons) {
            me.okBtn = new Ext.button.Button({
                ui: me.footerButtonUI,
                text: me.okText,
                handler: me.onOkClick,
                scope: me
            });
            me.cancelBtn = new Ext.button.Button({
                ui: me.footerButtonUI,
                text: me.cancelText,
                handler: me.onCancelClick,
                scope: me
            });
        }
 
        me.callParent();
    },
 
    /**
     * @method beforeRender
     * @inheritdoc
     * @private
     */
    beforeRender: function() {
        var me = this,
            i = 0,
            months = [],
            shortName = Ext.Date.getShortMonthName,
            monthLen = me.monthOffset,
            margin = me.monthMargin,
            style = '';
 
        if (me.padding && !me.width) {
            me.cacheWidth();
        }
 
        me.callParent();
 
        for (; i < monthLen; ++i) {
            months.push(shortName(i), shortName(+ monthLen));
        }
        
        if (Ext.isDefined(margin)) {
            style = 'margin: 0 ' + margin + 'px;';
        }
 
        Ext.apply(me.renderData, {
            months: months,
            years: me.getYears(),
            showButtons: me.showButtons,
            monthStyle: style
        });
    },
 
    cacheWidth: function() {
        var me = this,
            padding = me.parseBox(me.padding),
            widthEl = Ext.getBody().createChild({
                cls: me.baseCls + ' ' + me.borderBoxCls,
                style: 'position:absolute;top:-1000px;left:-1000px;',
                html: '&nbsp;' // required for opera 11.64 to measure a width
            });
 
        me.self.prototype.width = widthEl.getWidth() + padding.left + padding.right;
        widthEl.destroy();
    },
 
    /**
     * @method afterRender
     * @inheritdoc
     * @private
     */
    afterRender: function() {
        var me = this,
            body = me.bodyEl;
 
        me.callParent();
        
        // Month picker is not focusable and essentially is pointer only thing.
        // Clicking on it will focus the document body, which may disrupt the state
        // of the floating parent such as Date picker or a menu, and cause it to hide.
        // To work around that, we stop the mousedown events completely.
        if (me.up('[floating=true]')) {
            me.el.on('mousedown', me.onElClick, me, { translate: false });
        }
 
        body.on({
            scope: me,
            click: 'onBodyClick',
            dblclick: 'onBodyClick'
        });
 
        // keep a reference to the year/month elements since we'll be re-using them
        me.years = body.select('.' + me.baseCls + '-year a');
        me.months = body.select('.' + me.baseCls + '-month a');
 
        me.backRepeater = new Ext.util.ClickRepeater(me.prevEl, {
            handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
        });
 
        me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
        me.nextRepeater = new Ext.util.ClickRepeater(me.nextEl, {
            handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
        });
        me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
        me.updateBody();
        
        if (!Ext.isDefined(me.monthMargin)) {
            Ext.picker.Month.prototype.monthMargin = me.calculateMonthMargin();
        }
    },
    
    calculateMonthMargin: function() {
        // We use this method for locales where the short month name
        // may be longer than we see in English. For example in the 
        // zh_TW locale the month ends up spanning lines, so we loosen
        // the margins to get some extra space
        var me = this,
            months = me.months,
            first = months.first(),
            itemMargin = first.getMargin('l');
            
        while (itemMargin && me.getLargest() > me.measureMaxHeight) {
            --itemMargin;
            months.setStyle('margin', '' + itemMargin + 'px');
        }
 
        return itemMargin;
    },
    
    getLargest: function(months) {
        var largest = 0;
 
        this.months.each(function(item) {
            var h = item.getHeight();
 
            if (> largest) {
                largest = h;
            }
        });
 
        return largest;
        
    },
 
    /**
     * Set the value for the picker.
     * @param {Date/Number[]} value The value to set. It can be a Date object,
     * where the month/year will be extracted, or it can be an array, with the month
     * as the first index and the year as the second.
     * @return {Ext.picker.Month} this
     */
    setValue: function(value) {
        var me = this,
            active = me.activeYear,
            year;
 
        if (!value) {
            me.value = [null, null];
        }
        else if (Ext.isDate(value)) {
            me.value = [value.getMonth(), value.getFullYear()];
        }
        else {
            me.value = [value[0], value[1]];
        }
 
        if (me.rendered) {
            year = me.value[1];
 
            if (year !== null) {
                if ((year < active || year > active + me.yearOffset)) {
                    me.activeYear = year - me.yearOffset + 1;
                }
            }
 
            me.updateBody();
        }
 
        return me;
    },
 
    /**
     * Gets the selected value. It is returned as an array [month, year]. It may
     * be a partial value, for example [null, 2010]. The month is returned as
     * 0 based.
     * @return {Number[]} The selected value
     */
    getValue: function() {
        return this.value;
    },
 
    /**
     * Checks whether the picker has a selection
     * @return {Boolean} Returns true if both a month and year have been selected
     */
    hasSelection: function() {
        var value = this.value;
 
        return value[0] !== null && value[1] !== null;
    },
 
    /**
     * Get an array of years to be pushed in the template. It is not in strict
     * numerical order because we want to show them in columns.
     * @private
     * @return {Number[]} An array of years
     */
    getYears: function() {
        var me = this,
            offset = me.yearOffset,
            start = me.activeYear, // put the "active" year on the left
            end = start + offset,
            i = start,
            years = [];
 
        for (; i < end; ++i) {
            years.push(i, i + offset);
        }
 
        return years;
    },
 
    /**
     * Update the years in the body based on any change
     * @private
     */
    updateBody: function() {
        var me = this,
            years = me.years,
            months = me.months,
            yearNumbers = me.getYears(),
            cls = me.selectedCls,
            value = me.getYear(null),
            month = me.value[0],
            monthOffset = me.monthOffset,
            year,
            yearItems, y, yLen, el;
 
        if (me.rendered) {
            years.removeCls(cls);
            months.removeCls(cls);
 
            yearItems = years.elements;
            yLen = yearItems.length;
 
            for (= 0; y < yLen; y++) {
                el = Ext.fly(yearItems[y]);
 
                year = yearNumbers[y];
                el.dom.innerHTML = year;
 
                if (year === value) {
                    el.addCls(cls);
                }
            }
 
            if (month !== null) {
                if (month < monthOffset) {
                    month = month * 2;
                }
                else {
                    month = (month - monthOffset) * 2 + 1;
                }
 
                months.item(month).addCls(cls);
            }
        }
    },
 
    /**
     * Gets the current year value, or the default.
     * @private
     * @param {Number} defaultValue The default value to use if the year is not defined.
     * @param {Number} offset A number to offset the value by
     * @return {Number} The year value
     */
    getYear: function(defaultValue, offset) {
        var year = this.value[1];
 
        offset = offset || 0;
 
        return year === null ? defaultValue : year + offset;
    },
    
    onElClick: function(e) {
        e.stopEvent();
    },
 
    /**
     * React to clicks on the body
     * @private
     */
    onBodyClick: function(e, t) {
        var me = this,
            isDouble = e.type === 'dblclick';
 
        if (e.getTarget('.' + me.baseCls + '-month')) {
            e.stopEvent();
            me.onMonthClick(t, isDouble);
        }
        else if (e.getTarget('.' + me.baseCls + '-year')) {
            e.stopEvent();
            me.onYearClick(t, isDouble);
        }
    },
 
    /**
     * Modify the year display by passing an offset.
     * @param {Number} [offset=10] The offset to move by.
     */
    adjustYear: function(offset) {
        if (typeof offset !== 'number') {
            offset = this.totalYears;
        }
 
        this.activeYear += offset;
        this.updateBody();
    },
 
    /**
     * React to the ok button being pressed
     * @private
     */
    onOkClick: function() {
        this.fireEvent('okclick', this, this.value);
    },
 
    /**
     * React to the cancel button being pressed
     * @private
     */
    onCancelClick: function() {
        this.fireEvent('cancelclick', this);
    },
 
    /**
     * React to a month being clicked
     * @private
     * @param {HTMLElement} target The element that was clicked
     * @param {Boolean} isDouble True if the event was a doubleclick
     */
    onMonthClick: function(target, isDouble) {
        var me = this;
 
        me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
        me.updateBody();
        me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
        me.fireEvent('select', me, me.value);
    },
 
    /**
     * React to a year being clicked
     * @private
     * @param {HTMLElement} target The element that was clicked
     * @param {Boolean} isDouble True if the event was a doubleclick
     */
    onYearClick: function(target, isDouble) {
        var me = this;
 
        me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
        me.updateBody();
        me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
        me.fireEvent('select', me, me.value);
 
    },
 
    /**
     * Returns an offsetted number based on the position in the collection.
     * Since our collections aren't numerically ordered, this function helps
     * to normalize those differences.
     * @private
     * @param {Object} index 
     * @param {Object} offset 
     * @return {Number} The correctly offsetted number
     */
    resolveOffset: function(index, offset) {
        if (index % 2 === 0) {
            return (index / 2);
        }
        else {
            return offset + Math.floor(index / 2);
        }
    },
 
    doDestroy: function() {
        Ext.destroy(this.backRepeater, this.nextRepeater, this.okBtn, this.cancelBtn);
        
        this.callParent();
    },
 
    privates: {
        // Do the job of a container layout at this point even though we are not a Container.
        // TODO: Refactor as a Container.
        finishRenderChildren: function() {
            var me = this;
 
            this.callParent(arguments);
 
            if (this.showButtons) {
                me.okBtn.finishRender();
                me.cancelBtn.finishRender();
            }
        }
    }
});