(function($, useDOM0) {
    $.extend($.fn,
	     useDOM0 ? {
		 clearOptions: function() {
		     this.each(function(){ this.options.length=0 });
		 },
		     addOption: function(text, value, selected) {
		     this.each(function() {
                              this.options[this.options.length]
                                  = new Option(text, value, selected, selected);
			 });
		 }
	     } : {
		 clearOptions: function() {
		     this.empty();
		 },
		     addOption: function(text, value, selected) {
		     $(selected ? '<option selected="selected" />' : '<option />')
			 .text(text).attr('value', value).appendTo(this);
		 }
	     })
	})(jQuery, jQuery.browser.msie);

var DynamicForm = {};

(function($) {

DynamicForm.BoolHashBuilder = new (function() {
	var tmp = null;
	this.init = function(initial) {
	    tmp = initial;
	    return this;
	}
	this.getResult = function() {
	    return tmp;
	}
	this.union = function(hash) {
	    if(tmp == null) {
		tmp = {};
	    }
	    return $.extend(tmp, hash);
	}
	this.intersect = function(hash) {
	    if(tmp == null) {
		//clone
		tmp = $.extend({}, hash);
	    } else {
		var newTmp = {};
		for(var key in hash) {
		    if(tmp[key]) {
			newTmp[key] = tmp[key];
		    }
		}
		tmp = newTmp;
	    }
	    return tmp;
	}
	this.set = function(key, val) {
	    tmp[key] = val;
	}
    });

DynamicForm.Model = (function() {
	var MasterModel = new (function(){
		this.summary;

		var hotelCache = {};
		var hotelRankCache = {};
		var hotelTypeCache = {};
		var roomCategoryCache = {};

		this.init = function(s) {
		    this.summary = s;
		}

		this.getAirlineIdsFromDeptId = function(deptId) {
		    return this.summary.airlinesByDept[deptId];
		}

		this.getHotelIdsFromType = function(hotelType) {
		    return this.summary.hotelTypes[hotelType];
		}

		this.getHotelRank = function(rank) {
		    if(hotelRankCache[rank]) {
			return hotelRankCache[rank];
		    }
		    var tmp = {}, hotelData = this.summary.hotels;
		    var hotelTypes = {}, roomCategories = {};

		    tmp.hotels = this.summary.ranks[rank];
		    for(var hotelId in tmp.hotels) {
			if(hotelData[hotelId]) {
			    $.extend(hotelTypes, hotelData[hotelId].hotelTypes);
			    $.extend(roomCategories, hotelData[hotelId].roomCategories);
			}
		    }
		    tmp.hotelTypes = hotelTypes;
		    tmp.roomCategories = roomCategories;

		    hotelRankCache[rank] = tmp;
		    return tmp;
		}

		this.getHotelType = function(type) {
		    if(hotelTypeCache[type]) {
			return hotelTypeCache[type];
		    }
		    var tmp = {}, hotelData = this.summary.hotels;
		    var ranks = {}, roomCategories = {};

		    tmp.hotels = this.summary.hotelTypes[type];
		    for(var hotelId in tmp.hotels) {
			if(hotelData[hotelId]) {
			    $.extend(roomCategories, hotelData[hotelId].roomCategories);
			    ranks[hotelData[hotelId].rank] = true;
			}
		    }
		    tmp.roomCategories = roomCategories;
		    tmp.ranks = ranks;

		    hotelTypeCache[type] = tmp;
		    return tmp;
		}

		this.getRoomCategory = function(rcId) {
		    if(roomCategoryCache[rcId]) {
			return roomCategoryCache[rcId];
		    }
		    var tmp = {}, hotelData = this.summary.hotels;
		    var ranks = {}, hotelTypes = {};

		    tmp.hotels = this.summary.roomCategories[rcId];
		    for(var hotelId in tmp.hotels) {
			if(hotelData[hotelId]) {
			    $.extend(hotelTypes, hotelData[hotelId].hotelTypes);
			    ranks[hotelData[hotelId].rank] = true;
			}
		    }
		    tmp.hotelTypes = hotelTypes;
		    tmp.ranks = ranks;

		    roomCategoryCache[rcId] = tmp;
		    return tmp;
		}

		this.getHotel = function(hotelId) {
		    if(hotelCache[hotelId]) {
			return hotelCache[hotelId];
		    }
		    hotelCache[hotelId] = this.summary.hotels[hotelId];
		    hotelCache[hotelId].ranks = {};
		    hotelCache[hotelId].ranks[hotelCache[hotelId].rank] = true;
		    return hotelCache[hotelId];
		}
	    });

	var SelectorModel = jQuery.SimpleClass.create({
		initialize: function(name, emptyIndex) {
		    this.name = name;
		    this.labels = [];
		    this.values = [];
		    this.value2Index = {};
	
		    this.isIndexAvailable = {};
		    this.selectedIndex = null;
		    this.view = null;

		    //always available; usually empty
		    this.emptyIndex = emptyIndex;
		},
		isEmptySelected: function() {
		    return this.selectedIndex == this.emptyIndex;
		},
		/**
		 * @param i   Absolute index
		 * @param elm HTMLOptionElement
		 */
		makeOption: function(i, elm) {
		    this.labels.push(elm.text);
		    this.values.push(elm.value);
		    this.value2Index[elm.value] = i;
		    if(elm.selected) {
                this.selectedIndex = i;
		    }
		    this.isIndexAvailable[i] = true;
		},
		currentValue: function() {
		    return this.values[this.selectedIndex];
		},
		_updateView: function() {
		    var view = this.view;
		    setTimeout(function(){ view.update() }, 1);
		},
		select: function(val, updateView) {
		    this.selectedIndex = this.value2Index[val];
		    this.onSelected();
		    if(updateView) {
                this._updateView();
		    }
		},
        /**
         * @param object  { <index> => true }
         */
		_setAvailableIndices: function(indices) {
		    this.isIndexAvailable = indices;
		    if(this.emptyIndex !== null) {
                this.isIndexAvailable[this.emptyIndex] = true;
		    }
		    if(!this.isIndexAvailable[this.selectedIndex]) {
                this.selectedIndex = this.emptyIndex;
		    }
		    this._updateView();
		},
        /**
         * @param array values
        **/
		setAvailableValues: function(values) {
		    var self = this, arr = {};
		    $.each(values, function(i,e) {
			    if(self.value2Index[e] !== undefined) {
                    arr[self.value2Index[e]] = true;
			    }
			});
		    this._setAvailableIndices(arr);
		},
		setAvailableAssoc: function(assoc) {
		    var self = this, arr = {};
		    $.each(assoc, function(e,_) {
			    if(self.value2Index[e] !== undefined) {
                    arr[self.value2Index[e]] = true;
			    }
			});
		    this._setAvailableIndices(arr);
		},
		reset: function() {
		    this.setAvailableValues(this.values);
		},
		onSelected: function(){}
    });

    var CheckboxModel = jQuery.SimpleClass.create({
        initialize: function(name) {
		    this.name = name;
		    this.values = [];
		    this.value2Index = {};
	
		    this.isIndexAvailable = {};
		    this.selectedIndex = [];
		    this.view = null;
        },
        onChanged: function() {},
		/**
		 * @param i   Absolute index
		 * @param elm HTMLCheckboxElement
		 */
		makeOption: function(i, elm) {
		    this.values.push(elm.value);
		    this.value2Index[elm.value] = i;
		    if(elm.checked) {
                this.selectedIndex.push(i);
		    }
		    this.isIndexAvailable[i] = true;
		},
		currentValue: function() {
            var tmp = [], i = 0;
            for(; i<this.selectedIndex.length; ++i) {
                tmp.push(this.values[this.selectedIndex[i]]);
            }
		    return tmp;
		},
        isSelectedVal: function(val) {
            var index = this.value2Index[val];
            return $.inArray(index, this.selectedIndex) > -1;
        },
		select: function(val) {
            var index = this.value2Index[val];
            if(this.isSelectedVal(val)) {
                return;
            }
            this.selectedIndex.push(index);
            this.onChanged();
            this.updateView();
		},
        unselect: function(val) {
            var newVal = [],
                indexToRemove = this.value2Index[val];
            if(!this.isSelectedVal(val)) {
                return;
            }
            for(var i=0; i<this.selectedIndex.length; ++i) {
                if(indexToRemove != this.selectedIndex[i]) {
                    newVal.push(this.selectedIndex[i]);
                }
            }
            this.selectedIndex = newVal;
            this.onChanged();
            this.updateView();
        },
        updateView: function() {
        }
    });

	var AirlineModel = jQuery.SimpleClass.create(SelectorModel, {
		initialize: function() {
		    SelectorModel.prototype.initialize.call(this, 'airline_id', 0);
		},
		notifyDeparture: function(deptId) {
            // Dupulicates are not problem.
            var allIds = $.map(deptId, $.proxy(MasterModel.getAirlineIdsFromDeptId, MasterModel));
		    this.setAvailableValues(allIds);
		}
	    });

	var DeptModel = jQuery.SimpleClass.create(CheckboxModel, {
		initialize: function(airline) {
		    this.airline = airline;
            this.tokyoValues = [];
		    CheckboxModel.prototype.initialize.call(this, 'departure_id', null);
		},
		onChanged: function() {
		    this.airline.notifyDeparture(this.currentValue());
		},
        updateView: function() {
            this.view.update();
        },
        selectTokyo: function() {
            var self = this;
            $.each(this.tokyoValues, function(i,val) { self.select(val); });
        },
        unselectTokyo: function() {
            var self = this;
            $.each(this.tokyoValues, function(i,val) { self.unselect(val); });
        },
        isAllTokyoSelected: function() {
            for(var i=0; i<this.tokyoValues.length; ++i) {
                if(!this.isSelectedVal(this.tokyoValues[i])) {
                    return false;
                }
            }
            return true;
        }
    });

	var HotelColleagueModel = jQuery.SimpleClass.create(SelectorModel, {
		initialize: function(name) {
		    this.filters = {};
		    SelectorModel.prototype.initialize.call(this, name, 0);
		},
		setMediatior: function(mediator) {
		    this.mediator = mediator;
		},
		onSelected: function() {
		    this.mediator.modelChanged(this);
		},
		addFilter: function(name, assoc) {
		    this.filters[name] = assoc;
		    this._updateAvailableIndices();
		},
		removeFilter: function(name) {
		    this.filters[name] = null;
		    this._updateAvailableIndices();
		},
		/**
		 * calculate available indices from all the filters
		 */
		_updateAvailableIndices: function() {
		    var builder = DynamicForm.BoolHashBuilder.init(null);
		    var assoc;
		    for(var name in this.filters) {
			if(!(assoc = this.filters[name])) {
			    continue;
			}
			builder.intersect(assoc);
		    }

		    var result = builder.getResult();
		    if(result) {
			this.setAvailableAssoc(result);
		    } else {
			// all conditions are empty
			this.reset();
		    }
		    return result;
		}
	    });

	var HotelMediator = jQuery.SimpleClass.create({
		initialize: function() {
		    this.colleagueNames = ['hotel_id', 'hotel_type', 'hotel_rank', 'room_category_id'];
		    this.colleagues = {};
		},
		createModels: function(loadFunc) {
		    var name;
		    for(var i=0; i<this.colleagueNames.length; ++i) {
			name = this.colleagueNames[i];
			this.colleagues[name] = new HotelColleagueModel(name);
			this.colleagues[name].setMediatior(this);
			loadFunc(this.colleagues[name]);
		    }
		},
		onModelsSelected: function() {
		    $.each(this.colleagues, function() {
			    this.onSelected();
			});
		},
		clearAll: function() {
		    $.each(this.colleagues, function() {
			    this.select("", true);
			});
		},
		modelChanged: function(model) {
		    if(model.isEmptySelected()) {
			return this._removeTheModel(model);
		    }

		    var data;
		    switch(model.name) {
		    case 'hotel_id':
		    data = MasterModel.getHotel(model.currentValue());
		    this.colleagues['hotel_type'].addFilter(model.name, data.hotelTypes);
		    this.colleagues['hotel_rank'].addFilter(model.name, data.ranks);
		    this.colleagues['room_category_id'].addFilter(model.name, data.roomCategories);
		    break;

		    case 'hotel_rank':
		    data = MasterModel.getHotelRank(model.currentValue());
		    this.colleagues['hotel_id'].addFilter(model.name, data.hotels);
		    this.colleagues['hotel_type'].addFilter(model.name, data.hotelTypes);
		    this.colleagues['room_category_id'].addFilter(model.name, data.roomCategories);
		    break;

		    case 'hotel_type':
		    data = MasterModel.getHotelType(model.currentValue());
		    this.colleagues['hotel_id'].addFilter(model.name, data.hotels);
		    this.colleagues['room_category_id'].addFilter(model.name, data.roomCategories);
		    this.colleagues['hotel_rank'].addFilter(model.name, data.ranks);
		    break;

		    case 'room_category_id':
		    data = MasterModel.getRoomCategory(model.currentValue());
		    this.colleagues['hotel_id'].addFilter(model.name, data.hotels);
		    this.colleagues['hotel_type'].addFilter(model.name, data.hotelTypes);
		    this.colleagues['hotel_rank'].addFilter(model.name, data.ranks);
		    break;
		    }
		},
		_removeTheModel: function(model) {
		    // if a condition is cleared, remove the condition
		    $.each(this.colleagues, function() {
			    if(model != this) {
				this.removeFilter(model.name);
			    }
			});
		}
	    });

	return {Master: MasterModel,
		Airline: AirlineModel,
		Departure: DeptModel,
		HotelMediator: HotelMediator
		};
    })();

DynamicForm.View = (function() {
	var SelectorView = jQuery.SimpleClass.create({
		initialize: function(jqueryDom, model) {
		    this.jqueryDom = jqueryDom;
		    this.model = model;
		},
		update: function() {
		    if(this.jqueryDom.size() < 1) {
                return;
		    }

		    var model = this.model;
		    var selected = model.selectedIndex;

		    this.jqueryDom.clearOptions();
		    for(var i=0; i<model.values.length; ++i) {
                if(model.isIndexAvailable[i]) {
                    this.jqueryDom.addOption(model.labels[i], model.values[i], i == selected);
                }
		    }
		}
    });

    // 選択肢の増減機能はなし、checkedの制御のみ
    var CheckboxView = jQuery.SimpleClass.create({
        initialize: function(jqueryDom, model) {
            this.jqueryDom = jqueryDom;
            this.model = model;
        },
        update: function() {
            var model = this.model;
            this.jqueryDom.each(function(i) {
                $(this).attr('checked', $.inArray(i, model.selectedIndex) > -1);
                });
            $('#all-tokyo').attr('checked', model.isAllTokyoSelected());
        }
    });

	return { Selector: SelectorView, Checkbox: CheckboxView };
    })();

})(jQuery);


function setupTourSearchForm(summary) {
    var $ = jQuery;
    var formPath = "form#tour-search-form";

    DynamicForm.Model.Master.init(summary);

    function execute() {
	var airline = new DynamicForm.Model.Airline();
	var departure = new DynamicForm.Model.Departure(airline);
	var hotelMediator = new DynamicForm.Model.HotelMediator();
	loadModels(airline);
    loadCheckboxModel(departure);
	hotelMediator.createModels(loadModel);

    departure.onChanged();
	hotelMediator.onModelsSelected();

	$(function() {
		// implement clear buttons
		$(formPath +' a.clear').click(function(e) {
			$(formPath +' :input.clearable').val("");
			$('#serach-category input:checkbox, #serach-kansei input:checkbox, #serach-hotel input:checkbox').each(function(){
				this.checked = false;
			    });
			hotelMediator.clearAll();
			e.preventDefault();
		    });

		//implements datepicker
		if($.ui && $.ui.datepicker) {
		    setupDatepicker(DynamicForm.Model.Master);
		}

		//if "Oahu" is selected, checks this_city_only
		var citySel = $('#TourSearchCityId'), thisCityOnly = $('#TourSearchThisCityOnly');
		if(thisCityOnly.length > 0) {
		    citySel.change(function(){
			    if(citySel.val() === "1") {
				thisCityOnly.get(0).checked = "checked";
			    }
			});
		}
	    });

        function wrapTokyo($) {
            var inputs = $('.tokyo-airport');
            if(inputs.length < 2) {
                return;
            }
            departure.tokyoValues = $.map(inputs, function(input) { return input.value; });
	    if(departure.selectedIndex.length == 0) {
		$.each(departure.tokyoValues, function(i,val){ departure.select(val); });
	    }

	    $('div.checkbox').has('input.tokyo-airport').wrapAll('<div class="all-tokyo-wrapper"></div>');

            var initialChecked = departure.isAllTokyoSelected() ? 'checked="checked"' : "";
            var tokyoCheckbox = '<div class="checkbox">';
            tokyoCheckbox+= '<input type="checkbox" '+ initialChecked +' id="all-tokyo" />';
            tokyoCheckbox+= '<label for="all-tokyo">東京</label></div>';
	    $('.all-tokyo-wrapper').prepend(tokyoCheckbox + '<span>(</span>').append('<span>)</span>');
            $('#all-tokyo').click(function(e) {
                departure[this.checked ? 'selectTokyo' : 'unselectTokyo']();
            });
        }
        $(document).ready(wrapTokyo);
    }

    function setupDatepicker(masterModel) {
	var minDate = '+1 day';
	var maxDate = (function(p){ return new Date(p[0], parseInt(p[1], 10) - 1, p[2]);
	    })(masterModel.summary.departure_date_max.split(/-/));

	function updateYm(date) {
	    $('#TourSearchDepartureYm').val(""+ date[0] + date[1]);
	}

	function updateDay(date) {
	    $('#TourSearchDepartureDay').val(parseInt(date[2], 10).toString());
	}

	$('#_datepicker').datepicker({
		showOn: 'button',
		    buttonImage: '/img/icon_calendar.gif',
		    buttonImageOnly: true,
		    dateFormat: 'yy-mm-dd',
		    minDate: minDate,
		    maxDate: maxDate,
		    showMonthAfterYear: false,
		    onSelect: function(dateText) { var p = dateText.split(/-/); updateYm(p); updateDay(p); }
	    });
    }

    /**
     * load model data from HTML
     */
    function loadModels() {
        for(var i = 0; i<arguments.length; ++i) {
            loadModel(arguments[i]);
        }
    }

    function loadModel(model) {
        var name = model.name;

        var elm = $(formPath +' select[name="'+ name +'"]');
        $('option', elm).each(function(i, elm) {
            model.makeOption(i, elm);
	    });

        //fix for safari
        if(model.selectedIndex == null) {
            model.selectedIndex = model.emptyIndex;
        }

        //set Controller
        elm.change(function(e) {
            model.select(elm.val());
	    });
        model.view = new DynamicForm.View.Selector(elm, model);
        return model;
    }

    function loadCheckboxModel(model) {
        var name = model.name;

        var elm = $(formPath +' input[type="checkbox"][name="'+ name +'\[\]"]');
        elm.each(function(i, elm) {
            model.makeOption(i, elm);
	    });

        //set Controller
        elm.click(function(e) {
            if(this.checked) {
                model.select(this.value);
            } else {
                model.unselect(this.value);
            }
	    });
        model.view = new DynamicForm.View.Checkbox(elm, model);
        return model;
    }
    execute();
}


