$.fn.longlist = function (options)
{

    var _this = this;

    // Extend our default options with those provided.
    // Note that the first arg to extend is an empty object -
    // this is to keep from overriding our "defaults" object.
    var _opts = $.extend({}, $.fn.longlist.defaults, options);
    _opts.leftWidth = _opts.width - 20;
    _opts.height = _opts.count * (_opts.elementHeight + _opts.elementHeightDiff);

    _this.options = function ()
    {
        return _opts;
    };
    
    var _heightSum = _opts.elements.length * (_opts.elementHeight + _opts.elementHeightDiff);
    var _heightSumData = (_opts.count + 1) * (_opts.elementHeight + _opts.elementHeightDiff);
    var _currentIndex = _opts.initialIndex;
    var _isScrollBusy = false;

    this.css('width', _opts.width + 'px');
    this.css('height', _opts.height + 'px');
    this.css('border', _opts.border);
    this.css('overflow', 'hidden');
    this.css('position', 'relative');
    this.css('cursor', 'default');

    function doScrolling (ev, data)
    {
        if (_isScrollBusy) {
            return;
        }
        _isScrollBusy = true;

        var element = _scrollCol;
        var listIndexFloat = element[0].scrollTop / (_opts.elementHeight + _opts.elementHeightDiff);
        var listIndex = Math.floor(listIndexFloat);
        
        _dataCol.css('top', -1 * (listIndexFloat - listIndex) * (_opts.elementHeight + _opts.elementHeightDiff));

        if (_currentIndex !== listIndex || (data && data.forceBuild)) {
            buildContent(listIndex);
        }

        _isScrollBusy = false;
    }

    function buildContent (beginCount)
    {
        if (beginCount + _opts.count > _opts.elements.length) {
            beginCount = (_opts.elements.length - _opts.count <= 0) ? 0 : (_opts.elements.length - _opts.count);
        }
        _currentIndex = beginCount;

        endCount = beginCount + _opts.count + 1;

        if (endCount > _opts.elements.length) {
            endCount = _opts.elements.length;
        }

        var content = [];
        var elementTagName = _opts.renderType === 'div' ? 'div' : 'li';
        
        for (var i = beginCount; i < endCount; i++) {

            var element = _opts.elements[i];

            if (!element) {
                break;
            }
            var styles = getVal(element, 'styles', i) || '';
            var html = getVal(element, 'html', i) || '';
            var styleClass = getVal(element, 'styleClass', i) || '';
            
            content.push('<');
            content.push(elementTagName);
            content.push(' class="');
            content.push(styleClass);
            content.push('" style="');
            content.push(styles);
            content.push('height: ');
            content.push(_opts.elementHeight);
            content.push('px; "');
            content.push('listindex="');
            content.push(i);
            content.push('"');
            content.push('>');
            content.push(html);
            content.push('</');
            content.push(elementTagName);
            content.push('>');
        }
        _dataCol.html(content.join(''));
    }

    function getVal (element, item, arg)
    {
        if (element[item] === undefined || element[item] === null)
            return null;
        if (typeof(element[item]) === 'function') {
        	return element[item](element, arg, _this);
        }
        return element[item];
    }

    function disableSelection (target)
    {
        target.unselectable = "on";
        target.onselectstart = function ()
        {
            return false;
        };
        if (target.style) {
            target.style.MozUserSelect = "none";
        }
    }
    
    var _containerTagName = _opts.renderType === 'div' ? 'div' : 'ul';

    this
            .html('<'+_containerTagName+' class="datacol" style="height:'
                    + _heightSumData + 'px; position: absolute; left: 0px; margin-bottom:10px; overflow:hidden; width:'
                    + _opts.leftWidth
                    + 'px;"></'+_containerTagName+'>'
                    + '<div class="scrollcol" style="position: absolute; right: 0px; float:left;overflow-y: scroll;height: '
                    + _opts.height
                    + 'px;width: 20px;overflow-x: hidden;">'
                    + '<div class="scrollrange" style="height:'
                    + _heightSum + 'px"></div></div>');

    var _scrollCol = _this.children('.scrollcol');
    var _scrollRange = _scrollCol.children('.scrollrange');
    var _dataCol = _this.children('.datacol');

    buildContent(_currentIndex);

    _dataCol.unbind('click.itemclick').bind('click.itemclick', function (ev, data)
    {
        Phx.Event.stop(ev);
        var htmlElement = null;
        var listindex = $(ev.target).attr('listindex');
        if (listindex !== undefined && listindex !== null && listindex !== '') {
            htmlElement = $(ev.target);
        } else {
            htmlElement = $(ev.target).parents('[listindex]');
            listindex = htmlElement.attr('listindex');
        }
        if (listindex !== null && listindex !== undefined && _opts.elements[listindex] !== undefined) {
            setTimeout(function ()
            {
                _this.trigger('itemclick', {
                    listindex : listindex,
                    element : _opts.elements[listindex],
                    htmlElement : htmlElement,
                    list : _this
                })
            }, 1);
        }
    });
    
    _dataCol.unbind('dblclick.itemdblclick').bind('dblclick.itemdblclick', function (ev, data)
    {
        Phx.Event.stop(ev);
        var htmlElement = null;
        var listindex = $(ev.target).attr('listindex');
        if (listindex !== undefined && listindex !== null && listindex !== '') {
            htmlElement = $(ev.target);
        } else {
            htmlElement = $(ev.target).parents('[listindex]');
            listindex = htmlElement.attr('listindex');
        }
        if (listindex !== null && listindex !== undefined && _opts.elements[listindex] !== undefined) {
            setTimeout(function ()
            {
                _this.trigger('itemdblclick', {
                    listindex : listindex,
                    element : _opts.elements[listindex],
                    htmlElement : htmlElement,
                    list : _this
                })
            }, 1);
        }
    });    

    _scrollCol.scroll(function ()
    {
        setTimeout(doScrolling, 20)
    });
    
    _this.scrollToEnd = function () 
    {
        setTimeout(function() {
            _scrollCol[0].scrollTop = _scrollRange.height();
        }, 30);
    };

    _this.find('.datacol').bind('mousewheel', function (event, delta)
    {
        var currentTop = _scrollCol[0].scrollTop;
        delta = ($.browser.opera) ? (delta * (-1)) : delta;
        if (delta > 0) {
            _scrollCol[0].scrollTop = ((currentTop - 16) >= 0) ? (currentTop - 16) : 0;
        }

        if (delta < 0) {
            _scrollCol[0].scrollTop = ((currentTop + 16) >= 0) ? (currentTop + 16) : 0;
        }

        Phx.Event.stop(event);
    });

    _this.add = function (index, entries, updateView)
    {
        if (updateView === undefined || updateView === null) {
            updateView = true;
        }

        if (index < 0) {
            if (!(entries instanceof Array)) {
                _opts.elements.push(entries);
            } else {
                _opts.elements = _opts.elements.concat(entries);
            }
        } else {
            if (!(entries instanceof Array)) {
                _opts.elements.splice(index, 0, entries);
            } else {
                var arr1 = _opts.elements.slice(0, index);
                var arr2 = _opts.elements.slice(index);
                _opts.elements = arr1.concat(entries).concat(arr2);
            }
        }
        if (updateView) {
            buildContent(_currentIndex);
        }
        _heightSum = _opts.elements.length * (_opts.elementHeight + _opts.elementHeightDiff);
        _scrollRange.css('height', _heightSum + 'px');
    };

    _this.refresh = function (refreshScrollRange)
    {
        if (refreshScrollRange) {
            _heightSum = _opts.elements.length * (_opts.elementHeight + _opts.elementHeightDiff);
            _scrollRange.css('height', _heightSum + 'px');        	
        }
    	doScrolling(null, {
            forceBuild : true
        });
    };

    _this.getElements = function ()
    {
        return _opts.elements;
    };

    _this.setElements = function (elements)
    {
        _opts.elements = elements;
        _heightSum = _opts.elements.length * (_opts.elementHeight + _opts.elementHeightDiff);
        _scrollRange.css('height', _heightSum + 'px');
        doScrolling(null, {
            forceBuild : true
        });
    };

    _this.remove = function (index, count, updateView)
    {
        if (updateView === undefined || updateView === null) {
            updateView = true;
        }
        _opts.elements.splice(index, count || 1);
        _heightSum = _opts.elements.length * (_opts.elementHeight + _opts.elementHeightDiff);
        _scrollRange.css('height', _heightSum + 'px');
        if (updateView) {
            doScrolling(null, {
                forceBuild : true
            });
        }
    };

    if (_opts.disableSelection) {
        disableSelection(_dataCol[0]);
    };

    return this;
};

// plugin defaults - added as a property on our plugin function
$.fn.longlist.defaults = {
    count : 5,
    elementHeight : 20,
    elementHeightDiff : 0,
    width : '200',
    elements : [],
    renderType : 'div',
    border : "1px solid #000000",
    initialIndex : 0,
    disableSelection : true
};
