var ItemBuilder = Class.create();

ItemBuilder.prototype = {

    initialize: function() {
        },
    
    newItem: function(elem) {
    
        if (elem == null) {
            throw new Error("Element or element id must not be null nor empty");
        }
        var item = new Item();
        this.intializeOffsets(item);
        item.setElement(elem);
        
        // console.debug("item offsets 1: " + item.id + ": " + item.xOffset + "x" + item.yOffset);
        
        return item;
    },
    
    newImageItem: function(id, imageURL, width, height) {
    
        // console.debug("ItemBuilder.newImageItem: " + id + ", with size: " + width + "x" + height);
        var imgItem = new ImageItem();
        this.intializeOffsets(imgItem);
        imgItem.createNew(id, imageURL, width, height)
        imgItem.setPosition = imgItem._setPositionWithoutNeighbor;
        
        // console.debug("item offsets 2: " + imgItem.id + ": " + imgItem.xOffset + "x" + imgItem.yOffset);
        
        return imgItem;
    },
    
    intializeOffsets: function(item) {
        item.xOffset = null;
        item.yOffset = null;
    }
}

var Item = Class.create();

Item.prototype = {

    initialize: function() {
        this.movementStrategy = null;
    },
    
    setElement: function(element) {
    
        if (element == null) {
            throw new Error("Element or element id must not be null nor empty");
        }
        
        this.element = $(element);
        
        if (this.element == null) {
            throw new Error("No such element: " + element);
        }
        
        this.elemTagName = element.tagName;
        this.id = element.id;
        this.css = element.style;
        
        this.css.position = "absolute";
        this.hide();
        
        var width = 0;
        var height = 0;
        
        if (element.style.width && element.style.height) {
        
            width = element.style.width;
            height = element.style.height;
        }
        
        else {
            this.show();
            var dim = element.getDimensions();
            this.hide();
            
            width = dim['width'];
            height = dim['height'];
        }
        
        if (typeof(width) == 'string' && width.toLowerCase().endsWith("px")) {
            width = width.substring(0, width.length - 2);
        }
        
        if (typeof(height) == 'string' && height.toLowerCase().endsWith("px")) {
            height = height.substring(0, height.length - 2);
        }
        
        this.css.width = width + "px";
        this.xOffset = width ? (-1 * Math.round(width / 2)) : 0;
        this.css.height = height + "px";
        this.yOffset = height ? (-1 * Math.round(height / 2)) : 0;
        
        if (this.xOffset == null || typeof this.xOffset != "number") {
            this.xOffset = 0;
        }
        
        if (this.yOffset == null || typeof this.yOffset != "number") {
            this.yOffset = 0;
        }
    },
    
    removeElement: function() {
    
        var content = this.element;
        this.element = null;
        this.destroyed = true;
        return content;
    },
    
    getId: function() {
        return this.id;
    },
    
    isFollowable: function() {
    
        return false;
    },
    
    show: function() {
        this.css.display = "block";
    },
    
    hide: function() {
        this.css.display = "none";
        if (this.neighbor != null) {
            this.neighbor.hide();
        }
    },
    
    destroy: function() {
    
        if (this.destroyed) {
            top.status = "Ignoring destroy() because already destroyed elements";
        }
        
        try {
            if(this.element) {
                document.body.removeChild(this.element);
            }
            this.element = null;
        } 
        catch (e) {
            top.status = "Could not remove element " + this.element.id + ": " + e;
        }
        
        this.destroyed = true;
    },
    
    setBgColor: function(color) {
        this.css.backgroundColor = color;
    },
    
    
    setStyle: function(cssName, cssValue) {
        this.css[cssName] = cssValue;
    },
    
    setZindex: function(zIndex) {
    
        this.css.zIndex = zIndex;
        if (this.neighbor != null) {
            this.neighbor.setZindex(zIndex + 1);
        }
    },
    
    setBgImage: function(image) {
        this.css.backgroundImage = 'url(' + image + ')';
    },
    
    setPosition: function(point) {
    
        if (point == null) {
            throw new Error("point is null");
        }
        
        this.lastPosition = this.center;
        this.center = point;
        this.setStyle();
    },
    
    getPosition: function() {
        return this.center;
    },
    
    getOffsetX: function() {
        return this.xOffset;
    },
    
    getOffsetY: function() {
        return this.yOffset;
    },
    
    getCss: function() {
        //alert(this.css);
        return this.css;
    },
    
    setMovementStrategy: function(strategy) {
    
        this.movementStrategy = strategy;
    },
    
    setStyle: function() {
    
        var left = (this.xOffset + this.center.x);
        var top = (this.yOffset + this.center.y);
        if (isNaN(left)) {
            throw new Error("left is not a number");
        }
        
        if (isNaN(top)) {
            throw new Error("top is not a number");
        }
        try {
            this.css.left = left + "px";
            this.css.top = top + "px";
        } 
        catch (e) {
            //throw new Error("Illegal values: left: " + left + ", top: " + top);
            this.center.x = 0;
            this.center.y = 0;
            this.setStyle();
        }
    },
    
    toString: function() {
        return "Item (" + this.id + ") " + this.elemTagName;
    }
    
};

/**
 * @author Patrick Kopp
 *
 *
 * @param {String} id
 * @param {String} imageURL
 */
var ImageItem = Class.create();

ImageItem.prototype = Object.extend(new Item(), {

    createNew: function(id, imageURL, width, height) {
    
        this.xOffset = width ? (-1 * Math.round(width / 2)) : 0;
        this.yOffset = height ? (-1 * Math.round(height / 2)) : 0;
        
        // console.debug("ImageItem: " + width + "x" + height);
        
        this.img = this._prepareImage(id, imageURL, width, height);
        this.center = new Point(0, 0);
        
        this.id = id;
        this.setElement(this._createDiv(width, height));
        
        this.destroyed = false;
        
        this.lastPosition;
        this.movementStrategy;
        
        this.neighbor = null;
        this.parentItem = null;
        
        this.neighborPositionCounter = false;
        
        this.events = {};
    },
    
    _setPositionWithoutNeighbor: function(point) {
    
        if (point == null) {
            throw new Error("point is null");
        }
        
        this.lastPosition = this.center;
        this.center = point;
        this.setStyle();
    },
    
    _setPositionWithNeighbor: function(point) {
        this._setPositionWithoutNeighbor(point);
        if (this.neighbor == null) {
            throw new Error("Neighbor is null!");
        }
        this.neighbor.setPosition(this.center.newPoint(this.neighbor.ExtraXOffset, this.neighbor.ExtraYOffset));
    },
    
    getMovedDistance: function() {
        return this.lastPosition.getDistance(this.center);
    },
    
    newImage: function(newImageURL) {
        var newImgTag = getImageTag(_prepareImage(this.id, newImageURL));
        var actImgTag;
        for (var node = 0; node < this.element.childNodes.length; ++node) {
            if (this.element.childNodes[node].tagName.toLowerCase() == "img") {
                actImgTag = this.element.childNodes[node];
            }
        }
        if (actImgTag) {
            this.element.replaceChild(newImgTag, actImgTag);
        }
        this.setStyle();
    },
    
    setLink: function(url, target, title) {
        var imgHTML = this.element.innerHTML;
        if (!target) {
            target = "_self";
        }
        if (!title) {
            title = "_self";
        }
        this.element.innerHTML = "<a target='" + target + "' title='" + title + "' href='" + url + "'>" + imgHTML + "</a>";
    },
    
    /**
     * Adds an initially invisible item thats is does the same movements with the given offsets as this ImageItem.
     * @param {String} imageURL - the image of this neighbor
     * @param {Number} xOffset
     * @param {Number} yOffset
     */
    addNeighbor: function(imageURL, xOffsetToMe, yOffsetToMe, width, height) {
        this.neighbor = new ItemBuilder().newImageItem("__@" + this.id, imageURL, width, height);
        this.neighbor.setZindex(this.css.zIndex);
        this.hideNeighbor();
        this._calculateNeighborPosition(xOffsetToMe, yOffsetToMe);
        this.setPosition = this._setPositionWithNeighbor;
        this.neighbor.hide();
    },
    
    hasNeighbor: function() {
        return (this.neighbor != null && this.neighbor.div && $(this.neighbor.div.id));
    },
    
    showNeighbor: function() {
    
        if (this.neighbor == null) {
            throw new Error("Must not call showNeighbor() if there isn't any neighbor defined!");
        }
        if (!this.neighbor.ExtraXOffset && this.neighbor.ExtraXOffset != 0 ||
        !this.neighbor.ExtraYOffset && this.neighbor.ExtraYOffset != 0) {
            throw new Error("x- or y-offset of neighbor is not defined");
        }
        this.setPosition = this._setPositionWithNeighbor;
        this.setPosition(this.center); // to set neighbor to correct position
        this.neighbor.show();
    },
    
    hideNeighbor: function() {
    
        if (this.neighbor == null) {
            throw new Error("Must not call hideNeighbor() if there isn't any neighbor defined!");
        }
        this.neighbor.hide();
        this.setPosition = this._setPositionWithoutNeighbor;
    },
    
    getNeighbor: function() {
        return this.neighbor;
    },
    
    toString: function() {
        return "Item: " + this.id +
        ", pos: " +
        this.getPosition();
    },
    
    _calculateNeighborPosition: function(xOffsetToMe, yOffsetToMe) {
    
        this.neighbor.ExtraXOffset = xOffsetToMe + this.xOffset - this.neighbor.xOffset;
        this.neighbor.ExtraYOffset = yOffsetToMe + this.yOffset - this.neighbor.yOffset;
        this.neighbor.setPosition(this.center.newPoint(this.neighbor.ExtraXOffset, this.neighbor.ExtraYOffset));
        this.setPosition = this._setPositionWithNeighbor;
    }
});

ImageItem.INCREMENT_ID = 0;

ImageItem.prototype._createDiv = function(width, height) {

    var newDiv = document.createElement("div");
    var elemId = this.id;
    if (document.getElementById(elemId) != null) {
        // prevent zombies
        elemId += "_" + ImageItem.INCREMENT_ID;
        ImageItem.INCREMENT_ID++;
    }
    newDiv.id = elemId;
    
    var imgElem = this._newImageElement(this.img);
    
    newDiv.appendChild(imgElem);
    document.body.appendChild(newDiv);
    
    if (width && height) {
        newDiv.style.width = width + "px";
        newDiv.style.height = height + "px";
    }
    else {
        // set images width & height
        newDiv.style.width = imgElem.width + "px";
        newDiv.style.height = imgElem.height + "px";
    }
    
    newDiv.style.position = "absolute";
    newDiv.style.top = this.xOffset + this.center.x + "px";
    newDiv.style.left = this.yOffset + this.center.y + "px";
    return $(this.id);
}

ImageItem.prototype._newImageElement = function(image) {

    var img = document.createElement("img");
    img.setAttribute("src", image.src);
    img.setAttribute("name", image.name);
    
    if (image.width > 0) {
        img.setAttribute("width", image.width);
    }
    if (image.height > 0) {
        img.setAttribute("height", image.height);
    }
    return img;
}

ImageItem.prototype._prepareImage = function(namePrefix, imageURL, width, height) {

    var img = new Image();
    img.src = imageURL;
    img.name = namePrefix + "_img";
    
    if (height > 0) {
        img.height = height;
    }
    
    if (width > 0) {
        img.width = width;
    }
    
    return img;
}

/**
 * CAUTION: Removes all previously added event handlers of this eventType,
 * simply add all your events with this function.
 *
 * Adds an event handler to this image item (to its div).
 *
 * @param {String} eventType - may be every event of a <pre><div></pre>-element.
 * @param {Function} handler
 */
ImageItem.prototype.addEvent = function(id, eventType, handler) {

    var selfe = this;
    
    if (document.attachEvent) { // IE5 and later has proprietary event
        var wrappedHandler = function(e) {
            if (!e) {
                e = window.event;
            }
            
            // create mostly DOM-compatible event
            var event = {
                IE_event: e, // original IE-event
                type: e.type,
                target: e.srcElement,
                currentTarget: selfe.div,
                relatedTarget: e.fromElement ? e.fromElement : e.toElement,
                eventPhase: (e.srcElement == selfe.div) ? 2 : 3,
                clientX: e.clientX,
                clientY: e.clientY,
                screenX: e.screenX,
                screenY: e.screenY,
                altKey: e.altKey,
                ctrlKey: e.ctrlKey,
                shiftKey: e.shiftKey,
                charCode: e.keyCode,
                
                stopPropagation: function() {
                    this._event.cancelBubble = true;
                },
                preventDefault: function() {
                    this._event.returnValue = false;
                }
            }
            
            handler.call(selfe, event);
        }
        this.events[eventType + id] = wrappedHandler;
    }
    else {
        this.events[eventType + id] = handler;
    }
    
    this._setEventsInDiv(eventType);
}

ImageItem.prototype.removeEvent = function(id, eventType) {
    delete this.events[eventType + id];
    this._setEventsInDiv(eventType);
}

ImageItem.prototype._setEventsInDiv = function(eventType) {
    var selfe = this;
    var mergedHandlers = function(e) {
        for (var ident in selfe.events) {
            if (ident.indexOf(eventType) == 0) {
                selfe.events[ident].call(selfe, e);
            }
        }
    }
    
    eval('this.element.' + eventType + ' = ' + mergedHandlers);
}

var Followable = Class.create();

Followable.prototype = Object.extend(new Item(), {

    initialize: function(item) {
    
        this.item = item;
        var newDiv = document.createElement("div");
        newDiv.style.width = this.item.css.width;
        newDiv.style.height = this.item.css.height;
        if(animUtil.isDevelopmentMode()) {
          newDiv.style.border = "1px dotted #FC9128";
        }
        document.body.appendChild(newDiv);
        this.setElement(newDiv);
        this.xOffset = this.item.getOffsetX();
        this.yOffset = this.item.getOffsetY();
        this.show();
    },
    
    replaceElement: function(newElement) {
    
        if (this.element) {
            try {
                window.document.removeChild(this.element);
            } 
            catch (e) {
                        // Can live with that
            }
        }
        
        this.setElement(newElement);
        
    },
    
    isFollowable: function() {
    
        return true;
    },
    
    getId: function() {
    
        return this.item.getId();
    },
    
    toString: function() {
        return "Followable of " + this.item;
    }
});

