
var PeriodSelect = new Class({

    Implements: [Options, Events],
    
    /* Period ID select element and form ID we're overriding */
    str_select_id: null,
    str_form_id: null,

    /*
       Lookup object for years, months and weeks (potentially) extracted from
       the periods select component
    
       obj_lookup: {
           2001: {
               1: {
                   6: JULIAN_ID,
                   13: JULIAN_ID,
                   20: JULIAN_ID,
                   27: JULIAN_ID
               },
               2: {
                   ...
           },
           2002: {
               ....
           }
       }
    */
    obj_lookup: {},
    
    /*
        Which date part components this period has
    */
    boo_has_years: false,
    boo_has_months: false,
    boo_has_weeks: false,
    
    /*
        Standard options class extension
    */
    options: {
    
        // form label strings for each select component
        obj_labels: {
            week: 'Week',
            month: 'Month',
            year: 'Year'
        },
        
        // ids used for the date components
        obj_ids: {
            week: 'period_week',
            month: 'period_month',
            year: 'period_year'
        },
        
        // element sizes
        obj_sizes: {
            week: 10,
            month: 10,
            year: 10
        },
        
        // booleans for whether the element is multi-selectable
        obj_multi: {
            week: false,
            month: false,
            year: false
        },
        
        // element div container classes
        obj_container_class: {
            week: 'form_block',
            month: 'form_block',
            year: 'form_block'
        },
        
        // element div id classes
        obj_container_id: {
            week: 'period_week_block',
            month: 'period_month_block',
            year: 'period_year_block'
        },
        
        obj_reverse_order: {
            week: null,
            month: null,
            year: false
        }
    },
    
    arr_month_formats: {
        0:{
            F: 'January',
            M: 'Jan'
        },
        1:{
            F: 'February',
            M: 'Feb'
        },
        2:{
            F: 'March',
            M: 'Mar'
        },
        3:{
            F: 'April',
            M: 'Apr'
        },
        4:{
            F: 'May',
            M: 'May'
        },
        5:{
            F: 'June',
            M: 'Jun'
        },
        6:{
            F: 'July',
            M: 'Jul'
        },
        7:{
            F: 'August',
            M: 'Aug'
        },
        8:{
            F: 'September',
            M: 'Sep'
        },
        9:{
            F: 'October',
            M: 'Oct'
        },
        10:{
            F: 'November',
            M: 'Nov'
        },
        11:{
            F: 'December',
            M: 'Dec'
        }
    },


    /**
     * Constructor
     *
     * Creates new form elements and populates them.
     *
     * @param str str_form_id  ID of the form
     * @param str str_periods_id  ID of the periods element to override
     * @param obj obj_options  Class options
     * @return void
     */
    initialize: function(str_form_id, str_periods_id, obj_options) {
    
        this.setOptions(obj_options);
        
        var elm_periods = $(str_periods_id);
        var obj_ids = this.options.obj_ids;

        this.str_select_id = str_periods_id;
        this.str_form_id = str_form_id;

        // initially hide the original and the label for it if it exists
        elm_periods.setStyle('display', 'none');
        $(str_form_id).getElement('label[for=' + str_periods_id + ']').setStyle('display', 'none');
        
        // inspect the element's class to see which components it has (years, months, weeks)
        this.boo_has_years = elm_periods.hasClass('year');
        this.boo_has_months = elm_periods.hasClass('month');
        this.boo_has_weeks = elm_periods.hasClass('week');
        
        // create new controls
        this._create_controls(str_periods_id);
        
        // add listener to the form
        $(str_form_id).addEvent('submit', this._event_submit.bind(this));
        
        // cache lookup of period strings to julian weeks
        var arr_options = elm_periods.getElements('option');
        var arr_selected = new Array();
        
        for(var i = 0, ilen = arr_options.length; i < ilen; i++) {
        
            // extract title and value properties
            var str_title = arr_options[i].getProperty('title');
            var str_value = arr_options[i].getProperty('value');
            
            if(!str_title || !str_value) {
                continue;
            }
            
            // extract year, month and week properties from the title
            var str_date_properties = str_title.split(/-/g);
            
            var int_year = str_date_properties[0].toInt();
            var int_month = str_date_properties[1].toInt();
            var int_week = str_date_properties[2].toInt();
            
            // if this item is selected, store the attributes so we can select
            // them again in the new format once the options have been populated
            if(arr_options[i].getProperty('selected')) { 
                arr_selected.push({year: int_year, month: int_month, week: int_week});
            }
            
            // add to cache being sure to check for existance of the necessary
            // properties
            if(!this.obj_lookup[int_year]) {
                this.obj_lookup[int_year] = {};
            }
            
            // store julian ID under a week object?
            if(this.boo_has_weeks) {
            
	            if(!this.obj_lookup[int_year][int_month]) {
	                this.obj_lookup[int_year][int_month] = {};
	            }
	            
	            // store julian week as the final value
                this.obj_lookup[int_year][int_month][int_week] = str_value;
	        }
	        else {
	        
	           this.obj_lookup[int_year][int_month] = str_value;
	        }
        }
         
        // set year values
        var arr_years = new Array();
        
        var arr_temp = new Array();
        for(var i in this.obj_lookup) {
            arr_temp.push(i);
        }
        
        // in reverse-chronological order?
        if(this.options.obj_reverse_order.year) {
            for(i = arr_temp.length-1; i >= 0; i--) {
                arr_years.push({id: arr_temp[i], name: arr_temp[i]});
            }
        } else {
            for(i = 0; i < arr_temp.length; i++) {
                arr_years.push({id: arr_temp[i], name: arr_temp[i]});
            }
        }
        
        this._set_select_options(this.options.obj_ids.year, arr_years);

        // set month values
        var arr_months = new Array(
            {id: 1, name: 'January'}, {id: 2, name: 'February'}, {id: 3, name: 'March'}, {id: 4, name: 'April'}, 
            {id: 5, name: 'May'}, {id: 6, name: 'June'}, {id: 7, name: 'July'}, {id: 8, name: 'August'}, 
            {id: 9, name: 'September'}, {id: 10, name: 'October'}, {id: 11, name: 'November'}, {id: 12, name: 'December'}
        );

        this._set_select_options(this.options.obj_ids.month, arr_months);
        
        // now reselect items in the new format that were present in the original
        for(var i = 0, ilen = arr_selected.length; i < ilen; i++) {

            var obj_selected = arr_selected[i];
            
            if(this.boo_has_years) {
                $(obj_ids.year).getElement('option[value=' + obj_selected.year + ']').selected = true;
            }
            
            if(this.boo_has_months) {
                $(obj_ids.month).getElement('option[value=' + obj_selected.month + ']').selected = true;
            }
            
            if(this.boo_has_weeks) {
            
	            // set appropriate weeks
	            var arr_weeks = new Array();
	
	            for(var i in this.obj_lookup[obj_selected.year][obj_selected.month]) {
	                arr_weeks.push({id: i, name: this._get_week_string(obj_selected.year, obj_selected.month, i)});
	            }
	            
	            // reverse weeks prior to adding (we need to do this so they're in sequential order)
	            arr_weeks.reverse();
	            
	            this._set_select_options(obj_ids.week, arr_weeks);
	        }
        }
        
        // fake a change event on the years to ensure everything is in a suitable state
        // post load time
        this._event_select_change({target: $(obj_ids.year)});
        
        // finally select the weeks (we need to do this outside the prior loop as we're overwriting
        // the weeks each time (potentically anyway - this is for multiple selection support)
        if(this.boo_has_weeks) {
        
            for(var i = 0, ilen = arr_selected.length; i < ilen; i++) {
                $(obj_ids.week).getElement('option[value=' + arr_selected[i].week + ']').selected = true;
            }
        }
        
        // remove original periods element
        elm_periods.dispose();
    },
    
    
    
    /**
     * Creates the select controls for years, months and/or weeks
     *
     * @param str str_periods_id  ID of the original periods select item
     * @return void
     */
    _create_controls: function(str_periods_id) {
    
        var obj_ids = this.options.obj_ids;
        var obj_labels = this.options.obj_labels;
        var obj_sizes = this.options.obj_sizes;
        var obj_container_id = this.options.obj_container_id;
        var obj_container_class = this.options.obj_container_class;
    
        // create week component
        if(this.boo_has_weeks) {
        
            var elm_container = new Element('div', {
                'class': obj_container_class.week,
                'id': obj_container_id.week
            });
            
            (new Element('label', {
                'for': obj_ids.week
            })).set('html', obj_labels.week).inject(elm_container, 'inside');
            
            (new Element('select', {
                id: obj_ids.week,
                name: obj_ids.week + '[]',
                size: obj_sizes.week,
                multiple: this.options.obj_multi.week
            })).inject(elm_container, 'inside');

            // add the container div to the dom
            elm_container.inject(str_periods_id, 'after');
        }
        
        // create month component
        if(this.boo_has_months) {
        
            var elm_container = new Element('div', {
                'class': obj_container_class.month,
                'id': obj_container_id.month
            });
        
            (new Element('label', {
                'for': obj_ids.month
            })).set('html', obj_labels.month).inject(elm_container, 'inside');
            
            (new Element('select', {
                id: obj_ids.month,
                name: obj_ids.month + '[]',
                size: obj_sizes.month,
                multiple: this.options.obj_multi.month
            })).addEvent('change', this._event_select_change.bindWithEvent(this)).inject(elm_container, 'inside');
            
            // add the container div to the dom
            elm_container.inject(str_periods_id, 'after');
        }
        
        // create year component
        if(this.boo_has_years) {
        
            var elm_container = new Element('div', {
                'class': obj_container_class.year,
                'id': obj_container_id.year
            });
        
            (new Element('label', {
                'for': obj_ids.year
            })).set('html', obj_labels.year).inject(elm_container, 'inside');
        
            (new Element('select', {
                id: obj_ids.year,
                name: obj_ids.year + '[]',
                size: obj_sizes.year,
                multiple: this.options.obj_multi.year
            })).addEvent('change', this._event_select_change.bindWithEvent(this)).inject(elm_container, 'inside');
            
            // add the container div to the dom
            elm_container.inject(str_periods_id, 'after');
        }
    },
    
    
    
    /**
     * Utility method to set a select area's options. The obj_items parameter
     * contains value: name pairs.
     *
     * @param str str_id  ID of the select element to add the options to
     * @param arr arr_items  Items to add (in { id: 'x', name: 'x' } format)
     * @return void
     */
    _set_select_options: function(str_id, arr_items) {
    
        var elm_select = $(str_id);
        
        // empty current contents
        elm_select.empty();
    
        // create each new option element as per the contents of obj_items
        //for(var i in obj_items) {
        for(var i = 0, ilen = arr_items.length; i < ilen; i++) {
        
            var obj_item = arr_items[i];
            
            (new Element('option', {
                'value': obj_item.id
            })).set('html', obj_item.name).inject(elm_select, 'bottom');
        }
        
        // fixes weird bug in IE where it shrinks the select width every
        // time we change the options
        if(Browser.Engine.trident) {
            elm_select.setStyle('width', 'auto');
            elm_select.setStyle('width', '100%');
        }
        
        // notify any listeners a select area has changed
        this.fireEvent('elementChange');
    },
    
    
    
    /**
     * Utility method to return the week start/end dates from a single start date
     * (although it requires the year and month of the date too).
     *
     * @param int int_year  Year of the week to calculate
     * @param int int_month Month of the week to calculate
     * @param int int_day   The week ending date
     * @return string [formatted string in the form "JAN 01 - JAN 07"
     */
    _get_week_string: function(int_year, int_month, int_day) {

        // we have the ending date (note: months are indexed starting from 0)
        var obj_end_date = new Date(int_year, int_month - 1, int_day);
        
        // calculate the date 7 days ago (inclusive - hence, 6)
        var obj_start_date = new Date(obj_end_date.getTime() - (6 * 1000 * 60 * 60 * 24));

        var int_start_day = (obj_start_date.getDate());
        var int_end_day = obj_end_date.getDate();
        
        // format the number so they're always 2 digits (e.g. 01, 08, 22)
        int_start_day = int_start_day < 10 ? '0' + int_start_day : int_start_day;
        int_end_day = int_end_day < 10 ? '0' + int_end_day : int_end_day;
        
        // write the month of the corresponding date/day with each date range part (e.g. JAN 30 - FEB 03)
        
        // ensure that the month is a corresponding index
        int_start_month = obj_start_date.getMonth();
        if(int_start_month < 0) {
            int_start_month = int_start_month + 12;
        }
        
        int_end_month = obj_end_date.getMonth();
        if(int_end_month < 0) {
            int_end_month = int_end_month + 12;
        }
        
        // format start and end strings
        str_start = this.arr_month_formats[int_start_month]['M'] + ' ' + int_start_day;
        str_end = this.arr_month_formats[int_end_month]['M'] + ' ' + int_end_day;
        
        return str_start + ' - ' + str_end;
    },
    
    
    
    /**
     * Listens to change events from the month and years select items and updates
     * others based on these. For example, if months or years changes and there's a
     * weeks item, the weeks are updated. If years change, months are enabled/disabled
     * according to which are available.
     *
     * @param obj obj_event  Event object
     * @return void
     */
    _event_select_change: function(obj_event) {
        
        var obj_ids = this.options.obj_ids;
        var str_element_id = obj_event.target.getProperty('id');

        // if this is a year change, check which months should be enabled or disabled
        if(str_element_id == this.options.obj_ids.year && this.boo_has_years && this.boo_has_months) {
        
            var elm_select = $(str_element_id);
            var int_selected_year = elm_select.getProperty('value').toInt();
            
            if(int_selected_year) {
            
	            var arr_month_options = $(obj_ids.month).getElements('option');
	        
	            // check this selected years months
	            for(var i = 0, ilen = arr_month_options.length; i < ilen; i++) {
	            
	                var int_month = arr_month_options[i].getProperty('value');
	                
	                if(this.obj_lookup[int_selected_year][int_month]) {
	                    arr_month_options[i].disabled = false;
	                    arr_month_options[i].setStyle('color', 'black');
	                }
	                else {
	                    arr_month_options[i].disabled = true;
	                    arr_month_options[i].selected = false;
	                    arr_month_options[i].setStyle('color', 'gray');
	                }
	            }
	        }
        }
        
        // if we have weeks and this is a change to the months or year selections,
        // update the weeks content
        if(this.boo_has_weeks) {
        
            if(str_element_id == obj_ids.year || str_element_id == obj_ids.month) {
            
                var int_year_value = $(obj_ids.year).getProperty('value').toInt();
                var int_month_value = $(obj_ids.month).getProperty('value').toInt();
            
                // is there a selection in both year and month?
                if(int_year_value && int_month_value) {
                
                    // set appropriate weeks
                    var arr_weeks = new Array();

		            for(var i in this.obj_lookup[int_year_value][int_month_value]) {
		                arr_weeks.push({id: i, name: this._get_week_string(int_year_value, int_month_value, i)});
		            }
		            
                    this._set_select_options(obj_ids.week, arr_weeks);
                }
                
                // otherwise wipe the weeks options
                else { 
                
                    this._set_select_options(obj_ids.week, []);
                }
            }
        }
        
        // notify any listeners a select area has changed
        this.fireEvent('elementChange');
    },
    
    
    
    /**
     * Submit handler - detects a form submit action and adds the selected julian week
     * ids to hidden input elements prior to the submission
     *
     * @return void
     */
    _event_submit: function() {
        
        var obj_ids = this.options.obj_ids;
        var str_selected_year;
        var str_selected_month;
        
        // initially remove any previously created hidden ids (this method may have been
        // called before but validation may have halted it)
        var arr_previous = $(this.str_form_id).getElements('input[type=hidden]');
        
        for(var i = 0, ilen = arr_previous.length; i < ilen; i++) {
            if(arr_previous[i].getProperty('name') == this.str_select_id + '[]') {
                arr_previous[i].dispose();
            }
        }
        
        // retrieve selected year and month
        if(this.boo_has_years) {
            str_selected_year = $(obj_ids.year).getProperty('value');
        }
        
        if(this.boo_has_months) {
            str_selected_month = $(obj_ids.month).getProperty('value');
        }
        
        // if we have weeks, retrieve the selected items
        if(this.boo_has_weeks) {
        
            var arr_weeks_selected = $(obj_ids.week).getElements('option[selected]');

            // create a new hidden input value for each period (julian week) selected
            for(var i = 0, ilen = arr_weeks_selected.length; i < ilen; i++) {
            
                // sanity check
                if(arr_weeks_selected[i].selected) {
            
	                var str_value = this.obj_lookup[str_selected_year][str_selected_month][arr_weeks_selected[i].getProperty('value')];
	            
	                (new Element('input', {
		                type: 'hidden',
		                name: this.str_select_id + '[]',
		                value: str_value
		            })).inject(this.str_form_id, 'inside');
		        }
            }
        }
        
        // otherwise it's months
        else {
        
            var arr_months_selected = $(obj_ids.month).getElements('option[selected]');

            // create a new hidden input value for each period (julian week) selected
            for(var i = 0, ilen = arr_months_selected.length; i < ilen; i++) {
            
                var str_value = this.obj_lookup[str_selected_year][arr_months_selected[i].getProperty('value')];
            
                (new Element('input', {
                    type: 'hidden',
                    name: this.str_select_id + '[]',
                    value: str_value
                })).inject(this.str_form_id, 'inside');
            }
        }
    }
});
