/*
* jQuery selectbox plugin
*
* Copyright (c) 2007 Sadri Sahraoui (brainfault.com)
* Licensed under the GPL license and MIT:
*   http://www.opensource.org/licenses/GPL-license.php
*   http://www.opensource.org/licenses/mit-license.php
*
* The code is inspired from Autocomplete plugin (http://www.dyve.net/jquery/?autocomplete)
*
* Revision: $Id$
* Version: 0.6
* 
* Changelog :
*  Version 0.6
*  - Fix IE scrolling problem
*  Version 0.5 
*  - separate css style for current selected element and hover element which solve the highlight issue 
*  Version 0.4
*  - Fix width when the select is in a hidden div   @Pawel Maziarz
*  - Add a unique id for generated li to avoid conflict with other selects and empty values @Pawel Maziarz
*/

/* jQuery extension for change event fix in IE */
jQuery.fn.extend({
    fire: function (evttype) {
        el = this.get(0);
        if (document.createEvent) {
            var evt = document.createEvent('HTMLEvents');
            evt.initEvent(evttype, false, false);
            el.dispatchEvent(evt);

        } else if (document.createEventObject) {
            //PK: If we have hooked up the event trigger at the dom level then fire the event with IE "fireEvent" function
            if (el.onchange) {
                el.fireEvent('on' + evttype);
            }
            else {//PK: we have only hooked up to the "change" event with jQuery. This means the "fireEvent" doesn't fire, so call the jQuery "change" event
                $(el).change();
            }
        }
        return this;
    }
});

/* jQuery extension for Select box */
jQuery.fn.extend({
    selectbox: function (options) {
        return this.each(function () {
            new jQuery.SelectBox(this, options);
        });
    }
});

/* String ends with function */
String.prototype.endsWith = function (pattern) {
    var d = this.length - pattern.length;
    return d >= 0 && this.lastIndexOf(pattern) === d;
};


/* pawel maziarz: work around for ie logging */
if (!window.console) {
    var console = {
        log: function (msg) {
        }
    }
}
/* */

/* begin jquery selectbox logix */
jQuery.SelectBox = function (selectobj, options) {

    var opt = options || {};
    opt.inputClass = opt.inputClass || "selectbox";
    opt.containerClass = opt.containerClass || "selectbox-wrapper";
    opt.hoverClass = opt.hoverClass || "current";
    opt.currentClass = opt.selectedClass || "selected"
    opt.debug = opt.debug || false;
    opt.maxLength = opt.maxLength || 0;

    debug("SelectBox jquery called");

    var elm_id = selectobj.id;
    var active = 0;
    var inFocus = false;
    var hasfocus = 0;
    //jquery object for select element
    var $select = $(selectobj);
    // jquery container object
    var $container = setupContainer(opt);
    //jquery input object 
    var $input = setupInput(opt);

    //PK variables
    var _sDropdownFilter = "";
    var _bSkipShowContainerOnFocusEvent = false;
    var $label = $("label[for=" + $select.attr("id") + "]");
    var _objActiveElementFix = null;
    var _dateTimeLastKeyPress = new Date();
    //PK variables stop

    // hide select and append newly created elements
    $select.hide().before($input).before($container);

    // PK: Set the currently selected value (sets the hidden selectbox value too)
    $select.bind("changeselectedvalue", function (event, selectedValue) {

        // set dropdown value
        $select.val(selectedValue);
        fireSelectedItemChangedEvent();

        // go through each li
        $('li', $container).each(function (index) {

            // find the li which matches the value
            if (this.id.endsWith('_' + selectedValue)) {
                var li = this;

                // PK: This is copied from the li click event. Should be restructured to sor 
                debug('set value through event:' + li.id);
                // PK: Make li selected 
                makeLiSelected(li)

                //PK: finalise selection
                finaliseLiSelection(li, false);
            }

        });
    });

    init();

    debug("Binding input events start");
    //PK: Not working in opera
    $label.click(function (e) {
        debug('label clicked');
        //PK:Hide all other controls lists
        hIBaseDealDLlListOnPage();

        //PK: Focus on the input, false = fire full event
        focusOnInput(false);

        //PK: need this to stop firefox from taking focus from the input and putting on the label
        return false;
    }
	);

    $input
	.mousedown(function (e) {
	    //PK: Must be mousedown event. This fires before the input gets focus. If I use click event the input focus fires before the click which 
	    //causes the following problem;
	    //The drop down list container is show in the focus event but then hidden in the click event, so when you first click on the input,
	    //the drop down list is shown then hiden again!
	    debug("input click event. Container is " + $container.is(':visible'))

	    //PK: If we are visible and haven't just shown the container in the focus event then we need to hide the container
	    if ($container.is(':visible')) {
	        hideMe();
	    }
	    else if (!$container.is(':visible')) {
	        showMe();
	    }
	})
	.focus(function (e) {
	    //alert("start focus");
	    //PK: Clear filtered so user can start typing from scratch
	    setDropdownFilter("");

	    //PK: We have to make the textbox readonly on focus. We cannot do this when we create the form input because safari, chrome and opera
	    //won't table to a readonly text box
	    $input.attr("readonly", "readonly");

	    //set opt.debug to true at top of class
	    debug('input got focus: ' + $input.attr("id") + '. Skip show container on focus: ' + _bSkipShowContainerOnFocusEvent + '. Container visible: ' + $container.is(':visible'));

	    if (!$container.is(':visible') && !_bSkipShowContainerOnFocusEvent) {
	        inFocus = true;
	        //PK:Need to rest the _bSkipShowContainerOnFocusEvent flag
	        _bSkipShowContainerOnFocusEvent = false;
	        //PK: Show the list of li's
	        showMe();

	        debug('show list. Skip focus: ' + _bSkipShowContainerOnFocusEvent);
	    }

	    //alert("end focus");
	})
	.keydown(function (event) {
	    switch (event.keyCode) {
	        case 38: // up
	            event.preventDefault();
	            moveSelect(-1);

	            break;
	        case 40: // down
	            event.preventDefault();
	            moveSelect(1);

	            break;
	        case 9:  // tab   
	            debug("key pressed: tab");
	            finaliseLiSelection();

	            break;
	        case 13: // return
	            event.preventDefault(); // seems not working in mac !

	            finaliseLiSelection();

	            break;
	        case 27: //escape
	            finaliseLiSelection();
	            hideMe();
	            break
	        case 9:
	            break;
	        default:
	            //PK placed my login in function
	            //when this calls moveSelect this can't get offsetTop
	            inputOnKeyDown(event);
	            break;
	    }


	})
	.blur(function () {

	    debug('input blur');

	    //PK: Remove the readonly attibute that we added on focus
	    $input.removeAttr("readonly");

	    if ($container.is(':visible') && hasfocus > 0) {
	        debug('container visible and has focus');
	    }
	    else {
	        // Workaround for ie scroll - thanks to Bernd Matzner
	        if ($.browser.msie || $.browser.safari || $.browser.opera) { // check for safari too - workaround for webkit

	            var activeElement;
	            var activeElementID;

	            //PK: Fix for chrom or safari or opera. These browsers don't support active element so I have to simulate!
	            if ($.browser.safari || $.browser.opera) {
	                activeElement = _objActiveElementFix;
	            }
	            else {
	                activeElement = document.activeElement;
	            }

	            //PK: check for null. Active element can return null
	            if (activeElement != null) {
	                //PK: IE8 Fix. IE8 returns null if the id attribute hasn't been set other browsers return a empty string
	                if (activeElement.getAttribute('id') == null) {
	                    activeElementID = "";
	                }
	                else {
	                    activeElementID = activeElement.getAttribute('id');
	                }

	                if (activeElementID.indexOf('_container') == -1) {
	                    debug("blur hideMe():1");
	                    hideMe();
	                }
	                else {
	                    debug("blur trying to refocus on input. Active element: " + activeElementID);
	                    focusOnInput(true);
	                }
	            }
	            else {
	                debug("blur hideMe():2");
	                hideMe();
	            }
	        }
	        else {
	            debug("blur hideMe():3");
	            hideMe();
	        }
	    }

	    //PK: Reset if the textbox has just got form focus
	    _bFocusEventJustShownControl = false;
	});

    debug("Binding input events end");

    /* Init functions */

    function init() {
        debug("init() start");
        var childUL = getSelectOptions($input.attr('id'));

        if (childUL != null) {
            $container.append(childUL);
        }

        $container.hide();
        var width = $input.css('width');
        $container.width(width);
        debug("init() end");

        //PK: This is needed to reset the active element. If we don't do this it locks up all the UI!
        _objActiveElementFix = null
    }

    /* Setup functions */

    function setupContainer(options) {
        debug("setupContainer() start");
        var containerID = elm_id + '_container';
        var container = null;

        //PK: If we bind to a dropdown that we have already been bound to e.g in a form reset then we need to destroy the contanier first
        //destroy is needed and not removeChild as this messes up events clicks in every other browser than IE
        if ($("#" + containerID).length > 0) {
            container = $("#" + containerID);
            //remove children
            container.find('ul li').detach();
            container.find('ul').detach();
            container.detach();
        }

        container = document.createElement("div");
        $container = $(container);
        $container.attr('id', elm_id + '_container');
        $container.addClass(options.containerClass);

        //PK: Fix for chrom or safari. These browsers don't support active element so I have to simulate!
        //Used in the input blur event
        $container.mouseover(function (event) {
            _objActiveElementFix = this;
        }
	    )
	    .mouseout(function (event) {
	        _objActiveElementFix = null;
	    }
	    )

        debug("setupContainer() end");
        return $container;
    }

    function setupInput(options) {
        debug("setupInput() start");
        var inputID = elm_id + "_input";
        var input = null;

        //PK: If we bind to a dropdown that we have already been bound to e.g in a form reset then we need to destroy the contanier first
        //destroy is needed and not removeChild as this messes up events clicks in every other browser than IE
        if ($("#" + inputID).length > 0) {
            input = $("#" + inputID);
            debug("destroy input: #" + inputID);
            input.detach();
        }

        if ($("#" + inputID).length == 0) {
            input = document.createElement("input");
            $input = $(input);
            $input.attr("id", inputID);
            $input.attr("type", "text");
            $input.addClass(options.inputClass);
            $input.attr("autocomplete", "off");
            //PK: cannot tab to a readonly textbox in safari or chrome
            //$input.attr("readonly", "readonly");
            $input.attr("tabIndex", $select.attr("tabindex")); // "I" capital is important for ie
        }
        else {
            $input = $("#" + inputID);
        }

        debug("setupInput() end");
        return $input;
    }

    /* Helper functions */

    //PK: On prepare filter to filter items in select list
    function inputOnKeyDown(event) {
        var character = String.fromCharCode(event.keyCode);

        processTimeSinceLastKeyPress();

        //taken from character codes at http://www.scottklarr.com/topic/126/how-to-create-ctrl-key-shortcuts-in-javascript/
        if ((event.keyCode >= 48 && event.keyCode <= 90) || event.keyCode == 32) {
            //by default cancel event action
            event.preventDefault();

            //alpha numerics or space
            setDropdownFilter(_sDropdownFilter + character);

            filterList(_sDropdownFilter);

            showMe();
            //$container.show();
        }
        else if (event.keyCode >= 96 && event.keyCode <= 105) {
            //by default cancel event action
            event.preventDefault();

            //number page keys
            switch (event.keyCode) {
                case 96:
                    setDropdownFilter(_sDropdownFilter + "0");
                    break;
                case 97:
                    setDropdownFilter(_sDropdownFilter + "1");
                    break;
                case 98:
                    setDropdownFilter(_sDropdownFilter + "2");
                    break;
                case 99:
                    setDropdownFilter(_sDropdownFilter + "3");
                    break;
                case 100:
                    setDropdownFilter(_sDropdownFilter + "4");
                    break;
                case 101:
                    setDropdownFilter(_sDropdownFilter + "5");
                    break;
                case 102:
                    setDropdownFilter(_sDropdownFilter + "6");
                    break;
                case 103:
                    setDropdownFilter(_sDropdownFilter + "7");
                    break;
                case 104:
                    setDropdownFilter(_sDropdownFilter + "8");
                    break;
                case 105:
                    setDropdownFilter(_sDropdownFilter + "9");
                    break;
            }

            filterList(_sDropdownFilter);
        }
        else if (event.keyCode == 8) {
            //cancel event action
            event.preventDefault();

            //delete key
            if (_sDropdownFilter.length > -1) {
                //trim last character off
                setDropdownFilter(_sDropdownFilter.substring(0, _sDropdownFilter.length - 1));
                //redisplay list
                filterList(_sDropdownFilter);
            }
        }
    }

    //PK: Filter items in select list
    function filterList(filter) {
        var bMatched = false;
        var list = $container.get(0);

        if (filter != "") {
            $("li", $container).each(
                function (index) {
                    var li = $(this);
                    var text = li.text();
                    var optionText = text.substring(0, filter.length);

                    //not working properly!
                    if (optionText.toLowerCase() == filter.toLowerCase() && !bMatched) {
                        var numberOfStepsToMove = index - active;
                        //move to the selected li
                        //this is flacey... when you try to alert to debug, it will just stop getting the offsetTop ot scrollTop information!!!
                        //if you take out alerts it works fine!
                        moveSelect(numberOfStepsToMove);

                        //I can debug after the moveSelect function!!!
                        //alert(this.offsetTop);

                        //clear selected classes
                        $('li.' + opt.currentClass, $container).removeClass(opt.currentClass);
                        //set as the current item
                        li.addClass(opt.currentClass);

                        bMatched = true;
                    }

                }
            );
        }
        else {
            //go to the start of the list
            active = 0;
            moveSelect(1);
        }
    }

    //PK: set the dropdown filter value
    function setDropdownFilter(value) {
        _sDropdownFilter = value;
    }

    //PK: show the list container
    function showMe() {
        debug("called showMe()");
        //alert("showMe()");
        $container.show();

        //PK: need to maintain scoll position for chrome
        moveSelect(0);
        //alert("end showMe()");
    }

    function hideMe() {
        debug("called hideMe() called on: " + $container.attr("id"));
        debug("called hideMe() $container element length: " + $container.length);
        hasfocus = 0;
        $container.hide();
    }

    function moveSelect(step) {

        var lis = $("li", $container);
        if (!lis || lis.length == 0) return false;
        active += step;
        //loop through list
        if (active < 0) {
            active = lis.size() - 1; //PK: change this to minus one because the array is 0 based
        }
        else if (active >= lis.size()) //PK: canot be equal to size of list
        {
            active = 0;
        }

        scroll(lis, active);
        lis.removeClass(opt.currentClass);

        $(lis[active]).addClass(opt.currentClass);


        //PK: If the container is visible then the user is just scrolling through the options and hasn't selected one
        // if the container isn't visible and the user is scrolling then each change should be a selection
        if (!$container.is(":visible")) {
            setCurrent();
        }
    }

    function scroll(list, active) {
        var el = $(list[active]).get(0);
        var list = $container.get(0);

        //works when I call move from the main event
        if (el.offsetTop + el.offsetHeight > list.scrollTop + list.clientHeight) {
            list.scrollTop = el.offsetTop + el.offsetHeight - list.clientHeight;
        } else if (el.offsetTop < list.scrollTop) {
            list.scrollTop = el.offsetTop;
        }
    }

    function setCurrent() {
        debug("setCurrent()");
        //PK: need to trigger changed event for .net
        var li = $("li." + opt.currentClass, $container).get(0);
        var sCurrentDropdownValue = getCurrentSelected();
        var el = getUserSelectedValue();

        $select.val(el);
        setInputValue($(li).text());

        if (checkIfSelectedItemHasChanged(sCurrentDropdownValue, el)) {
            fireSelectedItemChangedEvent();
        }

        return true;
    }

    //PK: check if value changed
    function checkIfSelectedItemHasChanged() {
        var sCurrentDropdownValue = getCurrentSelected();
        var sUserSelectedValue = getUserSelectedValue();

        return checkIfSelectedItemHasChanged(sCurrentDropdownValue, sUserSelectedValue);
    }

    //PK: check if value changed
    //PARAM sCurrentDropdownValue = current value in the $select.
    //PARAM sUserSelectedValue = value selected by the user in the styled dropdown
    function checkIfSelectedItemHasChanged(sCurrentDropdownValue, sUserSelectedValue) {
        //PK: fire the list changed event
        debug("User user selected value: " + sUserSelectedValue + ". Current dropdown value: " + sCurrentDropdownValue)
        if (sUserSelectedValue != sCurrentDropdownValue) {
            debug("checkIfSelectedItemHasChanged() returns true");
            return true;
        }
        else {
            return false;
        }
    }

    //PK: fire select box changed events
    function fireSelectedItemChangedEvent() {
        if ($.browser.msie) {
            //PK: for .NET validators to work we need to fire the change event with this custom jQuery event fireing
            //got from http://stackoverflow.com/questions/168596/programmatically-triggering-events-in-javascript-for-ie-using-jquery
            $select.fire("change").blur();
        }
        else {
            $select.change();
            //$select.get(0).blur();
        }
    }

    //PK: Gets the value selected by the user in the styled dropdown
    function getUserSelectedValue() {
        var li = $("li." + opt.currentClass, $container).get(0);
        var ar = ('' + li.id).split('_');
        var el = ar[ar.length - 1];
        return el;
    }

    // select value
    function getCurrentSelected() {
        return $select.val();
    }

    // input value
    function getCurrentValue() {
        return $input.val();
    }

    //PK: input value
    function setInputValue(value) {
        debug("setInputValue: " + value);
        if (opt.maxLength != null && opt.maxLength > 0 && value != null && value.length > 0) {
            $input.val(value.substring(0, opt.maxLength));
        }
        else {
            $input.val(value);
        }
        debug("setInputValue: value has been set to: " + $input.val());
    }

    function getSelectOptions(parentid) {
        debug("getSelectOptions");

        var select_options = new Array();
        var ul = null;

        ul = document.createElement('ul');
        $select.children('option').each(function (index) {
            var li = document.createElement('li');
            li.setAttribute('id', parentid + '_' + $(this).val());
            li.innerHTML = $(this).html();
            if ($(this).is(':selected')) {
                setInputValue($(this).text());
                $(li).addClass(opt.currentClass);
                //PK: set the list step
                active = index;
                debug("setting li with current class");
            }
            ul.appendChild(li);
            $(li)
		    .mouseover(function (event) {
		        hasfocus = 1;
		        debug('over on : ' + this.id);
		        jQuery(event.target, $container).addClass(opt.hoverClass);
		    })
		    .mouseout(function (event) {
		        hasfocus = -1;
		        debug('out on : ' + this.id);
		        jQuery(event.target, $container).removeClass(opt.hoverClass);
		    })
		    .click(function (event) {

		        //PK: Not used and is causing error
		        //var fl = $('li.'+opt.hoverClass, $container).get(0);

		        debug('click on :' + li.id);
		        makeLiSelected(li);

		        //PK: TEST 1
		        //setCurrent();

		        //PK:extracted to function    
		        finaliseLiSelection(this);
		    });
        });

        return ul;
    }

    // set li styling and active index counter
    function makeLiSelected(li) {

        $('li.' + opt.currentClass).removeClass(opt.currentClass);
        $(li).addClass(opt.currentClass);

        //PK: need to set the active step property
        active = $("li", $container).index($(li));
    }

    function finaliseLiSelection() {
        var currentLI = $('li.' + opt.hoverClass);

        //PK: get the currently selected li
        if (currentLI.length == 0) {
            currentLI = $('li.' + opt.currentClass);
        }
        else {
            currentLI.removeClass(opt.hoverClass);
            currentLI.addClass(opt.hoverClass);
        }
        finaliseLiSelection(currentLI.get(0));
    }

    //PK: finialises the li selection
    //PARAMTER: li as html object NOT jQuery
    function finaliseLiSelection(li, doFocusOnInput) {
        finaliseLiSelection(li, true);
    }

    function finaliseLiSelection(li, doFocusOnInput) {
        //PK: this must be above setCurrent();
        hideMe();

        //set the selection to the dropdown list
        setCurrent();

        //PK: Once we have made a selection we need to clear the filter string
        setDropdownFilter("");

        //PK: Stops us showing the container again on focus. This is fox for IE
        if (doFocusOnInput) {
            focusOnInput(true);
        }
    }

    //PK: calls the input focus event.
    //PARAM: true if we want to show the list on focus
    function focusOnInput(bSkipShowContainerOnFocus) {
        //PK: If _bSkipShowContainerOnFocusEvent is true it stops us showing the container again on focus
        _bSkipShowContainerOnFocusEvent = bSkipShowContainerOnFocus;
        $input.focus();
    }

    //PK: hide all list that have the same style
    function hIBaseDealDLlListOnPage() {
        debug("hIBaseDealDLlListOnPage()");
        $("." + opt.containerClass).hide();
    }

    //PK: If 2 seconds has elapsed since the last key press then we want to restart the dropdown list filter
    //this is the same functionality as a html dropdown box
    function processTimeSinceLastKeyPress() {
        var currentTime = new Date();
        var timeDiffSeconds = ((currentTime.getTime() - _dateTimeLastKeyPress.getTime()) / 1000);

        //if it has been 3 seconds since the last filter then we need to start the filter gain
        if (timeDiffSeconds > 1) {
            setDropdownFilter("");
        }

        debug('time since last keypress: ' + timeDiffSeconds);
        _dateTimeLastKeyPress = currentTime;
    }

    //PK: put debuging into function
    function debug(text) {
        if (opt.debug) {
            console.log(text);
        }
    }

    debug("Selectbox end");
};

