function acFields()
{
    this.fields = {};

    this.register = function (name, data)
    {
        this.fields[name] = {'json' : data.jsonProvider,
                              'limit' : data.limit};
        if(data.affected) this.fields[name]['affected'] = data.affected;
        if(data.consider) this.fields[name]['consider'] = data.consider;

    }

    this.getJsonProvider = function (name)
    {
        if(this.fields[name]['json'] != undefined) {
            return this.fields[name]['json'];
        } else {
            return false;
        }
    }

    this.getConsiderFields = function (name)
    {
        if(this.fields[name]['consider'] != undefined) {
            return this.fields[name]['consider'];
        } else {
            return false;
        }
    }

    this.getAffectedFields = function (name)
    {
        if(this.fields[name]['affected'] != undefined) {
            return this.fields[name]['affected'];
        } else {
            return false;
        }
    }

    this.getLimit = function (name)
    {
        if(this.fields[name]['limit'] != undefined) {
            return this.fields[name]['limit'];
        } else {
            return false;
        }
    }

    this.getValue = function (name)
    {
        return $('#'+name).attr('value');
    }

    this.bindEvents = function ()
    {
       for(fieldId in this.fields) {
            field = $("#" + fieldId)
            .bind('keydown', acEvents.keydown)
            .bind('keyup', acEvents.keyup)
            .bind('keypress', acEvents.keypress)
            .bind('blur', acEvents.blur);
        }
    }
}

function acAjax()
{
    this.data = {};
    this.processed = {};
    this.fieldsConsiderValues = {};

    this.cacheAdd = function (name, value, data)
    {
        this.refreshConsiderValues(name);
        try {
            this.data[name][value + this.fieldsConsiderValues[name]['idkey']] = data;
        } catch (e) {
            this.data[name] = {};
            this.cacheAdd(name, value, data);
        }
        if(data == null){
            this.data[name][value + this.fieldsConsiderValues[name]['idkey']] = data;
        }
    }

    this.cacheGet = function (name, value)
    {
        if(this.inCache(name, value)) {
            return this.data[name][value + this.fieldsConsiderValues[name]['idkey']];
        } else {
            return false;
        }
    }

    this.inCache = function (name, value)
    {
        try {
            if(this.data[name][value + this.fieldsConsiderValues[name]['idkey']] != undefined){
                return true;
            } else {
                return false;
            }
        } catch (e) {
            return false;
        }
    }

    this.generateConsiderValuesIdkey = function (considerValues)
    {
        var str = '';
        for(field in considerValues) {
            str = str + field + considerValues[field];
        }
        return str;
    }

    this.refreshConsiderValues = function (name)
    {
        try {
            var consider = acFields.getConsiderFields(name);
            var values = this.collectConsiderValues(consider);
            this.fieldsConsiderValues[name]['values'] = values;
            this.fieldsConsiderValues[name]['idkey'] = this.generateConsiderValuesIdkey(values);
        } catch(e) {
            this.fieldsConsiderValues[name] = {'values' : '', 'idkey' : ''};
            this.refreshConsiderValues(name);
        }
    }

    this.loadData = function (name, value)
    {
        var value = acFields.getValue(name);
        var limit = acFields.getLimit(name);
        var affected = acFields.getAffectedFields(name);
        var consider = acFields.getConsiderFields(name);
        var jsonUrl = acFields.getJsonProvider(name);

        this.refreshConsiderValues(name);
        var considerValues = this.collectConsiderValues(consider);
        var idkey = this.generateConsiderValuesIdkey(considerValues);

	    if(value != ''){
	        if(!this.inCache(name, value) && !this.processed[name+value+idkey]){
	            this.processed[name+value+idkey] = true;

	            var toAjax = {};
	            toAjax[name] = value;
	            toAjax['limit'] = limit;
	            if(affected) toAjax['affected'] = acArrayEncoder.encode(affected);
	            if(consider) toAjax['consider'] = acArrayEncoder.encode(considerValues);

	            $.getJSON(
	                jsonUrl,
	                toAjax,
                    function(data){
                        acAjax.cacheData(data, name, value);
                    }
	            )
	        } else {
	           this.goToView(name, value)
	        }
	    } else {
	       acView.hideMenu();
	    }
    }

    this.collectConsiderValues = function (considerFields)
    {
        var out = new Array();
	    for(index in considerFields) {
	        var fieldName = considerFields[index];
	        out[fieldName] = $('#' + fieldName).attr('value');
	    }
	    return out;
    }

    this.cacheData = function (data, name, value)
    {
        this.cacheAdd(name, value, data);
        this.goToView(name);
    }

    this.goToView = function (name)
    {
        acView.process(name);
    }
}

function acEvents() {
    this.handleFlag = true;

    this.keydown = function (event)
    {
        switch (event.which) {
			case 38:
			    // up arrow
			    this.handleFlag = false;
			    acView.menuNavigate(event.target.id, 'up');
			    break;
			case 40:
			    // down arrow
			    this.handleFlag = false;
			    acView.menuNavigate(event.target.id, 'down');
			    break;
			case 13:
			    // enter
			    this.handleFlag = false;
			    break;
			case 27:
			    // escape
			    acView.hideMenu(true);
			    break;
			default:
			    // any other key
			    this.handleFlag = true;
			    break;
        }
    }

    this.keyup = function (event)
    {
        if(this.handleFlag != false){
            acAjax.loadData(event.target.id);
        }
    }

    this.keypress = function (event)
    {
        if(event.which == 13) {
            if(acView.hideMenu()){
               event.preventDefault();
               return false;
            }
        }
    }

    this.blur = function (event)
    {
        acView.hideMenu();
    }

}

function acView()
{
    this.currentMenu = null;
    this.selectedElement = null;
    this.selectedRowClass = 'autocompleteSelectedRow';
    this.resultListClass = 'autocompleteResult';

    this.process = function (name)
    {
        this.renderMenu(name);
    }

    this.renderMenu = function (name)
    {
	    if(this.currentMenu != null) {
            this.hideMenu();
	    }

	    var field = $('#' + name);
	    var value = field.attr('value');
	    var off = field.offset();

	    acAjax.refreshConsiderValues(name);
	    var fullData = acAjax.cacheGet(name, value);
	    var data = fullData[name];
	    var length = 0;
	    var firstElement = null;
	    for(t in data){
            if(firstElement === null) {
                firstElement = t;
            }
            length++;
	    }

	    if(data && length > 0) {
	        if(length == 1 && firstElement == value) {
	           return;
	        }
	        var menu = $("<ul/>")
	        .addClass(this.resultListClass)
	        .css('position', 'absolute')
	        .css('top', off.top + field.outerHeight())
	        .css('left', off.left)
	        .css('width', field.innerWidth())
	        .bind('mouseout', this.menuMouseOut)
	        .appendTo(document.body);

	        for(suggest in data){
                $("<li/>")
                .text(suggest)
                .appendTo(menu)
                .bind('mouseover', this.itemMouseOver)
                .bind('mousedown', {'name' : name}, this.itemMouseDown)
            }
	        this.currentMenu = {'value' : value, 'fieldId' : name, 'affectedValuesRollback' : {}};

            var affected = acFields.getAffectedFields(name);
	        for(key in affected) {
	            field = affected[key];
	            jField = $('#' + field);
	            this.currentMenu['affectedValuesRollback'][field] = jField.attr('value');
	        }

	        acBrowserFixes.showAction();
	    }
    }

    this.hideMenu = function (rollback)
    {
        if(this.currentMenu != null){
			if(rollback) {
			   $('#'+this.currentMenu['fieldId'])
			   .attr('value', this.currentMenu['value'])

			   for(field in this.currentMenu['affectedValuesRollback']) {
			     var value = this.currentMenu['affectedValuesRollback'][field];
			     jField = $('#' + field);
			     if (jQuery.nodeName(jField, 'select')) {
	            	jField.selectOptions(fieldValue);
	             } else {
	            	jField.attr('value', value);
	             }
			   }
			}
            $("."+this.resultListClass).remove();
            this.currentMenu = null;
            this.selectedElement = null;
            acBrowserFixes.hideAction();
            return true;
	    } else {
            return false;
        }
    }

    this.fillAffected = function (name, userEntry, suggest)
    {
        var affected = acFields.getAffectedFields(name);

        var data = acAjax.cacheGet(name, userEntry);
        for(key in affected) {
            field = affected[key];
            jField = $('#' + field);
            var fieldValue = data[name][suggest][field];

            if (jQuery.nodeName(jField, 'select')) {
            	jField.selectOptions(fieldValue);
            } else {
            	jField.attr('value', fieldValue);
            }
        }
        if (name == 'zip') {
        	var city = $('#city').val();
        	$('#zip').val($('#zip').val().substring(0, $('#zip').val().lastIndexOf(city)-1));
        	handleIdRelatedField();
        }
    }

    this.menuNavigate = function (name, direction)
    {
        var field = $('#'+name);
        if(this.currentMenu == null) {
	        acAjax.loadData(name);
	        return;
	    }
	    $('li.' + this.selectedRowClass).removeClass(this.selectedRowClass);
	    menu = $('.' + this.resultListClass);
	    var items = $('li', menu)
	    .get();
	    if(items.length > 0){
	        if(direction === 'down') {
		        if(this.selectedElement == items.length-1 || this.selectedElement == null) {
		            this.selectedElement = 0;
		        } else {
		            this.selectedElement = this.selectedElement + 1;
		        }
	        } else if (direction === 'up') {
                if(this.selectedElement == 0 || this.selectedElement == null) {
	                this.selectedElement = items.length - 1;
	            } else {
	                this.selectedElement = this.selectedElement - 1;
	            }
	        }
	        items[this.selectedElement].className = this.selectedRowClass;
	        var value = items[this.selectedElement].innerHTML;
	        $(field).attr('value', value);
	        this.fillAffected(name, this.currentMenu['value'], value);
	    }
    }

    this.menuMouseOut = function ()
    {
        $('li.' + acView.selectedRowClass).removeClass(acView.selectedRowClass);
    }

    this.itemMouseOver = function ()
    {
        $('li.' + acView.selectedRowClass).removeClass(acView.selectedRowClass);
        $(this).toggleClass(acView.selectedRowClass);
    }

    this.itemMouseDown = function (event)
    {
        var field = $('#'+event.data.name);

        var newValue = $(this).text();
        var oldValue = field.attr('value');
        field.attr('value', newValue);
        acView.fillAffected(event.data.name, oldValue, newValue);
    }
}

function acArrayEncoder()
{
    this.encode = function (array){
        var serializedString = '';
	    var arrayLength = 0;
	    for(var aKey in array) {
	        if(aKey * 1 == aKey) {
	            serializedString += 'i:' + aKey + ';';
	        }
	        else {
	            serializedString += 's:' + aKey.length + ':"' + aKey + '";';
	        }

	        if(array[aKey] * 1 == array[aKey]) {
	            serializedString += 'i:' + array[aKey] + ';';
	        }
	        else if(typeof(array[aKey]) == "string") {
	            serializedString += 's:' + array[aKey].length + ':"' + array[aKey] + '";';
	        } else if(array[aKey] instanceof Array) {
	            serializedString += serializeArray(array[aKey]);
	        }
	        arrayLength++;
	    }
	    serializedString = 'a:' + arrayLength + ':{' + serializedString + '}';

	    return serializedString;
    }
}

function acBrowserFixes()
{
    this.hiddenFromExplorer = new Array();

    this.showAction = function () {
        if($.browser.msie){
            this.explorerDropDownListsFixHide();
        }
    }

    this.hideAction = function () {
        if($.browser.msie){
            this.explorerDropDownListsFixShow();
        }
    }

    this.explorerDropDownListsFixHide = function ()
    {
        var menu = $('.' + acView.resultListClass);
	    var offset = menu.offset();
	    oh = menu.outerHeight();
	    ow = menu.outerWidth();
	    l = offset.left;
	    r = offset.left + ow;
	    t = offset.top;
	    b = offset.top + oh;
	    var key = 0;
	    $('select').each(function(){
	        var off = $(this).offset();
	        sl = off.left;
	        sr = off.left + $(this).outerWidth();
	        st = off.top;
	        sb = off.top + $(this).outerHeight();
	        if(((st >= t && sb <= b) || (st >= t && st <= b))
	        && ((sl >= l && sl <= r) || (sr <= r && sr >= l))){
	            var select = $(this);
	            select.css('visibility', 'hidden');
	            var arr = acBrowserFixes.hiddenFromExplorer;
	            arr.push(select);
            }
        });
    }

    this.explorerDropDownListsFixShow = function ()
    {
        for(select in this.hiddenFromExplorer) {
            this.hiddenFromExplorer[select].css('visibility', 'visible');
        }
        this.hiddenFromExplorer = new Array();
    }
}

var acFields        = new acFields();
var acAjax          = new acAjax();
var acEvents        = new acEvents();
var acArrayEncoder  = new acArrayEncoder();
var acView          = new acView();
var acBrowserFixes  = new acBrowserFixes();

$(document).ready(function(){
    acFields.bindEvents();
});