JSAN.use("DOM.Ready");

if ( typeof Animation == "undefined" ) Animation = {};

Animation.Resize = function (params) {
    this._initialize(params);
}

Animation.Resize.DEBUG = 0;
Animation.Resize.VERSION = "0.10";

Animation.Resize.DEFAULT_FRAMES = 20;
Animation.Resize.DEFAULT_DURATION = 500;

Animation.Resize.resizeElement = function (params) {
    new Animation.Resize(params);
}

Animation.Resize.prototype._initialize = function (params) {
    this._setParams(params);

    var self = this;
    DOM.Ready.onIdReady( params.elementId,
                         function (elt) { self._startResize(elt) }
                       );
}

Animation.Resize.prototype._setParams = function (params) {
    if ( ! params["elementId"] ) {
        throw new Error("Animation.Resize requires an elementId parameter");
    }

    if ( typeof params["targetWidth"] == "undefined" ) {
        throw new Error("Animation.Resize requires an targetWidth parameter");
    }

    if ( typeof params["targetHeight"]  == "undefined" ) {
        throw new Error("Animation.Resize requires an targetHeight parameter");
    }

    if ( ! params["anchorSides"] ) {
        params["anchorSides"] = {};
    }

    this._targetWidth  = params["targetWidth"];
    this._targetHeight = params["targetHeight"];
    this._anchors = params["anchorSides"];
    this._onFinish  = params["onFinish"];

    this._frameCount = params["frameCount"];
    this._totalDuration = params["totalDuration"];

    if ( typeof this._frameCount == "undefined" ) {
        this._frameCount = Animation.Resize.DEFAULT_FRAMES;
    }

    if ( typeof this._totalDuration == "undefined" ) {
        this._totalDuration = Animation.Resize.DEFAULT_DURATION;
    }

    this._intervalDuration =
        this._totalDuration / this._frameCount;

    if ( this._anchors.left && this._anchors.right ) {
        throw new Error("Cannot anchor both the left and right sides");
    }

    if ( this._anchors.top && this._anchors.bottom ) {
        throw new Error("Cannot anchor both the top and bottom sides");
    }
}

Animation.Resize.prototype._startResize = function (elt) {
    if ( Animation.Resize.DEBUG ) {
        alert( "Resizing: #" + elt.id
               + "\n"
               + "width: " + this._targetWidth
               + "\n"
               + "height: " + this._targetHeight
               + "\n"
               + "Anchors: " + this._anchorsAsString() );
    }

    this._elt = elt;
    this._calcFrames();

    var self = this;
    this._interval =
        setInterval( function () {
            self._doFrame()
        }, this._intervalDuration );
}

Animation.Resize.prototype._anchorsAsString = function () {
    var anchors = [];
    if ( this._anchors["top"] ) {
        anchors.push("top");
    }
    else if ( this._anchors["bottom"] ) {
        anchors.push("bottom");
    }

    if ( this._anchors["left"] ) {
        anchors.push("left");
    }
    else if ( this._anchors["right"] ) {
        anchors.push("right");
    }

    if ( anchors.length ) {
        return anchors.join(", ");
    }
    else {
        return "none";
    }
}

Animation.Resize.prototype._calcFrames = function () {
    var dims = this._calcInitialDimensions();

    var x_total = this._targetWidth  - this._currentWidth;
    var y_total = this._targetHeight - this._currentHeight;

    var x_direction = x_total >= 0 ? 1 : -1;
    var y_direction = y_total >= 0 ? 1 : -1;

    var x_step_base = parseInt( x_total / this._frameCount );
    var x_rem = Math.abs( x_total % this._frameCount );

    var y_step_base = parseInt( y_total / this._frameCount );
    var y_rem = Math.abs( y_total % this._frameCount );

    var frames = [];
    var debug = "";
    for ( i = 0; i < this._frameCount; i++ ) {
        var x_step = x_step_base;
        if ( x_rem-- > 0 ) {
            x_step += 1 * x_direction;
        }

        var y_step = y_step_base;
        if ( y_rem-- > 0 ) {
            y_step += 1 * y_direction;
        }

        frames[i] = { x: this._calcDeltas( i + 1, x_step, "left", "right" ),
                      y: this._calcDeltas( i + 1, y_step, "top", "bottom" ) };

        if ( Animation.Resize.DEBUG ) {
            debug =
                debug
                + "Frame #" + (i + 1) + ": "
                + "  x: { size: " + frames[i].x.size + ", move: " + frames[i].x.move + "}"
                + " - "
                + "y: { size: " + frames[i].y.size + ", move: " + frames[i].y.move + "}"
                + "\n\n";
        }
    }

    if ( Animation.Resize.DEBUG ) { alert(debug); }

    this._frames = frames;
}


Animation.Resize.prototype._calcDeltas = function (frame, step, anchor1, anchor2) {
    var deltas = { size: 0, move: 0 };

    if ( this._anchors[anchor1] ) {
        deltas.size = step;
    }
    else if ( this._anchors[anchor2] ) {
        deltas.move = step;
    }
    else {
        deltas.size = parseInt( step / 2 );
        deltas.move = deltas.size;

        if ( Math.abs(step) % 2 ) {
            /* It's important to alternate where the extra pixel is
               allocated or else by the end of the animation we'll
               have moved one side 25px (one per frame) more than the
               other */
            if ( frame % 2 ) {
                deltas.size += deltas.size < 0 ? -1 : 1;
            }
            else {
                deltas.move += deltas.move < 0 ? -1 : 1;
            }
        }
    }

    return deltas;
}

Animation.Resize.prototype._calcInitialDimensions = function () {
    var dims;

    var width  = this._sizeFromStyle("width");
    var height = this._sizeFromStyle("height");

    if ( typeof width == "undefined" || typeof height == "undefined" ) {
        if ( this._eltIsZeroSize() ) {
            width = 0;
            height = 0;
        }
        else {
            throw Error("The element to be resized must have an explicit width and height style in pixels, or be zero size");
        }
    }

    this._currentWidth  = width;
    this._currentHeight = height;
}

Animation.Resize.prototype._sizeFromStyle = function (propName) {
    var styleText = Animation.Resize._textForStyle( this._elt, propName );

    var match = styleText.match( /(\d+)(%|em|pt|px)?/ );

    if ( ! match ) {
        return undefined;
    }

    var units = match[2];
    if ( typeof units == "undefined" || units == "px" ) {
        return parseInt( match[1] );
    }
    else {
        return undefined;
    }
}

Animation.Resize._textForStyle = function ( elt, propName ) {
    if ( document.defaultView ) {
        return document.defaultView.getComputedStyle( elt, null ).getPropertyValue(propName);
    }
    else {
        return eval( "elt.currentStyle." + propName );
    }
}

Animation.Resize.prototype._eltIsZeroSize = function () {
    var width;
    var height;

    if ( this._elt.style.display != "none" ) {
        width  = this._elt.clientWidth;
        height = this._elt.clientHeight;
    }
    else {
        /* Taken from Prototype, which comments that when display is
         * none, clientWidth and clientHeight are always 0 */
        var vis = this._elt.style.visibility;
        var pos = this._elt.style.position;

        this._elt.style.visibility = 'hidden';
        this._elt.style.position = 'absolute';

        width  = this._elt.clientWidth;
        height = this._elt.clientHeight;

        this._elt.style.display = 'none';
        this._elt.style.position = pos;
        this._elt.style.visibility = vis;
    }

    if ( width == 0 && height == 0 ) {
        return true;
    }
    else {
        return false;
    }
}

Animation.Resize.prototype._doFrame = function () {
    if ( this._frames.length ) {
        this._applyStep( this._frames.shift() );

        if ( this._isLastFrame() ) {
            this._finish();
        }
    }
}

/* The animation looks better to me if the move part comes before the
 * resize. */
Animation.Resize.prototype._applyStep = function (step) {
    if ( step.x.size || step.x.move ) {
        if ( step.x.move ) {
            var left = this._elt.offsetLeft;
            left -= step.x.move;

            this._elt.style.left = left + "px";
        }

        this._currentWidth = this._currentWidth + step.x.size + step.x.move;
        this._elt.style.width = this._currentWidth + "px";
    }

    if ( step.y.size || step.y.move ) {
        if ( step.y.move ) {
            var top = this._elt.offsetTop;
            top -= step.y.move;

            this._elt.style.top = top + "px";
        }

        this._currentHeight = this._currentHeight + step.y.size + step.y.move;
        this._elt.style.height = this._currentHeight + "px";
    }

    if ( Animation.Resize.DEBUG ) {
        alert( "Current width: " + this._elt.style.width
               + "\n"
               + "Current height: " + this._elt.style.height
               + "\n"
               + "Current top: " + this._elt.style.top
               + "\n"
               + "Current left: " + this._elt.style.left );
    }
}

Animation.Resize.prototype._isLastFrame = function () {
    if ( this._frames.length ) {
        return false;
    }
    else {
        return true;
    }
}

Animation.Resize.prototype._finish = function () {
    this._clearInterval();
    if ( this._onFinish ) {
        this._onFinish();
    }
}

Animation.Resize.prototype.cancel = function () {
    this._clearInterval();
}

Animation.Resize.prototype._clearInterval = function () {
    clearInterval( this._interval );
}


/*

*/

