/*
 * modernart.js -- Transforming ordinary pictures into dynamic art
 * Copyright (C) 2010  Niels Serup
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * A copy of the GNU Lesser General Public License is available at
 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
 */

/*
 * Version 0.1.1
 * Maintainer: Niels Serup <ns@metanohi.name>
*/

/*
 * There is no real documentation for this script. Look at
 * <http://nohix.metanohi.org/modernart.htm> to get an idea on how to
 * implement it. It's very easy.
 * This has currently only been tested in Conkeror, IceCat (like
 * Firefox) 3.67 and Chromium.
*/

/*****************
 *** CHANGELOG ***
 0.1.1, September 25 2010 -- Now also works with images with absolute or
 fixed positions.

 0.1.0, August 5 2010 -- Initial release.
*****************/

createCode = function(dict) {
    for (x in dict) {
        try {
            dict[x].prototype.objectParent = dict;
        } catch (e) {};
    }
    return dict;
};

var modernart = createCode({
    ModernArtRunner: function(imgElem, boxElems, lineElems,
                              colorFunction) {
        this.imgElem = imgElem;
        this.boxElems = boxElems;
        this.lineElems = lineElems;
        this.colorFunctions = this.objectParent.colorFunctions;
        this.colorFunction = this.colorFunctions[colorFunction];
        this.parent = this.imgElem.parentNode;
        this.timers = [];

        for (var i in this.boxElems) {
            for (var j in this.boxElems[i]) {
                this.boxElems[i][j].objectParent = this;
                this.parent.appendChild(this.boxElems[i][j]);
            }
        }
        for (var i in this.lineElems)
            this.parent.appendChild(this.lineElems[i]);

        var self = this;
        var timerPause = function() {
            for (var i in self.timers)
                self.timers[i].pause();
        };
        var timerResume = function() {
            for (var i in self.timers)
                self.timers[i].resume();
        };
        var timerToggle = function() {
            for (var i in self.timers)
                self.timers[i].toggle();
        };
        this.imgElem.pause = timerPause;
        this.imgElem.resume = timerResume;
        this.imgElem.toggle = timerToggle;
        this.parent.pause = timerPause;
        this.parent.resume = timerResume;
        this.parent.toggle = timerToggle;

        if (this.objectParent.customFunction)
            this.objectParent.customFunction(this);

        this.rgb = [0, 255, 0];
        this.colorFunction();
    },

    Timer: function(func, delay) {
        this.func = func;
        this.delay = delay;
        this.resume();
    },

    colorFunctions: {
        flow: function() {
            var power = parseInt(this.imgElem.getSetting(
                'modernflowpower')) || 25;
            var flowType = this.imgElem.getSetting(
                'modernflowtype') || 'stop';
            var flowIgnore = flowType == 'stop';
            var xPower = parseInt(power / this.boxElems[0].length);
            var yPower = parseInt(power / this.boxElems.length);
            var flowRun = function() {
                var r = this.rgb[0];
                var g = this.rgb[1];
                var b = this.rgb[2];
                if (r < 255 && g <= 0 && b >= 255)
                    r += 10;
                else if (r > 0 && g >= 255 && b <= 0)
                    r -= 10;
                else if (r >= 255 && g < 255 && b <= 0) 
                    g += 10;
                else if (r <= 0 && g > 0 && b >= 255)
                    g -= 10;
                else if (r <= 0 && g >= 255 & b < 255)
                    b += 10;
                else if (r >= 255 && g <= 0 && b > 0)
                    b -= 10;

                this.rgb[0] = r;
                this.rgb[1] = g;
                this.rgb[2] = b;

                var tr, tg, tb;
                for (var i in this.boxElems) {
                    for (var j in this.boxElems[i]) {
                        if (flowIgnore) {
                            if (r > 255) tr = 255;
                            else tr = r;
                            if (g > 255) tg = 255;
                            else tg = g;
                            if (b > 255) tb = 255;
                            else tb = b;
                            this.boxElems[i][j].style.setBackground(
                                tr, tg, tb);
                        }
                        else
                            this.boxElems[i][j].style.setBackground(
                                r % 256, g % 256, b % 256);
                        r += xPower;
                        g += xPower;
                        b += xPower;
                    }
                    r += yPower;
                    g += yPower;
                    b += yPower;
                }
            };
            var self = this;
            this.timers.push(new this.objectParent.Timer(
                function() {flowRun.apply(self);}, 100));
        },

        individual: function() {
            var IndividualBox = function(boxElem) {
                this.elem = boxElem;
                this.objectParent = this.elem.objectParent;
                this.topParent = this.objectParent.objectParent;
                this.rgb = [this.topParent.randInt(255),
                            this.topParent.randInt(255),
                            this.topParent.randInt(255)];
                this.way = [this.topParent.randInt(1) || -1,
                            this.topParent.randInt(1) || -1,
                            this.topParent.randInt(1) || -1];
                this.generateSpeed();
                var self = this;
                this.objectParent.timers.push(
                    new this.topParent.Timer(function() {self.run()}, 100));
            };

            IndividualBox.prototype.generateSpeed = function() {
                this.spd = [this.topParent.randInt(5, 15),
                            this.topParent.randInt(5, 15),
                            this.topParent.randInt(5, 15)];
            };

            IndividualBox.prototype.run = function() {
                var rgb = this.rgb;
                var i;
                this.elem.style.setBackground(rgb[0], rgb[1], rgb[2]);
                for (i = 0; i < 3; i++) {
                    rgb[i] += this.spd[i] * this.way[i];
                    if (this.rgb[i] >= 255) {
                        rgb[i] = 255 - (this.spd[i] - (rgb[i] - 255));
                        this.generateSpeed();
                        this.way[i] *= -1;
                    }
                    else if (this.rgb[i] <= 0) {
                        rgb[i] = this.rgb[i] + this.spd[i];
                        this.generateSpeed();
                        this.way[i] *= -1;
                    }
                }
                for (i in rgb)
                    this.rgb[i] = rgb[i];
            };

            for (var i in this.boxElems) {
                for (var j in this.boxElems[i])
                    new IndividualBox(this.boxElems[i][j]);
            }
        }
    },

    applyEffectToImages: function() {
        var candidates = document.getElementsByTagName('img');
        for (var i = 0, node; node = candidates[i++];) {
            if (node.className.indexOf('modernart') != -1) {
                if (arguments.length > 0)
                    this.applyEffectToImage(node, arguments[0]);
                else
                    this.applyEffectToImage(node);
            }
        }
    },

    applyEffectToImage: function(imgElem) {
        var i, j;
        var size = [imgElem.offsetWidth, imgElem.offsetHeight];
        var parent = imgElem.parentNode;
        var colorFunction = imgElem.getSetting('moderncolor') || 'flow';
        var rectSize = parseInt(imgElem.getSetting('modernrect')) || 20;
        var lineWidth = parseInt(imgElem.getSetting('modernline'));
        if (isNaN(lineWidth))
            lineWidth = 5;
        var opacity = parseFloat(imgElem.getSetting('modernopacity')) || 0.5;
        var numbers = parseInt(imgElem.getSetting('modernnumbers'));
        if (arguments.length > 1)
            this.customFunction = arguments[1];
        else
            this.customFunction = null;

        if (!numbers) {
            numbers = [];
            var f = rectSize + lineWidth;
            for (i = 0; i < 2; i++)
                numbers.push(parseInt((size[i] + lineWidth) / f));
        }
        else {
            var relativeTo = imgElem.getSetting(
                'modernnumbersrel') || 'x';
            if (relativeTo == 'x') {
                numbers = [numbers,
                           parseInt(size[1] / (size[0] / numbers))];
                rectSize = ((size[0] + lineWidth) /
                            numbers[0] - lineWidth);
            }
            else { // y
                numbers = [parseInt(size[0] / (size[1] / numbers)),
                           numbers];
                rectSize = ((size[1] + lineWidth) /
                            numbers[1] - lineWidth);
            }
        }

        if (lineWidth == 0) {
            rectSize = [];
            for (i = 0; i < 2; i++)
                rectSize.push(size[i] / numbers[i]);

            lineWidth = [0, 0];
        }
        else {
            lineWidth = [];
            for (i = 0; i < 2; i++)
                lineWidth.push((size[i] - numbers[i] * rectSize) /
                               (numbers[i] - 1));

            rectSize = [rectSize, rectSize];
        }

        var container = document.createElement('div');
        container.className = imgElem.className;
        container.style.set('width', size[0] + 'px');
        container.style.set('height', size[1] + 'px');
        container.style.set('padding', '0');
        container.style.set('display', 'inline-block');
        container.style.set('float', 'none');
        found_pos = imgElem.style.getPropertyValue('position');
        container.style.set('position', imgElem.getSetting('modernposition')
        || found_pos || 'relative');

        try {
            var property, value;
            for (i in imgElem.style) {
                property = imgElem.style[i];
                value = imgElem.style.getPropertyValue(property);
                if (value)
                    container.style.set(property, value);
            }
        } catch (e) {};

        imgElem.style.set('border', 'none');
        imgElem.style.set('margin', '0');
        imgElem.style.set('left', '0');
        imgElem.style.set('top', '0');
        imgElem.style.set('background-color', '#000');
        parent.insertBefore(container, imgElem);
        parent.removeChild(imgElem);
        container.appendChild(imgElem);

        var elemPos = [0, 0];
        var boxElems = [];
        for (i = 0; i < numbers[1]; i++) {
            boxes = [];
            for (j = 0; j < numbers[0]; j++) {
                elem = document.createElement('div');
                elem.style.set('width', rectSize[0] + 'px');
                elem.style.set('height', rectSize[1] + 'px');
                elem.style.set('background-color', 'transparent');
                elem.style.set('position', 'absolute');
                elem.style.set('left', elemPos[0] + 'px');
                elem.style.set('top', elemPos[1] + 'px');
                elem.style.set('margin', '0');
                elem.style.set('padding', '0');
                elem.style.set('border', 'none');
                elem.style.setOpacity(opacity);
                boxes.push(elem);
                elemPos[0] += rectSize[0] + lineWidth[0];
            }
            boxElems.push(boxes);
            elemPos[1] += rectSize[1] + lineWidth[1];
            elemPos[0] = 0;
        }

        var lineElems = [];
        if (lineWidth[0] > 0) {
            var tempWidth, tempHeight, tempTop, tempLeft;
            for (i = 0; i < 2; i++) {
                elemPos = rectSize[i];
                if (i == 0) {
                    tempWidth = lineWidth[0];
                    tempHeight = size[1];
                }
                else {
                    tempWidth = size[0];
                    tempHeight = lineWidth[1];
                }
                for (j = 0; j < numbers[i] - 1; j++) {
                    if (i == 0) {
                        tempLeft = elemPos;
                        tempTop = 0;
                    }
                    else {
                        tempLeft = 0;
                        tempTop = elemPos;
                    }
                    elem = document.createElement('div');
                    elem.style.set('width', tempWidth + 'px');
                    elem.style.set('height', tempHeight + 'px');
                    elem.style.set('background-color', '#000');
                    elem.style.set('position', 'absolute');
                    elem.style.set('left', tempLeft + 'px');
                    elem.style.set('top', tempTop + 'px');
                    elem.style.set('margin', '0');
                    elem.style.set('padding', '0');
                    elem.style.set('border', 'none');
                    lineElems.push(elem);
                    elemPos += rectSize[i] + lineWidth[i];
                }
            }
        }
        new this.ModernArtRunner(imgElem, boxElems, lineElems, colorFunction);
    },

    randInt: function() { // function([min, ]max)
        if (arguments.length == 0)
            return 0;
        var min, max;
        if (arguments.length > 1) {
            min = arguments[0];
            max = arguments[1];
        }
        else {
            min = 0;
            max = arguments[0];
        }
        return Math.floor(Math.random() * (max + 1)) + min;
    }
});
delete createCode;

modernart.Timer.prototype.resume = function() {
    this.interval = setInterval(this.func, this.delay);
    this.running = true;
};

modernart.Timer.prototype.pause = function() {
    clearInterval(this.interval);
    delete this.interval;
    this.running = false;
};

modernart.Timer.prototype.toggle = function() {
    if (this.running)
        this.pause();
    else
        this.resume();
};

HTMLElement.prototype.getSetting = function(property) {
    var string = this.className;
    var start, end;
    start = string.indexOf(property + ':');

    if (start != -1) {
        end = string.indexOf(' ', start);
        if (end == -1)
            return string.substring(start + property.length + 1);
        else
            return string.substring(start + property.length + 1, end);
    }
};

CSSStyleDeclaration.prototype.set = function(property, value) {
    this.setProperty(property, value, null);
};

CSSStyleDeclaration.prototype.setOpacity = function(val) {
    this.set('opacity', val);
    this.set('-moz-opacity', val);
    this.set('-khtml-opacity', val);
    this.filter = 'alpha(opacity=' + val*100 + ')';
};

CSSStyleDeclaration.prototype.setBackground = function(r, g, b) {
    this.set('background-color', 'rgb('+r+', '+g+', '+b+')');
};
