var DropdownManager =
{
    dropdowns: [],

    add: function(dropdown)
    {
        this.dropdowns.push(dropdown);
    },

    dropdownActivated: function(sourceDropdown)
    {
        this.dropdowns.each(function(dropdown)
        {
            if (dropdown != sourceDropdown)
            {
                dropdown.hideChildren();
                dropdown.resetMainCssState();
            }
        }.bind(this));
    }
}

function DropdownItem(id, value, text)
{
    this.id = id;
    this.value = value;
    this.text = text;
}

DropdownItem.prototype.getDisplayText = function()
{
    return this.text;
}

function FilterAjaxDropdownItem(id, value, text, count)
{
    this.base = DropdownItem;
    this.base(id, value, text);

    this.count = count;
}
FilterAjaxDropdownItem.prototype = new DropdownItem;

FilterAjaxDropdownItem.prototype.getDisplayText = function()
{
    if (this.count == null)
        return this.text;

    return this.text + " (" + this.count + ")";
}

function FilterRangeDropdownItem(id, value, text, count, filterTerm)
{
    this.base = FilterAjaxDropdownItem;
    this.base(id, value, text, count);

    this.filterTerm = filterTerm;
}
FilterRangeDropdownItem.prototype = new FilterAjaxDropdownItem;

function Dropdown(id)
{
    this.id = id;
    this.mainDiv = $("#" + id);
    this.selectedTextDiv = $("#" + id + "-selectedText");
    this.imageDiv = $("#" + id + "-image");
    this.childrenDiv = $("#" + id + "-children");
    this.childrenInnerDiv = $("#" + id + "-children-inner");

    this.items = [];
    this.itemMapping = new HashMap();
    this.itemDivMapping = new HashMap();
    this.itemValueMapping = new HashMap();

    this.lastItem = null;
    this.defaultItem 
    this.currentItem = null;
    this.currentHoverItem = null;

    this.onChangeListeners = [];
    this.lateInitialize = false;
    this.lateInitialized = false;
    //this.lateSelectedItemValue = null;
    //this.lateSelectedItem = null;
    this.basicEventsInitialized = false;
    this.itemEventsInitialized = false;
    this.itemsBoundToHtml = false;
    this.isItemMouseDown = false;

    DropdownManager.add(this);
}

Dropdown.prototype.getSelectedValue = function()
{
    if (this.currentItem == null)
        return null;

    return this.currentItem.value;
}

Dropdown.prototype.addOnChangeListener = function(listener)
{
    this.onChangeListeners.push(listener);
}

Dropdown.prototype.fireOnChange = function(item)
{
    this.onChangeListeners.each(function(listener)
    {
        listener(item);
    });
}

Dropdown.prototype.addItem = function(value, text)
{				
    if (typeof(value) == "string")
    {
        var item = new DropdownItem(value, text);
        this.items.push(item);
    }
    else if (value instanceof DropdownItem)
    {
        this.items.push(value);
    }
}

Dropdown.prototype.bindToHtmlItems = function()
{
    if (this.itemsBoundToHtml)
        return;

    this.itemsBoundToHtml = true;

    this.items.each(function(item)
    {
        var itemDiv = $("#" + item.id);
        this.itemMapping.setItem(item, itemDiv[0]);
        this.itemDivMapping.setItem(itemDiv[0], item);
        this.itemValueMapping.setItem(item.value, itemDiv[0]);
    }.bind(this));
}

Dropdown.prototype.renderItems = function()
{
    this.items.each(function(item)
    {
        var displayText = item.getDisplayText();
        var itemDiv = $("<div class=\"item" + (item.count == 0 ? " off" : "") + "\" alt=\"" + displayText + "\" title=\"" + displayText + "\">" + displayText + "</div>");
        this.childrenInnerDiv.append(itemDiv);
        this.itemMapping.setItem(item, itemDiv[0]);
        this.itemDivMapping.setItem(itemDiv[0], item);
        this.itemValueMapping.setItem(item.value, itemDiv[0]);
    }.bind(this));
}

Dropdown.prototype.initializeFromHtmlItems = function(lateInitialize)
{
    this.lateInitialize = lateInitialize;

    if (this.lateInitialize)
    {
        this.initializeBasicEvents();
    }
    else
    {
        this.bindToHtmlItems();
        this.initializeBasicEvents();
        this.initializeItemEvents();
    }

    // This is needed in Chrome, Safari (more?) or clicking the items in the dropdown can cause overflow:hidden items to change scroll offset.
    document.body.appendChild(this.childrenDiv[0]);
}

Dropdown.prototype.initializeLate = function()
{
    if (!this.lateInitialize || this.lateInitialized)
        return;

    this.lateInitialized = true;    

    this.bindToHtmlItems();
    this.initializeItemEvents();

    if (this.currentItem != null)
        this.setSelectedItemByItem(this.currentItem);
}

Dropdown.prototype.initializeBasicEvents = function()
{
    if (this.basicEventsInitialized)
        return;

    this.basicEventsInitialized = true;

    this.mainDiv.bind("mouseenter", function()
    {
        this.mouseEnter();
    }.bind(this));

    this.mainDiv.bind("mouseleave", function()
    {
        this.mouseLeave();
    }.bind(this));

    this.mainDiv.bind("mousedown", function()
    {
        this.mouseDown();
    }.bind(this));

    this.mainDiv.bind("click", function(e)
    {
        this.click(e);
    }.bind(this));

    this.mainDiv.bind("blur", function(e)
    {
        this.blur(e);
    }.bind(this));

    this.mainDiv.bind("keydown", function(e)
    {
        this.keyDown(e);
    }.bind(this));

    $(document).bind("click", function()
    {
        this.documentClick();
    }.bind(this));
}

Dropdown.prototype.initializeItemEvents = function()
{
    if (this.itemEventsInitialized)
        return;

    this.itemEventsInitialized = true;

    this.items.each(function(item)
    {
        var jItemDiv = $("#" + item.id);

        jItemDiv.bind("mouseenter", function(e)
        {
            this.itemMouseEnter(e);
        }.bind(this));

        jItemDiv.bind("mouseleave", function(e)
        {
            this.itemMouseLeave(e);
        }.bind(this));

        jItemDiv.bind("mouseout", function(e)
        {
            this.itemMouseOut(e);
        }.bind(this));

        jItemDiv.bind("mouseover", function(e)
        {
            this.itemMouseOver(e);
        }.bind(this));

        jItemDiv.bind("click", function(e)
        {
            this.itemClick(e);
        }.bind(this));

        jItemDiv.bind("mousedown", function(e)
        {
            this.itemMouseDown(e);
        }.bind(this));
    }.bind(this));
}

Dropdown.prototype.documentClick = function()
{
    if (!this.childrenShowing())
        return;

    if (this.lastItem != this.currentItem)
        this.fireOnChange(this.currentItem);

    this.hideChildren();
    this.resetMainCssState();
}

Dropdown.prototype.resetMainCssState = function()
{
    this.mainDiv.removeClass("hover");
    this.mainDiv.removeClass("click");
}

Dropdown.prototype.childrenShowing = function()
{
    return this.childrenDiv.css("visibility") == "visible";
}

Dropdown.prototype.hideChildren = function()
{
    this.currentHoverItem = null;
    this.childrenDiv.css("visibility", "hidden");
    this.childrenDiv.scrollTop(0);
}

Dropdown.prototype.showChildren = function()
{
    this.showChildrenInternal();
}

Dropdown.prototype.showChildrenInternal = function()
{
    this.currentHoverItem = null;
    this.lastItem = this.currentItem;

    var documentScrollTop = $(document).scrollTop();
    var dropdownHeight = this.mainDiv.outerHeight(true);
    var documentPosition = A.Element.getPosition(this.mainDiv[0]);
    var position = this.mainDiv.position();
    var windowHeight = $(window).height();
    var firstItem = $("div.item:eq(1)", this.childrenInnerDiv)
    var itemHeight = firstItem.outerHeight(true);

    var heightLeftDown = windowHeight - dropdownHeight - (documentPosition.y - documentScrollTop);
    var heightLeftUp = (documentPosition.y - documentScrollTop);
    var heightLeft = 0;
    var downwards = true;

    if (heightLeftUp > heightLeftDown)
    {
        heightLeft = heightLeftUp;
        downwards = false;
    }
    else
    {
        heightLeft = heightLeftDown;
    }

    var visibleItems = ((heightLeft - (heightLeft % itemHeight)) / itemHeight) - 1;

    if (visibleItems > this.items.length)
        visibleItems = this.items.length;

    // Hard cutoff
    if (visibleItems > 20)
        visibleItems = 20;

    var visibleHeight = visibleItems * itemHeight;

    // If height needed to render is provided downwards (even though we have more space upwards, use downwards rendering).
    if (visibleHeight < heightLeftDown)
        downwards = true;

    var top = (downwards ? documentPosition.y + dropdownHeight - 1 : documentPosition.y - 1 - visibleHeight);

    this.childrenDiv.css( { height: visibleHeight + "px", top: top + "px", left: documentPosition.x + "px" } );
    this.childrenDiv.css("visibility", "visible");

    if (this.currentItem != null)
        this.scrollItemIntoView(this.currentItem);
}

Dropdown.prototype.mouseEnter = function()
{
    this.mainDiv.addClass("hover");
}

Dropdown.prototype.mouseLeave = function()
{
    if (this.childrenShowing())
        return;

    this.mainDiv.removeClass("hover");
    this.mainDiv.removeClass("click");
}

Dropdown.prototype.mouseDown = function(e)
{
    this.initializeLate();

    this.isItemMouseDown = true;
    this.mainDiv.removeClass("hover");
    this.mainDiv.addClass("click");

    if (this.childrenShowing())
        this.hideChildren();
    else
        this.showChildren();
}

Dropdown.prototype.click = function(e)
{
    this.initializeLate();

    e.stopPropagation();
    DropdownManager.dropdownActivated(this);
}

Dropdown.prototype.getValue = function(e)
{
    if (this.currentItem != null && this.currentItem.value != null)
        return this.currentItem.value;

    return "";
}

Dropdown.prototype.blur = function(e)
{
    if (this.isItemMouseDown)
        return;

    if (this.lastItem != this.currentItem)
        this.fireOnChange(this.currentItem);
    
    this.hideChildren();
    this.resetMainCssState();
}

Dropdown.prototype.keyDown = function(e)
{
    var firstShow = false;

    // 9 = TAB, 27 =  (Tab should be covered by the blur event, but Chrome doesn't react to this)
    if (e.which == 9 && this.childrenShowing())
    {
        this.hideChildren();

        if (this.lastItem != this.currentItem)
            this.fireOnChange(this.currentItem);
    }   
    // Esc
    else if (e.which == 27 && this.childrenShowing())
    {
        this.hideChildren();

        if (this.lastItem != this.currentItem)
            this.fireOnChange(this.currentItem);
    }
    // Arrow down
    else if (e.which == 40)
    {
        e.stopPropagation();
        e.preventDefault();

        if (!this.childrenShowing())
        {
            this.showChildren();
            firstShow = true;
            this.lastItem = this.currentItem;
        }

        if (this.currentItem == null && this.currentHoverItem == null)
        {
            this.currentItem = this.items[0];
            this.setSelectedItemByItem(this.currentItem, e);
        }
        else
        {
            var currentIndex = this.items.indexOf(this.currentHoverItem || this.currentItem);
            var newIndex = currentIndex + (firstShow ? 0 : 1);

            if (newIndex > this.items.length - 1)
                newIndex = 0;

            var newItem = this.items[newIndex];

            this.setSelectedItemByItem(this.items[newIndex], e);

            if (this.currentHoverItem != null)
            {
                var hoverDiv = this.itemMapping.getItem(this.currentHoverItem);
                var jHoverDiv = $(hoverDiv);
                jHoverDiv.removeClass("hover");
                this.currentHoverItem = null;
            }
        }
    }
    // Up arrow
    else if (e.which == 38)
    {
        e.stopPropagation();
        e.preventDefault();

        if (!this.childrenShowing())
        {
            this.showChildren();
            firstShow = true;
            this.lastItem = this.currentItem;
        }

        if (this.currentItem == null && this.currentHoverItem == null)
        {
            this.currentItem = this.items[this.items.length - 1];
            this.setSelectedItemByItem(this.currentItem, e);
        }
        else
        {
            var currentIndex = this.items.indexOf(this.currentHoverItem || this.currentItem);
            var newIndex = currentIndex - (firstShow ? 0 : 1);

            if (newIndex < 0)
                newIndex = this.items.length - 1;

            var newItem = this.items[newIndex];

            this.setSelectedItemByItem(this.items[newIndex], e);

            if (this.currentHoverItem != null)
            {
                var hoverDiv = this.itemMapping.getItem(this.currentHoverItem);
                var jHoverDiv = $(hoverDiv);
                jHoverDiv.removeClass("hover");
                this.currentHoverItem = null;
            }
        }
    }
    /*// Alt  key
    else if (e.which == 18)
    {
    }*/
}

Dropdown.prototype.itemMouseEnter = function(e)
{
    $(e.target).addClass("hover");
}

Dropdown.prototype.itemMouseLeave = function(e)
{
    $(e.target).removeClass("hover");
}

Dropdown.prototype.itemMouseOut = function(e)
{
    $(e.target).removeClass("hover");
}

Dropdown.prototype.itemMouseOver = function(e)
{
    var div = $(e.target);
    div.addClass("hover");

    var item = this.itemDivMapping.getItem(div[0]);
    this.currentHoverItem = item;
}

Dropdown.prototype.itemMouseDown = function(e)
{
    this.isItemMouseDown = true;
}

Dropdown.prototype.setSelectedItemByItem = function(item, e)
{
    this.hideCurrentItem();

    var itemDiv = this.itemMapping.getItem(item);
    var jItemDiv = $(itemDiv);

    if (jItemDiv.size() > 0 && item != null)
    {
        jItemDiv.addClass("selected")
        this.scrollItemIntoView(item, e);

        this.currentItem = item;
        this.updateSelectedText(item.getDisplayText());
    }
}

Dropdown.prototype.setSelectedItemByValue = function(value)
{
    this.hideCurrentItem();

    var itemDiv = this.itemValueMapping.getItem(value);
    var jItemDiv = $(itemDiv);
    var item = this.itemDivMapping.getItem(itemDiv);

    if (jItemDiv.size() > 0 && item != null)
    {
        jItemDiv.addClass("selected")
        this.scrollItemIntoView(item);

        this.currentItem = item;
        this.updateSelectedText(item.getDisplayText());
    }
}

Dropdown.prototype.scrollItemIntoView = function(item, e)
{
    if (item == null)
        return;

    var div = this.itemMapping.getItem(item);

    if (div == null)
        return;

    var itemIndex = this.items.indexOf(item);
    var jDiv = $(div);
    var scrollOffset = this.childrenDiv.scrollTop();
    var height = this.childrenDiv.height();
    var itemPosition = jDiv.position();
    var itemHeight = jDiv.outerHeight(true);
    var itemsVisible = (height - (height % itemHeight) / itemHeight);

    var offset = itemIndex * itemHeight;
    
    if (offset < scrollOffset || offset > scrollOffset + height - itemHeight)
    {
        /* TODO: prevent page like scroll when going downwards.
        if (e != null)
        {
            if (e.which = 40)
            {
                
            }
        }*/

        this.childrenDiv.scrollTop(offset);
    }
}

Dropdown.prototype.itemClick = function(e)
{
    this.isItemMouseDown = false;
    e.stopPropagation();

    var itemDiv = e.target;
    var jItemDiv = $(itemDiv);
    jItemDiv.addClass("selected")
    
    var item = this.itemDivMapping.getItem(itemDiv);

    if (item != null)
    {
        var newItemSelected = (this.currentItem != item);
        this.hideCurrentItem();

        this.currentItem = item;
        this.updateSelectedText(item.getDisplayText());

        if (newItemSelected)
            this.fireOnChange(item);
    }

    this.hideChildren();
    this.resetMainCssState();
}

Dropdown.prototype.hideCurrentItem = function(e)
{
    if (this.currentItem != null)
    {
        var currentItemDiv = this.itemMapping.getItem(this.currentItem);

        if (currentItemDiv != null)
        {
            var jCurrentItemDiv = $(currentItemDiv);
            jCurrentItemDiv.removeClass("selected");
        }

        this.currentItem = null;
    }
}

Dropdown.prototype.updateSelectedText = function(text)
{
    if (this.currentItem != null || text != null)
    {
        var currentText = this.selectedTextDiv.text().replace(/\n/g, "").trim();
        var displayText = (text || this.currentItem.getDisplayText());

        if (displayText != currentText)
            this.selectedTextDiv.text(displayText);
    }
}

Dropdown.prototype.getSelectedText = function()
{
    return this.selectedTextDiv.text().replace(/\n/g, "").trim();
}

Dropdown.prototype.getSelectedValue = function()
{
    this.initializeLate();

    if (this.currentItem != null)
        return this.currentItem.value;

    return "";
}

function FilterAjaxDropdown(id)
{
    this.base = Dropdown;
    this.base(id);

    this.loaderImg = $("#" + id + "-loader img");
    this.hideLoaderDelay = 200;
}
FilterAjaxDropdown.prototype = new Dropdown;

FilterAjaxDropdown.prototype.showChildren = function()
{
    this.showLoader();
    window.setTimeout(this.ajaxLoadFinished.bind(this), 1000);
}

FilterAjaxDropdown.prototype.addItem = function(value, text, count)
{				
    if (typeof(value) == "string")
    {
        var item = new FilterAjaxDropdownItem(value, text, count);
        this.items.push(item);
    }
    else if (value instanceof DropdownItem)
    {
        this.items.push(value);
    }
}

FilterAjaxDropdown.prototype.ajaxLoadFinished = function()
{
    this.hideLoader();
    this.showChildrenInternal();
}

FilterAjaxDropdown.prototype.hideLoader = function()
{
    window.setTimeout(this.hideLoaderDelayed.bind(this), this.hideLoaderDelay);
}

FilterAjaxDropdown.prototype.hideLoaderDelayed = function()
{
    this.loaderImg.css("visibility", "hidden");
}

FilterAjaxDropdown.prototype.showLoader = function()
{
    this.loaderImg.css("visibility", "visible");
}