

//=========================================================
//
// BgAnim constructor
//
function BgAnim(id, anim, actors) {

    // Properties

    this.active = false;
    this.activeSessionId = 0;


    // Methods

    // init()

    this.init = function(actorNum) {
        anim.width  = $('#' + anim.id).width();
        anim.height = $('#' + anim.id).height();

        // Duration of each frame of animation in msec
        anim.speed = Math.round(1000 / anim.fps);

        for (var i = 0; i < actors.length; i++) {
            var node = actors[i].node;
            var image = actors[i].image;
            var motion = actors[i].motion;

            $('#' + node.id).width(anim.width).height(anim.height);
            node.halfWidth  = node.width >> 1;
            node.halfHeight = node.height >> 1;
            node.bbox = {x1:0, y1:0, x2:0, y2:0};

            // Cache the images
            if (image.cache) {
                $fp.cacheImage(image.off);
                for (var j = 0; j < image.on.length; j++) {
                    $fp.cacheImage(image.on[j]);
                }
            }

            // Assign the appropriate motion function
            if (motion == undefined) { actors[i].motion = {update:noMotion}; }
            else if (motion.type == 'linear2d') { motion.update = linear2DMotion; }
        }
        return this;
    }


    // setBbox()

    function setBbox(actorNum) {
        var node = actors[actorNum].node;

        node.bbox.x1 = node.x - node.halfWidth;
        node.bbox.y1 = node.y - node.halfHeight;
        node.bbox.x2 = node.x + (node.width - node.halfWidth);
        node.bbox.y2 = node.y + (node.height - node.halfHeight);
    }


    // linearClip()

    function linearClip(actorNum) {
        var node = actors[actorNum].node;
        var motion = actors[actorNum].motion;

        if (node.bbox.x1 < 0) { node.x += (-node.bbox.x1 * 2); motion.angle = 180.0 - motion.angle; }
        if (node.bbox.y1 < 0) { node.y += (-node.bbox.y1 * 2); motion.angle = 90.0 + (270.0 - motion.angle); }

        if (node.bbox.x2 > anim.width)  { node.x -= (node.bbox.x2 - anim.width)  * 2; motion.angle = 180.0 - motion.angle; }
        if (node.bbox.y2 > anim.height) { node.y -= (node.bbox.y2 - anim.height) * 2; motion.angle = 90.0 + (270.0 - motion.angle); }

        motion.angle = $fp.roundAngle(motion.angle);   // Range [0.0 .. 360.0)
    }


    // noMotion

    function noMotion(actorNum) {
    }


    // linear2DMotion

    function linear2DMotion(actorNum) {
        var node = actors[actorNum].node;
        var motion = actors[actorNum].motion;

        // Change the direction

        if (motion.maxAngleBy > 0.0) {

            // Adjust the turn
            motion.angleBy += (($fp.rand(1) == 0) ? motion.dAngleBy : -motion.dAngleBy);

            // Bring the turn within the range [-maxAngleBy .. maxAngleBy]
            if      (motion.angleBy < -motion.maxAngleBy) { motion.angleBy = -motion.maxAngleBy - (motion.angleBy + motion.maxAngleBy); }
            else if (motion.angleBy >  motion.maxAngleBy) { motion.angleBy =  motion.maxAngleBy - (motion.angleBy - motion.maxAngleBy); }

            // Turn the angle
            motion.angle += motion.angleBy;
            motion.angle = $fp.roundAngle(motion.angle);   // Range [0.0 .. 360.0)
        }

        // Change the speed

        if (motion.maxSpeedBy > 0.0) {

            // Adjust the acceleration
            motion.speedBy += (($fp.rand(1) == 0) ? motion.dSpeedBy : -motion.dSpeedBy);

            // Bring the acceleration within the range [-maxAccel .. maxAccel]
            if      (motion.speedBy < -motion.maxSpeedBy) { motion.speedBy = -motion.maxSpeedBy - (motion.speedBy + motion.maxSpeedBy); }
            else if (motion.speedBy >  motion.maxSpeedBy) { motion.speedBy =  motion.maxSpeedBy - (motion.speedBy - motion.maxSpeedBy); }

            // Adjust the speed
            motion.speed += motion.speedBy;

            // Bring the speed within the range [minSpeed .. maxSpeed]
            if      (motion.speed < motion.minSpeed) { motion.speed = motion.minSpeed; }
            else if (motion.speed > motion.maxSpeed) { motion.speed = motion.maxSpeed; }
        }

        // Change the position

        // Get the unit vector for the angle of motion.
        var vectorX = Math.cos((motion.angle  * Math.PI) / 180.0);
        var vectorY = Math.sin((motion.angle  * Math.PI) / 180.0);

        // Reverse the y component, to reflect the pixel layout of the screen.
        vectorY = -vectorY;

        // Scale the vector based on the speed of the actor
        vectorX *= motion.speed;
        vectorY *= motion.speed;

        // Update the actor's position.
        node.x += vectorX;
        node.y += vectorY;

        setBbox(actorNum);
        linearClip(actorNum);
    }


    // setImage(sessionId)

    this.update = function(sessionId) {
 
        // If the mouse is currently over the animated area, and if the ID matches
        // the current animation session.
        if ((this.active) && (sessionId == this.activeSessionId)) {
            for (var i = 0; i < actors.length; i++) {
                var node = actors[i].node;
                var image = actors[i].image;
                var motion = actors[i].motion;

                // Update the actor's position
                motion.update(i);
                $('#' + node.id).css({'background-position':Math.round(node.x - node.halfWidth) + 'px ' + Math.round(node.y - node.halfHeight) + 'px'});

                // Update the frame number
                if (image.on.length == 1) { node.frameNum = 0; }                                                                          // Single frame
                else if ((node.frameNum == undefined) || (image.order === 'random')) { node.frameNum = $fp.rand(image.on.length - 1); }   // Random frame
                else { node.frameNum = (node.frameNum + 1) % image.on.length; }                                                           // Sequential frame

                $('#' + node.id).css({'background-image':'url(' + image.on[node.frameNum] + ')'})
            }
            // Recursively call the function, after the specified delay.
            var speed = anim.speed + ((anim.speedVariance != undefined) ? $fp.rand(anim.speedVariance) : 0);
            setTimeout('window.bgAnim["' + anim.id + '"].update(' + sessionId + ');', speed);
        }
        return this;
    }


    // on()

    this.on = function() {
        this.active = true;
        this.activeSessionId = $fp.rand(9999);   // Unique ID for the current animation session
        this.update(this.activeSessionId);
        return this;
    }


    // off()

    this.off = function() {
        this.active = false;

        for (var i = 0; i < actors.length; i++) {
            var node = actors[i].node;
            var image = actors[i].image;

            if (image.off != undefined) {
                $('#' + node.id).css({'background-image':'url(' + image.off + ')'});
                node.frameNum = undefined;
            }
        }
        return this;
    }
}


// Static methods

BgAnim.create = function(id, anim, actors) {
    if (window.bgAnim == undefined) { window.bgAnim = {}; }
    anim.id = id + "_BgAnim";

    // Create the animation dom element
    var parent = $fp.createElement({ tag:'div', attr: {id:anim.id, 'class':'bgAnim'} });

    // Create the actor dom actors
    for (var i = 0; i < actors.length; i++) {
        parent.appendChild( $fp.createElement({ tag:'div', attr: {id:actors[i].node.id, 'class':'node'} }) );
    }
    $('#' + id)[0].appendChild(parent);

    // Style the animation and actor dom elements
    $('#' + anim.id).css({position:'relative', overflow:'hidden'})
                    .width($('#' + id).width()).height($('#' + id).height());
    $('#' + anim.id + ' .node').css({position:'absolute', left:0, top:0, 'background-repeat':'no-repeat'})
                                .width($('#' + id).width()).height($('#' + id).height());

    // Create the animation object
    var bgAnim = window.bgAnim[anim.id] = (new BgAnim(id, anim, actors)).init();

    // Start or initialize the animation
    if (anim.alwaysOn) { window.bgAnim[anim.id].on(); }
    else {
        $('#' + anim.id).hover(function() { window.bgAnim[anim.id].on(); return true; }, function() { window.bgAnim[anim.id].off(); return true; });
        for (var i = 0; i < actors.length; i++) {
            var node = actors[i].node;
            var image = actors[i].image;

            $('#' + node.id).css({'background-position':Math.round(node.x - node.halfWidth) + 'px ' + Math.round(node.y - node.halfHeight) + 'px'});

            if (image.off != undefined) { $('#' + node.id).css({'background-image':'url(' + image.off + ')'}); }
            else {
                node.frameNum = $fp.rand(image.on.length - 1);
                $('#' + node.id).css({'background-image':'url(' + image.on[node.frameNum] + ')'})
            }
        }
    }
    return bgAnim;
}


