/*
    Scrollbar: class encapsulating scrollbar behaviour

    Author: Theo M. Gerrits (tmg)

    Dependencies: Logger.js
                  Timer.js
                  Layer.js
                  Scrollablelayer.js

    JavaScript1.5

    Copyright (C) 2005 Ontwerpfontein
*/

/*
Description:
    The scrollbar consist of the following script components:
      - scrollable area: ScrollableLayer
      - bar area: Layer
      - thumb: Layer
    Moreover, there should also be additional visual components:
      - Up arrow: for scrolling the scrollable area DOWN, and the thumb UP
      - Down arrow: for scrolling the scrollable area UP, and the thumb DOWN
    The bar area has a bounding rectangle with coordinates (top, left) and size width x height.
    The thumb will slide along the 'scroll line'. This line has coordinates (startX, startY) to (endX, endY).
    The default coordinates of this line are (left + width/2, top) to (left + width/2, top + height). This results in a vertical scrollBar.
    The thumb will slide along the scroll line, taking into account its own dimensions: from (startX - thumbWidth/2, startY - thumbHeight/2) to (endX - thumbWidth/2, endY - thumbHeight/2)
    
    The thumb will automatically adjust to the correct position on the scroll line via the following mechanism:
    1. define the onSlideStep() method of the scrollableArea content layer so, that it will call the updateThumbPosition() method of the Scrollbar.
    2. this method will then calculate the correct position of the thumb
    N.B.: This only works for sliding!
    
    TODO:
    - clicking in the bar area will 'page scroll'.
    - improve the connection mechanism: use subscriber/listener
*/

/*
Description     : constructor
Parameters      : id: id of the Scrollbar
                  isVertical: (optional) boolean indicating type of scrollbar
                  scrollableLayer: (optional) ScrollableLayer to attach the scrollbar to
Properties      :
        id: id of the Bcrollbar
        isVertical: true if the scrollbar is vertical (default), false if it is horizontal
    Components:
        scrollableArea
        bar
        thumb
    Position and size of bar:
        left
        top
        width
        height
    Scroll line:
        startX
        startY
        endX
        endY
    Thumb:
        thumbStartX: initial horizontal position of thumb in pixels
        thumbStartY: initial vertical position of thumb in pixels
        thumbMaxDisplacementX
        thumbMaxDisplacementY
    Thumb dragging:
        mouseOffsetX: horizontal offset of mouse to thumb when the thumb was clicked
        mouseOffsetY: vertical offset of mouse to thumb when the thumb was clicked
        orgDocumentOndragstart
        orgDocumentOnmouseup
        orgDocumentOnmousemove
        orgDocumentOnmouseout
*/
function Scrollbar (id, isVertical, scrollableLayer)
{
    this.id = id;
    this.isVertical = (isVertical != null) ? isVertical : true;
    
    this.scrollableArea = (scrollableLayer) ? scrollableLayer : new ScrollableLayer(this.id);
    this.bar = new Layer(this.id + Scrollbar.prototype.BAR_SUFFIX);
    this.thumb = new Layer(this.id + Scrollbar.prototype.THUMB_SUFFIX);

    this.left = this.bar.left;
    this.top = this.bar.top;
    this.width = this.bar.width;
    this.height = this.bar.height;

    if (isVertical)
        this.setVerticalScrollLine();
    else
        this.setHorizontalScrollLine();
    
    // connect content area with the scrollbar for onSlidestep()
    // ### better to use a subscriber array
    if (!this.scrollableArea.contentLayer.attachedScrollBars)
        this.scrollableArea.contentLayer.attachedScrollBars = new Array();
    this.scrollableArea.contentLayer.attachedScrollBars.push(this);
    // ### better to define a notifySubscriber with a 'notify' method
    this.scrollableArea.contentLayer.onSlideStep = scrollbarUpdateScrollbars;
    
    this.mouseOffsetX = 0;
    this.mouseOffsetY = 0;
    this.thumb.htmlElement.scrollbar = this;
    this.thumb.htmlElement.onmousedown = onScrollbarThumbDragStart;

    this.updateThumbPosition();
}


// constants
Scrollbar.prototype.BAR_SUFFIX = "Bar";
Scrollbar.prototype.THUMB_SUFFIX = "Thumb";

Scrollbar.prototype.STEP_SIZE = 3;
Scrollbar.prototype.PAGE_SIZE = 20;


// Description     : define the scroll line
// Parameters      : start end end coordinates of line in pixels
// Returns         : void
Scrollbar.prototype.setScrollLine = function (startX, startY, endX, endY)
{
    this.startX = startX;
    this.startY = startY;
    this.endX = endX;
    this.endY = endY;

    this.thumbStartX = this.startX - this.thumb.width/2;
    this.thumbStartY = this.startY - this.thumb.height/2;
    this.thumbMaxDisplacementX = this.endX - this.startX;
    this.thumbMaxDisplacementY = this.endY - this.startY;
}

// Description     : define the default vertical scroll line
// Parameters      : none
// Returns         : void
Scrollbar.prototype.setVerticalScrollLine = function ()
{
    this.setScrollLine(this.left + this.width/2, this.top, this.left + this.width/2, this.top + this.height);
}

// Description     : define the default horizontal scroll line
// Parameters      : none
// Returns         : void
Scrollbar.prototype.setHorizontalScrollLine = function ()
{
    this.setScrollLine(this.left, this.top + this.height/2, this.left + this.width, this.top + this.height/2);
}

// Description     : scroll and keep scrolling the scrollable area DOWN, and the thumb UP
// Parameters      : none
// Returns         : void
Scrollbar.prototype.up = function ()
{
    this.scrollableArea.scrollDown();
}

// Description     : scroll and keep scrolling the scrollable area UP, and the thumb DOWN
// Parameters      : none
// Returns         : void
Scrollbar.prototype.down = function ()
{
    this.scrollableArea.scrollUp();
}

// Description     : scroll and keep scrolling the scrollable area RIGHT, and the thumb LEFT
// Parameters      : none
// Returns         : void
// Remark          : 'left()' is not a valid function name
Scrollbar.prototype.goLeft = function ()
{
    this.scrollableArea.scrollRight();
}

// Description     : scroll and keep scrolling the scrollable area LEFT, and the thumb RIGHT
// Parameters      : none
// Returns         : void
Scrollbar.prototype.goRight = function ()
{
    this.scrollableArea.scrollLeft();
}

// Description     : stop scrolling immediately
// Parameters      : none
// Returns         : void
Scrollbar.prototype.stop = function ()
{
    this.scrollableArea.stop();
}

// Description     : scroll 1 step up
// Parameters      : none
// Returns         : void
Scrollbar.prototype.stepUp = function ()
{
    this.moveBy(null, this.STEP_SIZE);
}

// Description     : scroll 1 step down
// Parameters      : none
// Returns         : void
Scrollbar.prototype.stepDown = function ()
{
    this.moveBy(null, -this.STEP_SIZE);
}

// Description     : scroll 1 page up
// Parameters      : none
// Returns         : void
Scrollbar.prototype.pageUp = function ()
{
    this.moveBy(null, this.PAGE_SIZE);
}

// Description     : scroll 1 page down
// Parameters      : none
// Returns         : void
Scrollbar.prototype.pageDown = function ()
{
    this.moveBy(null, -this.PAGE_SIZE);
}

// Description     : set the scrollable area to a certain position
// Parameters      : the position coordinates in pixels
// Returns         : void
Scrollbar.prototype.jumpTo = function (x, y)
{
    this.scrollableArea.jumpTo(x, y);
    this.updateThumbPosition();
}

// Description     : scroll by a certain displacement
// Parameters      : the displacement in pixels
// Returns         : void
Scrollbar.prototype.moveBy = function (dx, dy)
{
    this.scrollableArea.scrollBy(-dx, -dy);
}

// Description     : call the updateThumbPosition() method of the attached scrollbars
// Parameters      : none
// Returns         : void
// Remark          : method of attached srollable area content layer
function scrollbarUpdateScrollbars()
{
    for (var i = 0; i < this.attachedScrollBars.length; i++)
        this.attachedScrollBars[i].updateThumbPosition();
}

// Description     : update the position of the thumb so that it reflects the scrolling position of the scrollableArea
// Parameters      : none
// Returns         : void
Scrollbar.prototype.updateThumbPosition = function ()
{
    if (this.isVertical)
    {
        // startPos + getVerticalScrollPercentage() * lineVector
        var x = this.thumbStartX + this.scrollableArea.getVerticalScrollPercentage()*this.thumbMaxDisplacementX;
        var y = this.thumbStartY + this.scrollableArea.getVerticalScrollPercentage()*this.thumbMaxDisplacementY;
        this.thumb.moveTo(x, y);
    }
    else
    {
        // startPos + getHorizontalScrollPercentage() * lineVector
        var x = this.thumbStartX + this.scrollableArea.getHorizontalScrollPercentage()*this.thumbMaxDisplacementX;
        var y = this.thumbStartY + this.scrollableArea.getHorizontalScrollPercentage()*this.thumbMaxDisplacementY;
        this.thumb.moveTo(x, y);
    }
}

function onScrollbarThumbDragStart (e)
{
    this.scrollbar.thumbDragStart(xb.getEvent(e));
}

// Description     : start dragging the thumb
// Parameters      : e: the event
// Returns         : void
Scrollbar.prototype.thumbDragStart = function (e)
{
    this.isDragging = true;

    this.orgDocumentOndragstart = document.ondragstart; // Internet Explorer 6
    this.orgDocumentOnmouseup = document.onmouseup;
    this.orgDocumentOnmousemove = document.onmousemove;
    this.orgDocumentOnmouseout = document.onmouseout;

    // stop default dragging behaviour
    if (e.preventDefault)
        e.preventDefault(); // Mozilla
    else
        document.ondragstart = new Function("return false;"); // Internet Explorer 6

    this.mouseOffsetX = this.thumb.left - e.clientX;
    this.mouseOffsetY = this.thumb.top - e.clientY;

    document.onmouseup = onScrollbarThumbDragStop;
    document.onmouseout = onScrollbarThumbDragStop; // ### check for target???
    document.onmousemove = onScrollbarThumbMoved;
    document.currentScrollbar = this;
    LOG.debug("Drag start: " + this.mouseOffsetX + ", " + this.mouseOffsetY);
}

// try to detect moving outside the browser window with the mouse button down:
// this would create a problem, since if the user releases the mouse button outside the window, no element would get a mouseup event.
function isNearlyOutsideWindow (e)
{
    return e.type == "mouseout" && e.target.id == "BackgroundSizeMeasurer";
}

function onScrollbarThumbDragStop (e)
{
    e = xb.getEvent(e);
    if (e.type == "mouseup" || isNearlyOutsideWindow(e))
        this.currentScrollbar.thumbDragStop();
}

// Description     : stop dragging the thumb
// Parameters      : none
// Returns         : void
Scrollbar.prototype.thumbDragStop = function ()
{
    LOG.debug("dragging stopped");
    document.ondragstart = this.orgDocumentOndragstart;
    document.onmouseup = this.orgDocumentOnmouseup;
    document.onmousemove = this.orgDocumentOnmousemove;
    document.onmouseout = this.orgDocumentOnmouseout;
    this.isDragging = false;
}

function onScrollbarThumbMoved (e)
{
    e = xb.getEvent(e);
    this.currentScrollbar.thumbMoved(e);
}

// Description     : update the scroll position when the thumb moves
// Parameters      : e: the event
// Returns         : void
Scrollbar.prototype.thumbMoved = function (e)
{
    if (this.isDragging)
    {
        var x = e.clientX;
        var y = e.clientY;
        
        if (this.isVertical)
        {
            var thumbY = Math.max(Math.min(y + this.mouseOffsetY, this.thumbStartY + this.thumbMaxDisplacementY), this.thumbStartY);
            this.thumb.moveTo(null, thumbY);
            this.scrollableArea.jumpRelativeTo(null, (thumbY - this.thumbStartY)/this.thumbMaxDisplacementY);
        }
        else
        {
            var thumbX = Math.max(Math.min(x + this.mouseOffsetX, this.thumbStartX + this.thumbMaxDisplacementX), this.thumbStartX);
            this.thumb.moveTo(thumbX);
            this.scrollableArea.jumpRelativeTo((thumbX - this.thumbStartX)/this.thumbMaxDisplacementX);
        }
    }
}


// Description     : TODO
// Parameters      : none
// Returns         : void
function XBrowser ()
{
}

// Description     : TODO
// Parameters      : none
// Returns         : void
XBrowser.prototype.getElementById = function ()
{
    // TODO: implement
}

// Description     : TODO
// Parameters      : none
// Returns         : void
XBrowser.prototype.getEvent = function (e)
{
    if (!e)
        e = window.event; // Internet Explorer 6
    if (!e.target)
        e.target = e.srcElement; // Internet Explorer 6
        
    return e;
}

xb = new XBrowser();
