/**
 * @author mathieso
 */

var debugFlag = false;
var debugLog = '';

function createDisplayObject(displaySpec) {
    if ( debugFlag ) {
        addDebugLog('Start createDisplayObject.');
        addDebugLog('displaySpec.id: ' + displaySpec.id);
    }
    var newObject = null;
    failIfEmpty(displaySpec, 'No display object');
    failIfEmpty(displaySpec.objectType, 'Object type not specified');
    var objectType = displaySpec.objectType.toLowerCase();
    if ( debugFlag ) {
        addDebugLog('createDisplayObject: check objectType.');
        addDebugLog('objectType: ' + objectType);
    }
    switch(objectType) {
        case 'image': 
            newObject = $('<img>');
            break;
        case 'text':
            newObject = $('<span></span>');
            break;
        case 'button':
            newObject = $('<button></button>');
            break;
        default:
            throw 'Unknown object type: ' + objectType;
    }
    if ( exists(displaySpec.id) ) {
        newObject.attr('id',displaySpec.id);
    }
    if ( exists(displaySpec.src) ) {
        newObject.attr('src',displaySpec.src);
    }
    if ( exists(displaySpec.text) ) {
        newObject.attr('innerHTML', displaySpec.text);
    }
    if ( exists(displaySpec.width) ) {
        newObject.css('width',displaySpec.width + 'px');
    }
    if ( exists(displaySpec.height) ) {
        newObject.css('height',displaySpec.height + 'px');
    }
    if ( exists(displaySpec.className) ) {
        newObject.addClass(displaySpec.className);
    }
    newObject.css('position', 'absolute');
    //Set vertical position. Defaults to 0.
    newObject.css('top', 0+'px');
    if ( exists(displaySpec.verticalPosition) ) {
        var verticalPosition = displaySpec.verticalPosition;
        if ( ! isNaN(verticalPosition) ) {
            //Number
            newObject.css('top', verticalPosition + 'px');
        }
        else if ( typeof(verticalPosition) == 'string' ) {
            //Keyword.
            //Get offset, if it exists.
            var verticalOffset = 0;
            if ( exists(displaySpec.verticalOffset) ) {
                if ( isNaN(displaySpec.verticalOffset) ) {
                    throw 'verticalOffset not numeric: ' + 
                        displaySpec.verticalOffset;
                }
                verticalOffset = displaySpec.verticalOffset;
            }
            //Get target element if it exists.
            var verticalTarget = null;
            if ( exists(displaySpec.verticalTarget) ) {
                if ( typeof(displaySpec.verticalTarget) == 'string' ) {
                    //Assume it is the name of an object.
                    verticalTarget = eval(displaySpec.verticalTarget);
                }
                else {
                    //Assume it is an object.
                    verticalTarget = displaySpec.verticalTarget;
                }
                failIfEmpty(verticalTarget, 'verticalTarget not found: '
                        + displaySpec.verticalTarget);
            }
            //Look for a keyword.
            switch(verticalPosition) {
                case 'top':
                    newObject.css('top', verticalOffset + 'px');
                    break;
                case 'align-top':
                    validateVerticalTarget(verticalTarget);
                    var topPx = parseInt(stripPx(verticalTarget.css('top')));
                    var pos = topPx + verticalOffset;
                    newObject.css('top', pos + 'px');
                    break;
                case 'align-center':
                    validateVerticalTarget(verticalTarget);
                    var targetTop = getElementDimension(verticalTarget, 'top');
                    var targetHeight = getElementDimension(verticalTarget, 'height');
                    var elementHeight = getElementDimension(newObject, 'height');
                    var pos = targetTop + (targetHeight - elementHeight)/2 + verticalOffset;
                    newObject.css('top', pos + 'px');
                    break;
                case 'align-bottom':
                    validateVerticalTarget(verticalTarget);
                    var targetTop = getElementDimension(verticalTarget, 'top');
                    var targetHeight = getElementDimension(verticalTarget, 'height');
                    var elementHeight = getElementDimension(newObject, 'height');
                    var pos = targetTop + targetHeight - elementHeight + verticalOffset;
                    newObject.css('top', pos + 'px');
                    break;
                case 'below':
                    validateVerticalTarget(verticalTarget);
                    var targetTop = getElementDimension(verticalTarget, 'top');
                    var targetHeight = getElementDimension(verticalTarget, 'height');
                    var pos = targetTop + targetHeight + verticalOffset;
                    newObject.css('top', pos + 'px');
                    break;
                default:
                    throw('Unknown verticalPosition: ' + verticalPosition);
            }
        }
        else {
            throw('Cannot interpret displaySpec.verticalPosition' 
                    + displaySpec.verticalPosition);
        }
    } // end vertical position

    //Set horizontal position. Defaults to 0.
    newObject.css('left', 0+'px');
    if ( exists(displaySpec.horizontalPosition) ) {
        var horizontalPosition = displaySpec.horizontalPosition;
        if ( ! isNaN(horizontalPosition) ) {
            //Number
            newObject.css('left', horizontalPosition + 'px');
        }
        else if ( typeof(horizontalPosition) == 'string' ) {
            //Keyword.
            //Get offset, if it exists.
            var horizontalOffset = 0;
            if ( exists(displaySpec.horizontalOffset) ) {
                if ( isNaN(displaySpec.horizontalOffset) ) {
                    throw 'horizontalOffset not numeric: ' + 
                        displaySpec.horizontalOffset;
                }
                horizontalOffset = displaySpec.horizontalOffset;
            }
            //Get target element if it exists.
            var horizontalTarget = null;
            if ( exists(displaySpec.horizontalTarget) ) {
                if ( typeof(displaySpec.horizontalTarget) == 'string' ) {
                    //Assume it is the name of an object.
                    horizontalTarget = eval(displaySpec.horizontalTarget);
                }
                else {
                    //Assume it is an object.
                    horizontalTarget = displaySpec.horizontalTarget;
                }
                failIfEmpty(horizontalTarget, 'horizontalTarget not found: '
                        + displaySpec.horizontalTarget);
            }
            //Look for a keyword.
            switch(horizontalPosition) {
                case 'left':
                    newObject.css('left', horizontalOffset+'px');
                    break;
                case 'right':
                    var bodyWidth = getBodyWidth();
                    var elementWidth = getElementDimension(newObject, 'width');
                    newObject.css('left', bodyWidth - elementWidth - horizontalOffset);
                    break;
                case 'center':
                    var bodyWidth = getBodyWidth();
                    var elementWidth = getElementDimension(newObject, 'width');
                    var elementLeft = bodyWidth/2 - elementWidth/2 + horizontalOffset;
                    newObject.css('left', elementLeft + 'px');
                    break;
                case 'center-on-target':
                    failIfEmpty(horizontalTarget, 'No horiontal target');
                    var targetElementWidth = getElementDimension(horizontalTarget, 'width');
                    var elementWidth = getElementDimension(newObject, 'width');
                    var targetElementLeft = getElementDimension(horizontalTarget, 'left');
                    var elementLeft = targetElementLeft + (targetElementWidth - elementWidth)/2;
                    newObject.css('left', elementLeft + 'px');
                    break;
                case 'right-of-target':
                    failIfEmpty(horizontalTarget, 'No horiontal target');
                    var targetElementWidth = getElementDimension(horizontalTarget, 'width');
                    var targetElementLeft = getElementDimension(horizontalTarget, 'left');
                    var pos = targetElementWidth + targetElementLeft + horizontalOffset;
                    newObject.css('left', pos + 'px');
                    break;
                case 'left-of-target':
                    failIfEmpty(horizontalTarget, 'No horiontal target');
                    var targetElementLeft = getElementDimension(horizontalTarget, 'left');
                    var elementWidth = getElementDimension(newObject, 'width');
                    var pos = targetElementLeft - elementWidth - horizontalOffset;
                    newObject.css('left', pos + 'px');
                    break;
                default:
                    throw('Unknown horizontalPosition: ' + horizontalPosition);
            }
        }
        else {
            throw('Cannot interpret displaySpec.horizontalPosition' 
                    + displaySpec.horizontalPosition);
        }
    } // end horizontal position
    $('body').append(newObject);
    return newObject;
}

/**
 * Return a clone of a target. Hidden. Done in queue
 * @param {Object} target
 */
function createCopyInQueue(target, result) {
    $("body").animate(
        { 
            'margin-right': 
                parseInt(stripPx($("body").css('margin-right'))+1)
        },
        {
            queue: 'global',
            duration: 1,
            complete: function () { 
                failIfEmpty(target, 'createCopy: empty target');
                var newObject = target.clone();
                newObject.css('opacity', '0');
                newObject.css('z-index', 5);
                $('body').append(newObject);
                result = newObject;
            }
        }
    );
}

/*
 * Move source object to center of target object.
 */
function moveTo(source, target, rate, easingToUse) {
    //Compute destination pixel.
    var targetHeight = getElementDimension(target, 'height');
    var targetWidth = getElementDimension(target, 'width');
    var targetTop = getElementDimension(target, 'top');
    var targetLeft = getElementDimension(target, 'left');
    var sourceHeight = getElementDimension(source, 'height');
    var sourceWidth = getElementDimension(source, 'width');
    
    var destinationTop = targetTop + (targetHeight - sourceHeight)/2;
    var destinationLeft = targetLeft + (targetWidth - sourceWidth)/2;
    
    //Animation.
    if ( ! exists(rate) ) {
        rate = 'slow';
    }
    if ( ! exists(easingToUse) ) {
        easingToUse = 'linear';
    }
    source.show();
    source.animate(
        { 
            'top': destinationTop,
            'left': destinationLeft
        },
        {
            duration: rate,
            easing: easingToUse,
            queue: 'global'
        }
    );
}

/**
 * Delay for give time. In animation queue.
 * @param {Object} ms Time to delay
 */
function doNothingInQueue(ms) {
    $("body").animate(
        { 
            'margin-right': 
                parseInt(stripPx($("body").css('margin-right'))+1)
        },
        {
            queue: 'global',
            duration: ms
        }
    );
}

/**
 * Assign a value to HTML of an object, queued with animations.
 * @param {Object} obj
 * @param {Object} attributeValue
 */
function assignHtmlInQueue(obj, attributeValue) {
    $("body").animate(
        { 
            'margin-right': 
                parseInt(stripPx($("body").css('margin-right'))+1)
        },
        {
            queue: 'global',
            duration: 100,
            complete: function () { obj.html(attributeValue) }
        }
    );
}

/**
 * Run some JS, queued with animations.
 * @param {String} codeToRun
 */
function runCodeInQueue(codeToRun) {
    $("body").animate(
        { 
            'margin-right': 
                parseInt(stripPx($("body").css('margin-right'))+1)
        },
        {
            queue: 'global',
            duration: 1,
            complete: function () { eval(codeToRun) }
        }
    );
}

/**
 * Assign a value to a CSS property of an object, queued with animations.
 * @param {Object} obj
 * @param {Object} propertyName
 * @param {Object} propertyValue
 */
function assignCssPropertyInQueue(obj, propertyName, propertyValue) {
    $("body").animate(
        { 
            'margin-right': 
                parseInt(stripPx($("body").css('margin-right'))+1)
        },
        {
            queue: 'global',
            duration: 1,
            complete: function () { obj.css(propertyName, propertyValue) }
        }
    );
}

/**
 * Assign a value to a property of an object, queued with animations.
 * @param {Object} obj
 * @param {Object} propertyName
 * @param {Object} propertyValue
 */
function assignPropertyInQueue(obj, propertyName, propertyValue) {
    $("body").animate(
        { 
            'margin-right': 
                parseInt(stripPx($("body").css('margin-right'))+1)
        },
        {
            queue: 'global',
            duration: 1,
            complete: function () { obj.attr(propertyName, propertyValue) }
        }
    );
}


function destroyObject(obj) {
    //Wait until the queue is empty
    //Kill the object.
    $("body").animate(
        { 
            'margin-right': 
                parseInt(stripPx($("body").css('margin-right'))+1)
        },
        {   
            queue: 'global',
            complete: function () {obj.remove(); },
            duration: 1
        }
    );
  
}

/**
 * Position center of object over center of the target.
 */
function positionOn(obj, target, relationSpec){
    //Wait until the queue is empty
    //Kill the object.
    $("body").animate({
        'margin-right': parseInt(stripPx($("body").css('margin-right')) + 1)
    }, {
        queue: 'global',
        duration: 1,
        complete: function(){
            var targetHeight = getElementDimension(target, 'height');
            var targetWidth = getElementDimension(target, 'width');
            var targetTop = getElementDimension(target, 'top');
            var targetLeft = getElementDimension(target, 'left');
            var elementHeight = getElementDimension(obj, 'height');
            var elementWidth = getElementDimension(obj, 'width');
            
            //Extract the relation spec.
            var horizontalRelation = 'center';
            if (relationSpec && relationSpec.horizontalRelation) {
                horizontalRelation = relationSpec.horizontalRelation.toLowerCase();
            }
            var verticalRelation = 'center';
            if (relationSpec && relationSpec.verticalRelation) {
                verticalRelation = relationSpec.verticalRelation.toLowerCase();
            }
            var horizontalOffset = 0;
            if (relationSpec && relationSpec.horizontalOffset) {
                failIfNaN(relationSpec.horizontalOffset, 'positionOn: relationSpec.horizontalOffset not numeric: ' +
                relationSpec.horizontalOffset);
                horizontalOffset = parseInt(relationSpec.horizontalOffset);
            }
            var verticalOffset = 0;
            if (relationSpec && relationSpec.verticalOffset) {
                failIfNaN(relationSpec.verticalOffset, 'positionOn: relationSpec.verticalOffset not numeric: ' +
                relationSpec.verticalOffset);
                verticalOffset = parseInt(relationSpec.verticalOffset);
            }
            
            var elementTop;
            var elementLeft;
            switch (horizontalRelation) {
                case 'center':
                    elementLeft = targetLeft + (targetWidth - elementWidth) / 2 + horizontalOffset;
                    break;
                case 'left-align':
                    elementLeft = targetLeft + horizontalOffset;
                    break;
                case 'left-outside':
                    elementLeft = targetLeft - elementWidth - horizontalOffset;
                    break;
                case 'right-align':
                    elementLeft = targetLeft + targetWidth - elementWidth + horizontalOffset;
                    break;
                case 'right-outside':
                    elementLeft = targetLeft + targetWidth + horizontalOffset;
                    break;
                default:
                    throw ('positionOn: Unknown horizontalRelation: ' + horizontalRelation);
            }
            switch (verticalRelation) {
                case 'center':
                    elementTop = targetTop + (targetHeight - elementHeight) / 2 + verticalOffset;
                    break;
                case 'top-align':
                    elementTop = targetTop + verticalOffset;
                    break;
                case 'top-above':
                    elementTop = targetTop - elementHeight + verticalOffset;
                    break;
                case 'bottom-align':
                    elementTop = targetTop + targetHeight - elementHeight + verticalOffset;
                    break;
                case 'bottom-below':
                    elementTop = targetTop + targetHeight + verticalOffset;
                    break;
                default:
                    throw ('positionOn: Unknown verticalRelation: ' + verticalRelation);
            }
            obj.css('left', elementLeft);
            obj.css('top', elementTop);
        }
    });
}

/**
 * Get the width of the page's body.
 */
function getBodyWidth() {
    failIfEmpty($('body').css('width'), 'No body width');
    var bodyWidth = stripPx($('body').css('width'));
    failIfNaN(bodyWidth, 'Body width not a number: ' + bodyWidth);
    bodyWidth = parseInt(bodyWidth);
    return bodyWidth;    
}

/*
 * Get the dimension of an element.
 */
function getElementDimension(element, dimensionName) {
    failIfEmpty(element.css(dimensionName), 'No ' + dimensionName + ' for the element');
    var dimValue = stripPx(element.css(dimensionName));
    failIfNaN(dimValue, 'Element ' + dimensionName + ' not number: ' + dimValue);
    dimValue = parseInt(dimValue);
    return dimValue;
}

function addDebugLog(txt) {
    debugLog += txt;
}

/*
 * Check that a vertical target is OK.
 */
function validateVerticalTarget(targetToCheck) {
    validateTarget(targetToCheck, 'top');
}

/*
 * Check that a horizontal target is OK.
 */
function validateHorizontalTarget(targetToCheck) {
    validateTarget(targetToCheck, 'left');
}

/*
 * Check that a target has a valid value on the given dimension
 */
function validateTarget(targetToCheck, dim) {
    failIfEmpty(targetToCheck, 'No target');
    failIfEmpty(targetToCheck.css(dim), 'Missing target.' + dim);
    var posPx = stripPx(targetToCheck.css(dim));
    failIfNaN(posPx, 
        'Bad value target.' + dim + ': ' + targetToCheck.css(dim));
}

function failIfEmpty(thing, message) {
    if (thing == '' || thing == null) {
        throw message;
    }
}

function failIfNaN(thing, message) {
    if (isNaN(thing) ) {
        throw message;
    }
}

/*
 * Throw error if the object does not have an id.
 */
function failIfNoId(obj, message) {
    if ( ! exists(obj.attr('id')) ) {
        throw message;
    }
}

function exists(thing) {
    return thing != '' && thing != null && thing != '0';
}

function stripPx(thing) {
    var spl = thing.split('px');
    return spl[0];
}

//http://javascript.geniusbug.com/index.php?action=show&name=clone
function clone(myObj) {
	if(typeof(myObj) != 'object') return myObj;
	if(myObj == null) return myObj;

	var myNewObj = new Object();

	for(var i in myObj)
		myNewObj[i] = clone(myObj[i]);

	return myNewObj;
}


