var Weblib = {
    
    defaultConfig: {
        numberOfNavigationItems: 10,
        limit: 10,
        baseUrl: 'http://t34.sanomaonline.hu:8080/search/apro/select'
    },

    base: function(){},
    extend: function(base, spr) {
        var f = function(){};
        if (spr == null) {
            spr = Weblib.base;
        }
        f.prototype = spr.prototype;
        base.prototype = new f();
        base.prototype.constructor = base;
        base.superclass = spr.prototype;
        for (var i=2; i<arguments.length; i++) {
            $.extend(base.prototype, arguments[i]);
        }
    },

    createUUID: function() {
        var s = [], itoh = '0123456789ABCDEF', i = 0;

        for (i = 0; i <36; i++) {
            s[i] = Math.floor(Math.random()*0x10);
        }

        s[14] = 4;
        s[19] = (s[19] & 0x3) | 0x8;

        for (i = 0; i <36; i++) {
            s[i] = itoh[s[i]];
        }

        s[8] = s[13] = s[18] = s[23] = '-';

        return s.join('');
    }

};

// --------------------------------
// Events for weblib
function WeblibEvent($type, $data) {
    this.__constructor($type, $data);
}

Weblib.extend(
    WeblibEvent,
    null,
    {
        _type: null,
        _data: null,
        __constructor: function($type, $data) {
            this._type = $type;
            this._data = $data;
        },

        getType: function() {
            return this._type;
        },

        getData: function() {
            return this._data;
        }
    }
    );

// --------------------------------
// Event listener
function EventListener() {
    this.__constructor();
}

Weblib.extend(
    EventListener,
    null,
    {
        _uuid: null,
        __constructor: function() {
            this._uuid = Weblib.createUUID();
        },

        getUUID: function() {
            return this._uuid;
        },

        on: function($event) {
            throw new Error(
                "No event listener is implemented for " + $event.getType()
                );
        }
    }
    );

function ClosureEventListener($target, $closure) {
    this.__constructor($target, $closure);
}

Weblib.extend(
    ClosureEventListener,
    EventListener,
    {
        _target: null,
        _closure: null,
        __constructor:function($target, $closure) {
            ClosureEventListener.superclass.__constructor.apply(this);
            this._target  = $target;
            this._closure = $closure;
        },

        on: function($event) {
            this._target[this._closure]($event);
        }
    }
    );

// --------------------------------
// Event dispatcher
function EventDispatcher(){}

Weblib.extend(
    EventDispatcher,
    null,
    {
        _listeners : {},

        addEventListener: function($eventType, $listener) {
            if ( !this.hasEventListener($eventType) ) {
                this._listeners[$eventType] = [];
            }
            this._listeners[$eventType].push($listener);
        },

        removeEventListener: function($eventType, $listener) {
            if (this.hasEventListener($eventType)) {
                var $eventListeners = this._listeners[$eventType];
                var uuid = $listener.getUUID();
                $.each($eventListeners, function($index, $elem){
                    if ($elem.getUUID() == uuid) {
                        $eventListeners.splice($index,1);
                        return false;
                    }
                    return true;
                })
            }
        },

        hasEventListener: function($eventType) {
            if ($.isArray(this._listeners[$eventType])) {
                return (this._listeners[$eventType].length > 0);
            }
            return false;
        },

        dispatchEvent: function($event) {
            var $type = $event.getType();

            if (this.hasEventListener($type)) {
                var $eventListeners = this._listeners[$type];
                $.each($eventListeners, function($index, $elem) {
                    $elem.on($event);
                })
            }
        }
    }
    );

function WeblibConfigurable($config) {
    this.__constructor($config);
}

Weblib.extend(
    WeblibConfigurable,
    null,
    {
        _config:null,
        __constructor: function($config) {
            this._config = $config;
        },
        getLimit: function() {
            return this._config.limit;
        },
        getNumberOfNavigationItems: function() {
            return this._config.numberOfNavigationItems;
        },
        getBaseUrl: function() {
            return this._config.baseUrl;
        },
        getMaxNumberBefore: function() {
            return Math.floor((this.getNumberOfNavigationItems()-1) / 2);
        },
        getMaxNumberAfter: function() {
            return this.getNumberOfNavigationItems() - this.getMaxNumberBefore() - 1;
        }
    }
    );
// --------------------------------
// Weblib service
function WeblibService($config) {
    this.__constructor($config);
}

Weblib.extend(
    WeblibService,
    WeblibConfigurable,
    EventDispatcher.prototype,
    {
        _inProgress: false,

        __constructor: function($config) {
            console.log(this.addEventListener);
            WeblibService.superclass.__constructor.apply(this, [$config]);
        },
        execute: function() {
            if (!this.isInProgress()) {
                var _this = this;
                this.setInProgress(true);
                try {
                    $.ajax(
                        this.getBaseUrl(),
                        {
                            data: this.getData(arguments),
                            dataType: 'jsonp',
                            jsonp: 'json.wrf',
                            success: function(data) {
                                _this.onResult(data);
                            },
                            error: function() {
                                _this.onFault();
                            }
                        }
                        );
                } catch (e) {
                    this.onFault(null, 'error', e);
                }
            }
            return false;
        },
        onResult: function(data) {
            this.setInProgress(false);
            console.log(data);
            this.dispatchEvent(new WeblibEvent('ServiceResult', data));
        },

        onFault: function() {
            this.setInProgress(false);
            this.dispatchEvent(new WeblibEvent('ServiceFault'));
        },

        getData: function(args) {
            throw new Error('WeblibService.getData is not implemented');
        },

        isInProgress: function() {
            return this._inProgress;
        },

        setInProgress: function($inProgress) {
            this._inProgress = ($inProgress === true);
        }
    }
    );

function WeblibHighlightSearchService($config) {
    this.__constructor($config);
}

Weblib.extend(
    WeblibHighlightSearchService,
    WeblibService,
    {
        __constructor: function($config) {
            console.log(this,$config,WeblibHighlightSearchService.superclass.__constructor);

            WeblibHighlightSearchService.superclass.__constructor.apply(this,[$config]);
        },
        getData: function(args) {
            return {
                'q'     : args[0],
                'start' : args[1],
                'rows'  : args[2],
                'qt'    : 'hl'
            };
        }
    }
    );

function NavigationManager($config) {
    this.__constructor($config);
}

Weblib.extend(
    NavigationManager,
    WeblibConfigurable,
    {
        __constructor: function($config) {
            NavigationManager.superclass.__constructor.apply(this,[$config]);
        },
        addItemsBefore: function($items, $target) {
            var before = this.getMaxNumberBefore();
            var limit = this.getLimit();
            for (var count=0; ($target.value>=0) && (count<before); $target.value-=limit, count++) {
                $items.unshift([$target.value]);
            }
            return this;
        },
        addMoreItemsBefore: function($items, $target) {
            var navItems = this.getNumberOfNavigationItems();
            var limit = this.getLimit();
            if ($items.length < navItems) {
                for (; ($target.value>=0) && ($items.length < navItems); $target.value -= limit) {
                    $items.unshift([$target.value]);
                }
            }
            if ($target.value>0) {
                $items.unshift([0,'First']);
            }
            return this;
        },
        addItemsAfter: function($items, $target, $all) {
            var after = this.getMaxNumberAfter();
            var limit = this.getLimit();
            for (var count=0; ($target.value < $all) && (count < after); $target.value+=limit, count++) {
                $items.push([$target.value]);
            }
            return this;
        },
        addMoreItemsAfter: function($items, $target, $all) {
            var navItems = this.getNumberOfNavigationItems();
            var limit = this.getLimit();
            if ($items.length < navItems) {
                for (; ($target.value<$all) && ($items.length < navItems); $target.value += limit) {
                    $items.push([$target.value]);
                }
            }
            if ($target.value+limit<$all) {
                $items.push([Math.floor(($all-1)/limit) * limit, 'Last']);
            }
            return this;
        },
        getItems: function($all, $current) {
            var limit = this.getLimit();
            var min={
                'value':$current-limit
                },max={
                'value':$current+limit
                };
            var items = [];

            if ($all <= limit) {
                console.log($all, limit);
                return items;
            }

            items.push([$current]);
            this
            .addItemsBefore(items,min)
            .addItemsAfter(items,max,$all)
            .addMoreItemsAfter(items,max,$all)
            .addMoreItemsBefore(items,min);
            return items;
        }
    }
    )
function WeblibResultRenderer($container, $config) {
    this.__constructor($container, $config);
}

Weblib.extend(
    WeblibResultRenderer,
    WeblibConfigurable,
    EventDispatcher.prototype,
    {
        _container: null,
        _inProgressLayer: null,
        _navigation: null,
        __constructor: function($container, $config) {
            WeblibResultRenderer.superclass.__constructor.apply(this,[$config]);
            this._navigation = new NavigationManager($config);
            this._container = $container;
            this._inProgressLayer = $('.progressLayer', this._container);
        },
       
        setInProgress: function($inProgress) {
            $inProgress
            ? this._inProgressLayer.show()
            : this._inProgressLayer.hide();
        },

        render: function($result) {
            var _this = this;
            this.clean();
            $.each($result.response.docs, function($index, $elem) {
                _this.renderItem($result.highlighting[$elem.id]);
            });
            this.renderNavigation($result);

        },

        renderItem: function($instance) {
            var $item = $('<div></div>').addClass('resultItem');
            $('<p>' + $instance.title + '</p>').appendTo($item);
            $('<p>' + $instance.text + '</p>').appendTo($item);
            this._container.append($item);
        },

        renderNavigation: function($result) {
            var limit = this.getLimit();
            var $navigationContainer = $('<div></div>')
            .css('text-align','center')
            .addClass('navigation');
            $navigationContainer.appendTo(this._container);
            var start = $result.response.start;
            var all = $result.response.numFound;
            var items = this._navigation.getItems(all, start);
            console.log(items);
            if (items.length == 0) {
                return;
            }

            for (var i=0; i<items.length; i++) {
                var $pageIx = Math.floor(items[i][0]/limit) + 1;
                var offset = items[i][0];
                var label = items[i][1];
                this.renderNavigationItem(
                    $navigationContainer,
                    offset, $pageIx, start, label
                    );
            }
        },

        renderNavigationItem: function($container, $start, $pageIx, $current, $label) {
            var _this = this;
            $item = $('<a>' + ($label ? $label : $pageIx) + '</a>')
            .attr('href','javascript:;')
            .css('padding-right','3px')
            .appendTo($container)
            .click(function(){
                _this.pager($start)
                });
            if ($start == $current) {
                $item.addClass('navigationSelected').css('color', 'red');
            }
        },
       
        pager: function($start) {
            this.dispatchEvent(new WeblibEvent('onPage', $start))
        },

        clean: function() {
            this._container.children('.resultItem').remove();
            this._container.children('.navigation').remove();
        }
    }
    );

function WeblibSearchManager($container, $requestInput, $config) {
    this.__constructor($container, $requestInput, $config);
}

Weblib.extend(
    WeblibSearchManager,
    null,
    {
        _service      : null,
        _renderer     : null,
        _requestInput : null,
        _config       : null,

        __constructor: function($container, $requestInput, $config) {
            this._requestInput = $requestInput;
            this._config = $.extend({}, Weblib.defaultConfig, $config);
            console.log(this._config);
            this._initService()._initRenderer($container);
        },

        _initService: function() {
            this._service = new WeblibHighlightSearchService(this._config);
            this._service.addEventListener(
                'ServiceResult',
                new ClosureEventListener(this, 'onSearchResult')
                );
            this._service.addEventListener(
                'ServiceFault',
                new ClosureEventListener(this, 'onSearchFault')
                );
            return this;
        },
        
        _initRenderer: function($container) {
            this._renderer = new WeblibResultRenderer(
                $container,
                this._config);
            this._renderer.addEventListener(
                'onPage',
                new ClosureEventListener(this, 'onPage')
                );
            return this;
        },

        getLimit: function() {
            return this._config.limit;
        },

        getNumberOfNavigationItems: function() {
            return this._config.numberOfNavigationItems;
        },

        getRequest: function() {
            return this._requestInput.val();
        },

        onSearchResult: function(event) {
            this._renderer.setInProgress(false);
            this._renderer.render(event.getData());
        },
        
        onSearchFault: function() {
            this._renderer.setInProgress(false);
            alert('error');
        },

        onPage: function($event) {
            this.search($event.getData())
        },

        search: function($start) {
            this._renderer.setInProgress(true);
            this._service.execute(
                this.getRequest(),
                $start,
                this.getLimit()
                );
            return false;
        }
    }
    )
