;
(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 puzzleMaskMatrix = {
        path: 'img/puzzle-mask/',
        rows: 3,
        cols: 4,
        pieces: [
            {
                name: 'puzzle00',
                image: '00.png',
                margins: {
                    top: false,
                    bottom: false,
                    left: false,
                    right: true
                }
            },
            {
                name: 'puzzle01',
                image: '01.png',
                margins: {
                    top: false,
                    bottom: false,
                    left: false,
                    right: true
                }
            },
            {
                name: 'puzzle02',
                image: '02.png',
                margins: {
                    top: false,
                    bottom: true,
                    left: false,
                    right: false
                }
            },
            {
                name: 'puzzle03',
                image: '03.png',
                margins: {
                    top: false,
                    bottom: false,
                    left: true,
                    right: false
                }
            },
            {
                name: 'puzzle04',
                image: '04.png',
                margins: {
                    top: true,
                    bottom: true,
                    left: false,
                    right: true
                }
            },
            {
                name: 'puzzle05',
                image: '05.png',
                margins: {
                    top: true,
                    bottom: false,
                    left: false,
                    right: false
                }
            },
            {
                name: 'puzzle06',
                image: '06.png',
                margins: {
                    top: false,
                    bottom: false,
                    left: true,
                    right: false
                }
            },
            {
                name: 'puzzle07',
                image: '07.png',
                margins: {
                    top: true,
                    bottom: true,
                    left: true,
                    right: false
                }
            },
            {
                name: 'puzzle08',
                image: '08.png',
                margins: {
                    top: false,
                    bottom: false,
                    left: false,
                    right: false
                }
            },
            {
                name: 'puzzle09',
                image: '09.png',
                margins: {
                    top: true,
                    bottom: false,
                    left: true,
                    right: true
                }
            },
            {
                name: 'puzzle10',
                image: '10.png',
                margins: {
                    top: true,
                    bottom: false,
                    left: false,
                    right: false
                }
            },
            {
                name: 'puzzle11',
                image: '11.png',
                margins: {
                    top: false,
                    bottom: false,
                    left: true,
                    right: false
                }
            }
        ],
        size: {
            margin: 50,
            width: 200,
            height: 200
        }
    };

    var View = function(game, opts)
    {
        var _this = this,
            loadedMasks = 0,
            puzzleMaskImages = {};

        this.game = game;
        this.opts = opts;
        this.$element = $(opts.element);
        this.eventsDelegated = false;
        this.puzzlePositions = {};
        $(window).on("resize", function()
        {
            _this.$element.find("table, .puzzle").remove();
            _this.resize();
        });

        this.start = function()
        {
            this.$image = $("<img/>").attr("src", opts.image).load(function()
            {
                _this.resize();
            });
            this.image = this.$image[0];

            this.$element.find(".popup-overlay, .popup-instruction").show();
            this.$element.find(".popup.full-image .content").append(this.$image.clone());
        };

        this.delegateEvents = function()
        {
            this.$element.find(".popup.full-image").on("click", function()
            {
                $(this).hide();
                return false;
            });

            this.$element.find("#refresh").on("click", function()
            {
                window.location.reload();
            });

            this.$element.find(".puzzle-container-outer .next").on("click", function()
            {
                var $this = $(this),
                    left = parseInt($this.parent().find(".puzzle-container").css("left"));
                $this.parent().find(".puzzle-container").stop(false, true).animate({
                    left: left - 200
                }, 200);
            });

            this.$element.find(".puzzle-container-outer .prev").on("click", function()
            {
                var $this = $(this),
                    left = parseInt($this.parent().find(".puzzle-container").css("left"));
                $this.parent().find(".puzzle-container").stop(false, true).animate({
                    left: left + 200
                }, 200);
            });

            this.$element.find(".popup-instruction .start").on("click", function()
            {
                _this.$element.find(".popup-instruction, .popup-overlay").hide();
            });
        };

        this.render = function()
        {
            var $table = $("<table></table>").appendTo(".image").addClass("border"),
                x, y,
                i = 0;

            if(!this.eventsDelegated)
            {
                this.delegateEvents();
                this.eventsDelegated = true;
            }

            this.$image.appendTo("body");

            this.imageWidth = this.$image.width();
            this.imageHeight = this.$image.height();

            this.$image.appendTo(this.$element.find(".wrapper .image"));

            this.resizedImageWidth = this.$image.width();
            this.resizedImageHeight = this.$image.height();

            this.$image.css({
                visibility: "hidden"
            });

            for(y = 0; y < this.opts.rows; y += 1)
            {
                var $tr = $("<tr/>").appendTo($table);
                for(x = 0; x < this.opts.cols; x += 1)
                {
                    var $td = $("<td><div></div></td>").appendTo($tr)
                        .attr("data-index", i);

                    i += 1;
                }
            }
            this.$element.find("table").clone().appendTo(this.$element.find(".wrapper .image")).addClass("puzzle-placeholder").removeClass("border");
            this.$element.find("table.border td")
                .css({
                    width: Math.floor(this.resizedImageWidth / this.opts.cols) - 4,
                    maxWidth: Math.floor(this.resizedImageWidth / this.opts.cols) - 4,
                    height: Math.floor(this.resizedImageHeight / this.opts.rows) - 4,
                    maxHeight: Math.floor(this.resizedImageHeight / this.opts.rows) - 4
                });
            this.$element.find("table.puzzle-placeholder td")
                .css({
                    width: Math.floor(this.resizedImageWidth / this.opts.cols),
                    maxWidth: Math.floor(this.resizedImageWidth / this.opts.cols),
                    height: Math.floor(this.resizedImageHeight / this.opts.rows),
                    maxHeight: Math.floor(this.resizedImageHeight / this.opts.rows)
                })
                .find("div").droppable({
                    accept: '.puzzle',
                    drop: function(evt, ui)
                    {
                        if($(this).find(".puzzle").size())
                        {
                            $(this).find(".puzzle").appendTo(_this.$element.find(".puzzle-container")).css({
                                marginLeft: "0",
                                marginTop: "0"
                            });
                        }

                        var $draggable = $(ui.draggable).detach().css({
                            top: "auto",
                            left: "auto"
                        }).appendTo($(this));

                        $(this).removeClass("active-drop");

                        _this.puzzlePositions[$draggable.attr("data-puzzle-id")] = $(this).parent().attr("data-index");
                        _this.check();
                        _this.recalculateMargins();
                    },
                    over: function(event, ui)
                    {
                        $(this).addClass('active-drop');
                    },
                    out: function(event, ui)
                    {
                        $(this).removeClass('active-drop');
                    }
                }).end();

            this.$element.find(".puzzle-container").droppable({
                accept: ".puzzle",
                drop: function(evt, ui)
                {
                    var $draggable = $(ui.draggable).detach().css({
                        position: "relative",
                        top: "auto",
                        left: "auto",
                        marginLeft: 0,
                        marginTop: 0
                    }).appendTo($(this));

                    $(this).removeClass("active-drop");

                    _this.puzzlePositions[$draggable.attr("data-puzzle-id")] = false;
                },
                over: function(event, ui)
                {
                    $(this).addClass('active-drop');
                },
                out: function(event, ui)
                {
                    $(this).removeClass('active-drop');
                }
            });

            this.$element.find("table").css({
                marginLeft: (-Math.floor(parseInt(this.$element.find("table").width()) / 2)) + "px"
            });

            this.generatePuzzles();
        };

        this.generatePuzzles = function()
        {
            var y, x, i = 0,
                _this = this,
                puzzleImageCanvas = document.createElement("canvas"),
                puzzleImageCtx = puzzleImageCanvas.getContext("2d"),
                scaleV = this.resizedImageWidth / this.imageWidth,
                scaleH = this.resizedImageHeight / this.imageHeight,
                puzzleImage = new Image(),
                puzzleIndex = 0;

            loadedMasks = 0;

            puzzleImageCanvas.width = this.resizedImageWidth;
            puzzleImageCanvas.height = this.resizedImageHeight;

            puzzleImageCtx.scale(scaleV, scaleH);
            puzzleImageCtx.drawImage(this.$image[0], 0, 0);

            puzzleImage.src = puzzleImageCanvas.toDataURL("image/png");
            puzzleImage.onload = function()
            {
                for(y = 0; y < _this.opts.rows; y += 1)
                {
                    for(x = 0; x < _this.opts.cols; x += 1)
                    {
                        _this.generatePuzzle(x, y, i, puzzleImage, puzzleIndex++);
                        i += 1;
                    }
                }
            }
        };

        this.generatePuzzle = function(x, y, i, puzzleImage, puzzleIndex)
        {
            var _this = this,
                $puzzle = $("<div class='puzzle'><img src='' alt='' /></div>"),
                puzzleMask = puzzleMaskMatrix.pieces[i];

            puzzleMaskImages[i] = new Image();

            puzzleMaskImages[i].onload = (function(opts)
            {
                return function()
                {
                    var canvas = document.createElement("canvas"),
                        context,
                        margins = opts.puzzleMask.margins,
                        top = 0,
                        left = 0,
                        bottom = 0,
                        right = 0,
                        width = _this.resizedImageWidth / _this.opts.cols,
                        height = _this.resizedImageHeight / _this.opts.rows,
                        aspectWidth = width / puzzleMaskMatrix.size.width,
                        aspectHeight = height / puzzleMaskMatrix.size.height,
                        diffH, diffV;

                    if(margins.top)
                    {
                        diffH = Math.abs(height - (opts.puzzleMaskImage.height * aspectHeight));
                        if(margins.bottom)
                        {
                            top = bottom = Math.floor(diffH / 2);
                        }
                        else
                        {
                            top = diffH;
                        }

                    }
                    if(margins.bottom)
                    {
                        diffH = Math.abs(height - (opts.puzzleMaskImage.height * aspectHeight));
                        if(margins.top)
                        {
                            top = bottom = Math.floor(diffH / 2);
                        }
                        else
                        {
                            bottom = diffH;
                        }
                    }
                    if(margins.left)
                    {
                        diffV = Math.abs(width - (opts.puzzleMaskImage.width * aspectWidth));
                        if(margins.right)
                        {
                            left = right = Math.floor(diffV / 2);
                        }
                        else
                        {
                            left = diffV;
                        }
                    }
                    if(margins.right)
                    {
                        diffV = Math.abs(width - (opts.puzzleMaskImage.width * aspectWidth));
                        if(margins.left)
                        {
                            right = left = Math.floor(diffV / 2);
                        }
                        else
                        {
                            right = diffV;
                        }
                    }

                    top = Math.round(top);
                    bottom = Math.round(bottom);
                    left = Math.round(left);
                    right = Math.round(right);

                    canvas.width = width + left + right;
                    canvas.height = height + top + bottom;
                    context = canvas.getContext("2d");
                    context.drawImage(opts.puzzleMaskImage, 0, 0, width + left + right, height + top + bottom);
                    context.globalCompositeOperation = 'source-atop';

                    var sx = (opts.x * width) - left,
                        sy = (opts.y * height) - top,
                        sWidth = width + right + left,
                        sHeight = height + bottom + top;

                    try
                    {
                        context.drawImage(opts.puzzleImage, sx, sy, sWidth, sHeight, 0, 0, canvas.width, canvas.height);
                    }
                    catch(exception)
                    {

                    }

                    $puzzle
                        .clone()
                        .find("img")
                        .attr("src", canvas.toDataURL("image/png"))
                        .end()
                        .attr("data-puzzle-id", opts.puzzleIndex)
                        .attr("data-margin-top", top)
                        .attr("data-margin-bottom", bottom)
                        .attr("data-margin-left", left)
                        .attr("data-margin-right", right)
                        .appendTo(_this.$element.find(".puzzle-container"))
                        .draggable({
                            helper: "clone",
                            revert: "invalid",
                            appendTo: _this.$element.find(".wrapper")
                        });

                    loadedMasks += 1;

                    if(loadedMasks === 12)
                    {
                        $.each(_this.puzzlePositions, function(puzzleID, cellIndex)
                        {
                            if(cellIndex)
                            {
                                _this.$element.find("[data-puzzle-id='" + puzzleID + "']").appendTo(_this.$element.find("table.puzzle-placeholder td:eq(" + cellIndex + ") div"));
                            }
                        });

                        _this.recalculateMargins();

                        _this.$element.find(".puzzle-container .puzzle").randomize();
                    }
                };
            }({
                index: i,
                puzzleMask: puzzleMask,
                puzzleMaskImage: puzzleMaskImages[i],
                puzzleImage: puzzleImage,
                x: x,
                y: y,
                puzzleIndex: puzzleIndex
            }));
            puzzleMaskImages[i].src = puzzleMaskMatrix.path + puzzleMask.image;
        };

        this.recalculateMargins = function()
        {
            this.$element.find("table .puzzle").each(function()
            {
                var $puzzle = $(this);
                if($puzzle.attr("data-margin-left"))
                {
                    $puzzle.css("margin-left", (-parseInt($puzzle.attr("data-margin-left"))) + "px");
                }
                if($puzzle.attr("data-margin-top"))
                {
                    $puzzle.css("margin-top", (-parseInt($puzzle.attr("data-margin-top"))) + "px");
                }
            })
        };

        this.resize = function()
        {
            var _this = this;
            loadedMasks = 0;

            this.render();

            var lineHeight = this.$element.find(".puzzle-container-outer").height();
            this.$element.find(".puzzle-container-outer a").css("line-height", lineHeight + "px");
            this.$element.find(".puzzle-container").css("line-height", lineHeight + "px");
        };

        this.check = function()
        {
            var total = 12,
                correct = 0,
                incorrect = 0;

            if(this.$element.find(".puzzle-placeholder .puzzle").size() === this.opts.rows * this.opts.cols)
            {
                this.$element.find(".puzzle-placeholder .puzzle").each(function()
                {
                    var puzzleID = $(this).attr("data-puzzle-id"),
                        rowID = $(this).closest("td").attr("data-index");

                    if(puzzleID === rowID)
                    {
                        correct += 1;
                    }
                    else
                    {
                        incorrect += 1;
                    }
                });

                if(incorrect > 0)
                {
                    this.$element.find(".popup-result-try-again .correct").text(correct);
                    this.$element.find(".popup-result-try-again .incorrect").text(incorrect);
                    this.$element.find(".popup-result-try-again, .popup-overlay").show();
                }
                else
                {
                    this.$element.find(".popup-result-success, .popup-overlay").show();
                }
            }
        };
    };

    var Game = function(opts)
    {
        this.view = new View(this, opts);

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

    window.Game = Game;
})(jQuery, window, document);