;
(function($, window, document, undefined)
{
    $.fn.randomize = function(selector)
    {
        (selector ? this.find(selector) : this).parent().each(function()
        {
            $(this).children(selector).sort(function()
            {
                return Math.random() - Math.random();
            }).detach().appendTo(this);
        });

        return this;
    };

    var View = function(game, opts)
    {
        var _this = this,
            $color = $("<div/>").addClass("color"),
            getPixelIndex = function getPixelIndex(x, y)
            {
                return y * _this.imageData.width * 4 + x * 4;
            },
            hexToRGBA = function hexToRGBA(hex)
            {
                var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
                return result ? {
                    r: parseInt(result[1], 16),
                    g: parseInt(result[2], 16),
                    b: parseInt(result[3], 16),
                    a: 255
                } : null;
            },
            floodFill = function floodFill(x, y, newValue)
            {
                var queue = [],
                    mark = [],
                    imageData = _this.imageData,
                    mapWidth = imageData.width,
                    mapHeight = imageData.height,
                    color = hexToRGBA(newValue);

                for(i = 0; i < mapHeight; i++)
                {
                    mark[i] = [];
                }

                queue.push([x, y]);
                mark[y][x] = true;

                while(queue.length)
                {
                    var newPosition = queue.pop(),
                        currX = newPosition[0],
                        currY = newPosition[1],
                        index = getPixelIndex(currX, currY);

                    if(!_this.isColorBlack(currX, currY))
                    {
                        imageData.data[index + 0] = color.r;
                        imageData.data[index + 1] = color.g;
                        imageData.data[index + 2] = color.b;

                        var i = currX,
                            j = currX;

                        while(i > 0 && !_this.isColorBlack(i - 1, currY))
                        {
                            --i;
                            if(mark[currY][i])
                            {
                                break;
                            }
                            index = getPixelIndex(i, currY);
                            imageData.data[index + 0] = color.r;
                            imageData.data[index + 1] = color.g;
                            imageData.data[index + 2] = color.b;
                        }

                        while(j < mapWidth - 1 && !_this.isColorBlack(j + 1, currY))
                        {
                            ++j;
                            if(mark[currY][j])
                            {
                                break;
                            }
                            index = getPixelIndex(j, currY);
                            imageData.data[index + 0] = color.r;
                            imageData.data[index + 1] = color.g;
                            imageData.data[index + 2] = color.b;
                        }

                        for(currX = i; currX <= j; currX++)
                        {
                            if(currY > 0)
                            {
                                if(!_this.isColorBlack(currX, currY - 1))
                                {
                                    if(!mark[currY - 1][currX])
                                    {
                                        mark[currY - 1][currX] = true;
                                        queue.push([currX, currY - 1]);
                                    }
                                }
                            }
                            if(currY < mapHeight - 1)
                            {
                                if(!_this.isColorBlack(currX, currY + 1))
                                {
                                    if(!mark[currY + 1][currX])
                                    {
                                        mark[currY + 1][currX] = true;
                                        queue.push([currX, currY + 1]);
                                    }
                                }
                            }
                        }
                    }
                }
            };

        this.game = game;
        this.opts = opts;
        this.$element = $(opts.element);
        this.$image = this.$element.find("img");

        this.imageData = null;
        this.currentColor = null;
        this.sizeRatio = 1;

        this.render = function()
        {
            var loadCallback = function loadCallback()
            {
                _this.imageOriginalWidth = _this.$image.get(0).naturalWidth;
                _this.imageOriginalHeight = _this.$image.get(0).naturalHeight;

                // create canvas
                var canvasHTML = '<canvas width="' + _this.imageOriginalWidth + '" height="' + _this.imageOriginalHeight + '"></canvas>';
                _this.$canvas = $("body").append(canvasHTML).find("canvas");
                _this.canvas = _this.$canvas.get(0);
                _this.ctx = _this.canvas.getContext("2d");
                _this.ctx.drawImage(_this.$image.get(0), 0, 0, _this.imageOriginalWidth, _this.imageOriginalHeight);
                _this.imageData = _this.ctx.getImageData(0, 0, _this.imageOriginalWidth, _this.imageOriginalHeight);

                _this.$image.unbind("load", loadCallback);
                _this.$image.attr("src", _this.canvas.toDataURL("image/png"));

                _this.$canvas.hide();
                _this.resize();
            };
            this.$image.load(function()
            {
                _this.resize();
            }).load(loadCallback);

            $.each(_this.opts.colors, function(i, color)
            {
                var $col = $color.clone()
                    .attr("data-color", color)
                    .css("background-color", color)
                    .appendTo(_this.$element.find(".coloring-container .colors-container .colors-wrapper"));
            });

            this.$element.find(".popup.popup-instruction, .popup-overlay").show();

            this.$image.attr("src", this.opts.image);
        };

        this.delegateEvents = function()
        {
            this.$image.on("click", function(e)
            {
                if(!_this.currentColor)
                {
                    _this.game.playErrorSound();
                    return false;
                }
                var posX = Math.floor((e.clientX - $(this).offset().left) / _this.sizeRatio),
                    posY = Math.floor((e.clientY - $(this).offset().top) / _this.sizeRatio);

                if(!_this.isColorBlack(posX, posY))
                {
                    _this.colorImage(posX, posY, _this.currentColor);
                }
                else
                {
                    _this.game.playErrorSound();
                }
                return false;
            });
            this.$element.find(".popup.popup-instruction .start").on("click", function()
            {
                _this.$element.find(".popup.popup-instruction, .popup-overlay").hide();
                return false;
            });

            this.$element.find(".colors-container .color").on("click", function()
            {
                _this.currentColor = $(this).attr("data-color");
                _this.$element.addClass("cursor-paintbrush");
                _this.$element.find(".color.is-active").removeClass("is-active");
                $(this).addClass("is-active");
                return false;
            });

            this.$element.find("#refresh").on("click", function()
            {
                window.location.replace("../index.html");
                return false;
            });

            $(window).on("resize", function()
            {
                _this.resize();
            });
        };

        this.resize = function()
        {
            this.sizeRatio = this.$image.width() / this.$image.get(0).naturalWidth;

            //this.$element.find(".wrapper").css("line-height", this.$element.find(".wrapper").height() + "px");
            this.$element.find(".coloring-container .colors-container .color").width(this.$element.find(".coloring-container .colors-container .color").height());
            this.$element
                .find(".coloring-container .image-container")
                .height(this.$element.find(".coloring-container").height());
            this.$element.find(".colors-container").css("line-height", this.$element.find(".colors-container").height() + "px");

            var elementsPerRow = 1,
                elementSize = this.$element.find(".colors-container").innerWidth() - Math.floor(parseInt(this.$element.find(".colors-container").css("padding-left")) * 2),
                marginBottom = parseInt(this.$element.find(".colors-container .color").css("margin-bottom")),
                marginRight = parseInt(this.$element.find(".colors-container .color").css("margin-right")),
                containerHeight = this.$element.find(".colors-container").innerHeight() - Math.floor(parseInt(this.$element.find(".colors-container").css("padding-bottom")) * 2),
                containerWidth = this.$element.find(".colors-container").innerWidth() - Math.floor(parseInt(this.$element.find(".colors-container").css("padding-left")) * 2),
                elementsCount = this.$element.find(".colors-container .color").size(),
                rows = elementsCount;

            while(rows * (elementSize + marginBottom) > containerHeight)
            {
                elementSize -= 1;
                elementsPerRow = Math.floor(containerWidth / (elementSize + marginRight));
                rows = Math.ceil(elementsCount / elementsPerRow);
            }
            this.$element.find(".colors-container .color").width(elementSize - 4).height(elementSize - 4);
        };

        this.isColorBlack = function(x, y)
        {
            var index = getPixelIndex(x, y),
                r = this.imageData.data[index + 0],
                g = this.imageData.data[index + 1],
                b = this.imageData.data[index + 2];

            return r < 10 && g < 10 && b < 10;
        };

        this.colorImage = function(x, y, color)
        {
            floodFill(x, y, color);
            this.ctx.putImageData(this.imageData, 0, 0);
            this.$image.attr("src", _this.canvas.toDataURL("image/png"));
        }
    };

    var Game = function(opts)
    {
        var _this = this,
            audio = document.createElement('audio'),
            supportAudio = !!(audio.canPlayType && audio.canPlayType('audio/mpeg;').replace(/no/, ''));

        this.opts = opts;
        this.view = new View(this, opts);

        this.start = function()
        {
            this.view.render();
            this.view.delegateEvents();
        };

        this.playErrorSound = function()
        {
            var audio = new Audio("audio/error.mp3");
            audio.play();
        }
    };

    window.Game = Game;

})(jQuery, window, document);