// Copyright (c) 2006 Shapely Shadow, Inc.
// JavaScript functions for the files generated by the FastFit Application.

<!-- ...location of the pageVariables object -->

//
// Cross-browser event handling
//
// obj: JavaScript/DOM object to attach an event handler to
// eventType: string indicating the type of event we're handling
// fn: JavaScript function to handle the event
// useCapture:If true, useCapture indicates that the user wishes to
//    initiate capture. After initiating capture, all events of the specified
//    type will be dispatched to the registered EventListener before being
//    dispatched to any EventTargets beneath them in the tree. Events which
//    are bubbling upward through the tree will not trigger an EventListener
//    designated to use capture.

function addEvent(obj, eventType, fn, useCapture)
{
    if (obj.addEventListener)
    {
        obj.addEventListener(eventType, fn, useCapture);
        return true;
    }
    else if (obj.attachEvent)
    {
        var r = obj.attachEvent('on' + eventType, fn);
        return r;
    }
    else
    {
        obj['on' + eventType] = fn;
    }

}

function findXPos(obj)
{
    // based on code at http://www.quirksmode.org/js/findpos.html
    var curLeft = 0;
    if (obj.offsetParent)
    {
        do
        {
            curLeft += obj.offsetLeft;
        } while (obj = obj.offsetParent);
    }
    else if (obj.x)
    {
        curLeft += obj.x;
    }
    return curLeft;
}

function leadingZero(s, len)
{
    var myString = String(s);
    while (myString.length < len)
    {
        myString = '0' + myString;
    }
    return myString;
}

//
// Constructor for ASpinner class.
//
// We assume that we will be animating images that are named like
// baseName_frame#

function ASpinner(id, options)
{
    this.fId = id;
    this.fNotify = options.notifyId || "";
    this.fBaseName = options.baseName;
    this.fFrameCount = options.frameCount;
    this.fRate = 1000 / options.rate;

    this.fTimerId = null;
    // if we'r'e scrubbing, we are not doing timed animation
    this.fScrub = true;
    // keep track of the current frame
    this.fCurrentFrame = 0;
    this.fLoadingFrame = 0;
    this.fLoaded = false;
    // an array of images...
    this.fFrames = null;
    this.fFrames = new Array(this.fFrameCount);
    // load up the images...
    this.fFramesArrived = 0;
    this.LoadFrames();
    //this.LoadNextFrame();
    var self = this;

    var mouseOverFunc = function()
    {
        self.MouseOver();
    }

    var mouseMoveFunc = function(e)
    {
        self.MouseMove(e);
    }

    var node = document.getElementById(this.fId);
    if (node)
    {
        //addEvent(node, 'mouseover', mouseOverFunc, false);
        //addEvent(node, 'mousemove', mouseMoveFunc, false);
    }
    else
    {
        alert("can't get element for " + this.fId + " = " + node);
    }

}

ASpinner.prototype.MouseOver = function(e)
{
    this.Stop();
}

ASpinner.prototype.MouseMove = function(e)
{
    if (this.fLoaded)
    {
        // handle IE and Moxilla-style events.
        var e = window.event ? window.event : e;
        var target = e.target ? e.target : e.srcElement;
        //this.NotifyText("X = " + e.clientX + "Frame count = " + this.fFrameCount +  "width = " + target.width);
        var xPos = e.clientX - findXPos(target);
        //this.NotifyText("xPos = " + xPos);
        // figure out which frame we should be displaying
        var frame = (xPos / target.width) * (this.fFrameCount - 1) + 0.5;
        frame = Math.floor(frame);
        frame = Math.min(Math.max(0, frame), this.fFrameCount - 1);
        this.NotifyText("Frame # " + (frame + 1));
        this.GoTo(frame);
    }

}

ASpinner.prototype.LoadFrames = function()
{
    var i;
    var self = this;
    for (i = 0; i < this.fFrameCount; ++i)
    {
        var theSource = this.fBaseName + "_" + leadingZero((i + 1), 2) + ".jpg";
        this.fFrames[i] = new Image();
        this.fFrames[i].src = theSource;
        var doComplete = function()
        {
            self.FrameArrived();
        }
        this.fFrames[i].onload = doComplete;
    }
}

ASpinner.prototype.FrameArrived = function()
{
    this.fFramesArrived += 1;
    if (this.fFramesArrived == this.fFrameCount)
    {
        this.fLoaded = true;
        if (!this.fScrub)
        {
            this.Start();
        }
    }

}

ASpinner.prototype.LoadNextFrame = function()
{
    var i = this.fLoadingFrame;
    var theSource = this.fBaseName + "_" + leadingZero((i + 1), 2) + ".jpg";
    this.fFrames[i] = new Image();
    this.fFrames[i].src = theSource;
    this.NotifyLoad(i);
    var self = this;

    var doComplete = function()
    {
        self.LoadComplete();
    }
    this.fFrames[i].onload = doComplete;
}

ASpinner.prototype.LoadComplete = function()
{
    var i = this.fLoadingFrame;
    this.GoTo(i);
    this.fLoadingFrame += 1;
    if (this.fLoadingFrame < this.fFrameCount)
    {
        this.LoadNextFrame();
    }
    else
    {
        this.fLoaded = true;
        if (false == this.fScrub)
        {
            this.Start();
        }
    }

}

ASpinner.prototype.NotifyLoad = function(frameNum)
{
    var text = "Loading frame " + (frameNum + 1) + " of " + this.fFrameCount;
    this.NotifyText(text);
}

ASpinner.prototype.NotifyText = function(text)
{
    var theDiv = document.getElementById(this.fNotify);
    if (theDiv)
    {
        theDiv.innerHTML = text;
    }
}

ASpinner.prototype.Next = function()
{
    var nextFrame = this.fCurrentFrame + 1;
    if (nextFrame >= this.fFrameCount)
    {
        // if we're out of frames, wrap back to the beginning.
        nextFrame = 0;
    }

    this.GoTo(nextFrame);
}

ASpinner.prototype.Prev = function()
{
    var prevFrame = this.fCurrentFrame - 1;
    if (prevFrame < 0)
    {
        prevFrame = this.fFrameCount - 1;
    }
    this.GoTo(prevFrame);

}

ASpinner.prototype.GoTo = function(frameNumber)
{
    var theImage = document.getElementById(this.fId);
    if (theImage)
    {
        if (frameNumber < this.fFrameCount)
        {
            theImage.src = this.fFrames[frameNumber].src;
            this.NotifyText("Frame # " + (frameNumber + 1));
            this.fCurrentFrame = frameNumber;
        }
        else
        {
            alert("bogus frame number");
        }
    }
    else
    {
        alert("Hey! Can't find " + this.fId);
    }
}

ASpinner.prototype.SourceFor = function(frame) {
    return this.fFrames[frame].src;
};

ASpinner.prototype.Start = function()
{
    this.fScrub = false;
    this.fLoaded = true;
    if (this.fLoaded && !this.fTimerId)
    {
        this.Next();
      // We need a separate 'self' variable that's equal to our 'this' because of
        // the scoping rules of JavaScript -- our private timeoutFunc function
        // below captures the 'self' variable into a closure and is able to use it
        // in the future when the timer function calls it.
        var self = this;
        var timeoutFunc = function()
        {
            self.Next();
        }
        this.fTimerId = window.setInterval(timeoutFunc, this.fRate);
    }
}

ASpinner.prototype.Stop = function()
{
    if (false == this.fScrub)
    {
        this.fScrub = true;
        window.clearInterval(this.fTimerId);
    }
}

/**********************************************
 Here begin functions for zooming functionality.
 **********************************************/
function removeEvent(obj, evType, fn, useCapture) {
    if (obj.removeEventListener) {
        obj.removeEventListener(evType, fn, useCapture);
        return true;
    } else if (obj.detachEvent) {
        var r = obj.detachEvent("on" + evType, fn);
        return r;
    }
}

function move(obj, x, y, width, height) {
    obj.style.marginLeft = x + "px";
    obj.style.marginTop = y + "px";
    if (typeof width != "undefined")
        setSize(obj, width, height);
}

function setSize(obj, width, height) {
    if (obj.tagName.toLowerCase() == "img") {
        obj.width = width;
        obj.height = height;
    } else {
        obj.style.width = width + "px";
        obj.style.hight = height + "px";
    }
}


// Constructor for AZoomer class
function AZoomer(baseImage, config) {
    this.baseImage = baseImage;

	config = config ? config : {};

    this.zoomFactor = config.zoomFactor ? config.zoomFactor : 1.4;
    this.maxZoom = config.maxZoom ? config.maxZoom : 5;
	this.fitToContainer = config.fitToContainer ? true : false;
    this.started = false;
}

// Remove all event listeners and DOM elements added by this AZoomer
AZoomer.prototype.Detach = function() {
	this.Stop();
	this.MouseUp();
	this.baseImage = null;
	if (this.zoomDiv) {
		removeEvent(this.zoomDiv, 'mousedown', this.doMouseDown, false);
		this.zoomDiv.parentNode.removeChild(this.zoomDiv);
		this.zoomDiv = null;
	}
}

// switch to zoomable view.
AZoomer.prototype.Start = function() {
    if (!this.zoomDiv) {
        this.zoomDiv = document.createElement("div");
        this.zoomDiv.style.overflow = "hidden";

        this.zoomImg = document.createElement("img");
        this.zoomImg.style.display = "block";
        
        //this is specified manually here because sleight.js sets a style rule that would make it hidden
        this.zoomImg.style.visibility = "visible";
        
		//this.zoomImg.style.position = "relative";

        this.zoomDiv.appendChild(this.zoomImg);
        this.baseImage.parentNode.insertBefore(this.zoomDiv, this.baseImage);

        var self = this;
        this.doMouseDown = function(event) {
            self.MouseDown(event);
        }

        addEvent(this.zoomDiv, 'mousedown', this.doMouseDown, false);
    }
    this.height = this.baseImage.height;
    this.width = this.baseImage.width;
    this.frameHeight = this.fitToContainer ? this.baseImage.parentNode.clientHeight : this.height;
    this.frameWidth = this.fitToContainer ? this.baseImage.parentNode.clientWidth : this.width;
    
    this.zoom = 1;
    this.panX = 0;
    this.panY = 0;

    this.zoomImg.src = this.baseImage.src;
    this.Zoom(0);

    this.zoomDiv.style.cursor = "auto";
    this.zoomDiv.style.display = "block";
	if (this.fitToContainer) {
	    this.zoomDiv.style.height = "100%";
	    this.zoomDiv.style.width = "100%";
	} else {
	    this.zoomDiv.style.height = this.height + "px";
	    this.zoomDiv.style.width = this.width + "px";
	}
    
    this.baseImage.style.display = "none";
    this.started = true;
}

// go back to normal animation view
AZoomer.prototype.Stop = function() {
    this.zoomDiv.style.display = "none";
    this.baseImage.style.display = "";
    this.started = false;
}

AZoomer.prototype.ZoomIn = function() {
    if (!this.started) {
        this.Start();
    }
    this.Zoom(this.zoom + 1);
}

AZoomer.prototype.ZoomOut = function() {
    this.Zoom(this.zoom - 1);
}

AZoomer.prototype.Zoom = function(zoom) {
    if (zoom < 0)
        zoom = 0;
    if (zoom > this.maxZoom)
        zoom = this.maxZoom;

    if (zoom == this.zoom) return;

    var zoomDif = Math.pow(this.zoomFactor, zoom - this.zoom);
    this.panX *= zoomDif;
    this.panY *= zoomDif;

    this.zoom = zoom;

    zoom = Math.pow(this.zoomFactor, zoom);
    var width = this.width * zoom;
    var height = this.height * zoom;
    this.CheckPan(width, height);
    var x = -(width - this.frameWidth) / 2 + this.panX;
    var y = -(height - this.frameHeight) / 2 + this.panY;
    move(this.zoomImg, x, y, width, height);

    if (this.zoom == 0) {
        this.zoomDiv.style.cursor = "auto";
        this.Stop();
    } else {
        this.zoomDiv.style.cursor = "move";
    }
}

AZoomer.prototype.MouseDown = function(e) {
    // handle IE and Moxilla-style events.
    var e = window.event ? window.event : e;

	//mouseX,Y store the last location of the mouse for comparison on mouseMove events
    this.mouseX = e.clientX;
    this.mouseY = e.clientY;
    if (!this.doMouseUp) {
        var self = this;
        this.doMouseUp = function(event) {
            self.MouseUp(event);
        }
        this.doMouseMove = function(event) {
            self.MouseMove(event);
        }
    }
    addEvent(this.zoomDiv, 'mousemove', this.doMouseMove, false);
    addEvent(document, 'mouseup', this.doMouseUp, false);

	//cancel browser built in dragging
    e.cancelBubble = true;
    e.returnValue = false;
    if (e.preventDefault) e.preventDefault();
}

AZoomer.prototype.MouseUp = function(e) {
    this.isMouseDown = false;
    removeEvent(this.zoomDiv, 'mousemove', this.doMouseMove, false);
    removeEvent(document, 'mouseup', this.doMouseUp, false);
}

AZoomer.prototype.MouseMove = function(e) {
    // handle IE and Moxilla-style events.
    var e = window.event ? window.event : e;

    var diffX = e.clientX - this.mouseX;
    var diffY = e.clientY - this.mouseY;
    this.mouseX = e.clientX;
    this.mouseY = e.clientY;

    this.panX += diffX;
    this.panY += diffY;
    this.CheckPan(this.zoomImg.width, this.zoomImg.height);

    var x = -(this.zoomImg.width - this.frameWidth) / 2 + this.panX;
    var y = -(this.zoomImg.height - this.frameHeight) / 2 + this.panY;

    move(this.zoomImg, x, y);
    
    //cancel browser built in dragging
	e.cancelBubble = true;
	e.returnValue = false;	
	if (e.preventDefault) e.preventDefault();
}

AZoomer.prototype.CheckPan = function(width, height) {
    var maxX = (width - this.frameWidth) / 2;
    var maxY = (height - this.frameHeight) / 2;

    if (this.panX < -maxX) this.panX = -maxX;
    if (this.panX > maxX) this.panX = maxX;
    if (this.panY < -maxY) this.panY = -maxY;
    if (this.panY > maxY) this.panY = maxY;
}

function Fastfit(config) {
    var self = this;

    this.spinner = new ASpinner(config.fastfit.container, config.fastfit.options);
    this.spinner.GoTo(0);
    
    this.slider = YAHOO.widget.Slider.getHorizSlider(config.slider.container, config.slider.thumb, 0, (config.fastfit.options.frameCount * 2) - 1, 2);
    this.slider.keyIncrement = 2;
    this.slider.subscribe("change", function(offset) {
        var frame = (offset / 2) || 0;
        jQuery(config.fastfit.frameContainer).html(frame + 1);
        if (frame >= 0 && frame < config.fastfit.options.frameCount) { self.spinner.GoTo(frame); }
    });

    this.zoomer = new AZoomer(document.getElementById(config.fastfit.container));
    jQuery(config.zoom.inButton).click(function() {
        self.zoomer.ZoomIn();
        self.slider.lock();
    });
    jQuery(config.zoom.outButton).click(function() {
        self.zoomer.ZoomOut();
        if (!self.zoomer.started) { self.slider.unlock(); }
    });
}

/**
 * This function attaches zoom capability to any image.
 *
 * config = {
 *   image: 'image or id of image to make zoomable',
 *   inButton: 'jQuery string for button to attach for zoom in',
 *   outButton: 'jQuery string for button to attach for zoom out'
 * }
 */
function AttachZoom(config) {
	var self = this;
    
    var img = config.image;
    if (typeof img == 'string')
    	img = document.getElementById(img)
    
    this.zoomer = new AZoomer(img);
    jQuery(config.inButton).click(function() {
        self.zoomer.ZoomIn();
        self.slider.lock();
    });
    jQuery(config.outButton).click(function() {
        self.zoomer.ZoomOut();
        if (!self.zoomer.started) { self.slider.unlock(); }
    });	
}