smoothScroll = function(scrollArea, opts) {
    var accelFactor;
    var deaccelFactor;
    var stopDelta; //when the speed is less than stopDelta, scrolling stops
    
    //interval between calling timerTick function
    //the less value, the faster scroll effect
    var timerDelta;
    
    var container;
    var timer = false;
    var timerId;
    
    var isMouseDown = false;
    var basePoint = {x:0, y:0}; //recorded at mouseDown event
    var speed = {x:0.0, y:0.0};
    var downTime; //recorded at mouseDown event
    var deltaTime; //time between mouseDown and mouseUp events
    
    var mouseX;
    var mouseY;
    
    /**
    * Checks if element el is inside the container (inclusive)
    **/
    var isInsideContainer = function(el) {
        var p = el;
        
        while (p !== null && (typeof p !== 'undefined')) {
            if (p === container) {
                return true;
            }
            p = p.parentElement || p.parentNode; // webkit || mozilla
        }
        return false;
    }
    
    var mouseIsOut = function(event) {
        // Sometimes mouseout event is dispatched inside the container.
        // In this case, do nothing and return
        if (isInsideContainer(event.toElement || event.relatedTarget)) {
            return;
        }
        
        // Code below prevents undesirable behavior,
        // while you click inside and drag outside a container area.
        if (isMouseDown) {
            isMouseDown = false;
        }
    }
    
    var mouseIsDown = function(event) {
        downTime = (new Date()).getTime();
        isMouseDown = true;
        
        basePoint.x = event.clientX;
        basePoint.y = event.clientY;
        speed.x = 0;
        speed.y = 0;
        
        if (timer === false) {
            timer = true;
            timerId = setInterval(timerTick, timerDelta);
        }
    }
    
    var mouseIsUp = function(event) {
        var mx, my, delta;
        
        if (isMouseDown) {
            isMouseDown = false;
            
            deltaTime = (new Date()).getTime() - downTime;
            mx = event.clientX;
            my = event.clientY;
            delta = {x:mx-basePoint.x, y:my-basePoint.y};
            
            speed.x = accelFactor * delta.x/deltaTime;
            speed.y = accelFactor * delta.y/deltaTime;
        }
    }
    
    var timerTick = function() {
        var mx, my, delta;
        var sdx, sdy;
        
        if (!container) {
            //we need to return becouse dispose function was called
            return;
        }
        
        if (isMouseDown) {
            mx = mouseX;
            my = mouseY;
            delta = {x:mx-basePoint.x, y:my-basePoint.y};
            
            speed.x = accelFactor * delta.x * 0.002;
            speed.y = accelFactor * delta.y * 0.002;
        }
        
        sdx = Math.abs(speed.x);
        sdy = Math.abs(speed.y);
        
        // stop scrolling, if the speed is very low
        if ((sdx < stopDelta || sdy < stopDelta) && !(sdx > stopDelta || sdy > stopDelta) ) {
            speed.x = 0;
            speed.y = 0;
            
            if (!isMouseDown) {
                clearInterval(timerId);
                timerId = undefined;
                timer = false;
            }
            return;
        }
        
        // scrolling
        
        if (speed.x > 0) {
            speed.x -= speed.x/deaccelFactor;
            
            if (container.scrollLeft > 0) {
                container.scrollLeft -= speed.x;
            }
        } else if (speed.x < 0) {
            speed.x += -speed.x/deaccelFactor;

            if (container.scrollLeft < container.scrollWidth) {
                container.scrollLeft -= speed.x;
            }
        }
        
        if (speed.y > 0) {
            speed.y -= speed.y/deaccelFactor;
            
            if (container.scrollTop > 0) {
                container.scrollTop -= speed.y;
            }
        } else if (speed.y < 0) {
            speed.y += -speed.y/deaccelFactor;

            if (container.scrollTop < container.scrollHeight) {				
                container.scrollTop -= speed.y;				
            }
        }
    }
    
    var dispose = function() {
        clearInterval(timerId);
        timerId = undefined;
        timer = false;
        disconnect(container, "mouseout", mouseIsOut, false);
        disconnect(container, "mousedown", mouseIsDown, false);
        disconnect(container, "mouseup", mouseIsUp, false);
        disconnect(document.body, "mousemove", mousePositionChanged, false);
        container = null;
    }
    
    var mousePositionChanged = function(event) {
        mouseX = event.clientX;
        mouseY = event.clientY;
    }
    
    /**
    * Main function
    * el - an HTML element which will be smooth
    */
    var initElement =  function(elm) {
        container = elm;
        
        connect(document.body, 'mousemove', mousePositionChanged);
        
        elm.ondragstart = function() { return false; };
        elm.onmousedown = function() { return false; };
        elm.onselectstart = function() { return false; };
        elm.style.MozUserSelect = "none";
        
        connect(elm, 'mouseout', mouseIsOut);
        connect(elm, 'mousedown', mouseIsDown);
        connect(elm, 'mouseup', mouseIsUp);
    }
    
    var connect = function(element, eventType, callback) {
        var fn;
        
        if (document.attachEvent) {
            fn = function(el, et, clk) {
                return el.attachEvent('on'+et, clk);
            }
        } else {
            fn = function(el, et, clk) {
                return el.addEventListener(et, clk, false);
            }
        }
        
        return fn(element, eventType, callback);
    };
    
    var disconnect = function(element, eventType, callback) {
        var fn;
        
        if (document.detachEvent) {
            fn = function(el, et, clk) {
                return el.detachEvent('on'+et, clk);
            }
        } else {
            fn = function(el, et, clk) {
                return el.removeEventListener(et, clk, false);
            }
        }
        
        return fn(element, eventType, callback);
    }
    
    var condValue = function(cond, val) {
        return (typeof cond === 'undefined') ? val : cond;
    }
    
    var publicInterface = {};
    publicInterface.dispose = dispose;
    
    return function(el, op) {
        if (!op) { op = {}; }
        accelFactor = condValue(op.accelFactor, 20);
        deaccelFactor = condValue(op.decelFactor, 50);
        stopDelta = condValue(op.stopDelta, 0.21);
        timerDelta = condValue(op.timerDelta, 30);
            
        initElement(el);
        return publicInterface;
    }(scrollArea, opts);
};
