/**
 * Klasse PuzzlePice
 * Teil in einem Puzzle
 *
 * @constructor
 * @param {float} pStartPosX Startposition.x
 * @param {float} pStartPosY Startposition.y
 * @param {float} pDestinationPosX Zielposition.x
 * @param {float} pDestinationPosY Zielposition.y
 * @param {float} pWidth Briet
 * @param {float} pHeight Höhe
 * @param {string} pImgSrc Bildpfad
 * @param {Box} rStartBox Startbox
 * @param {Box} rDestinationBox Zielbox
 */

function PuzzlePiece(pStartPosX, pStartPosY, pDestinationPosX, pDestinationPosY, pWidth, pHeight, pImgSrc, rStartBox, rDestinationBox) {
    this.pType = "PuzzlePiece";
    this.pStartPosX = pStartPosX;
    this.pStartPosY = pStartPosY;
    this.pDestinationPosX = pDestinationPosX;
    this.pDestinationPosY = pDestinationPosY;
    this.pPosX = this.pStartPosX;
    this.pPosY = this.pStartPosY;
    this.pInitialPosX = this.pPosX;
    this.pInitialPosY = this.pPosY;
    this.pWidth = pWidth;
    this.pHeight = pHeight;
    this.pInitialWidth = pWidth;
    this.pInitialHeight = pHeight;
    this.pImgSrc = pImgSrc;
    this.rSelector;
    this.pIsMouseDown = false;
    this.pLastPosX = 0;
    this.pLastPosY = 0;
    this.pSolved = false;
    this.pScale = 1;
    this.rBox = rStartBox;
    this.rDestinationBox = rDestinationBox;
    this.rStartBox = rStartBox;
    this.rPositions = [];
    this.pJsonId = newJsonId();
    //this.targetElement = targetElement;

    /**
     * Setzt die Position des Teils
     * @param {float} pPosX X-Position
     * @param {float} pPosY Y-Position
     */
    this.setPos = function (pPosX, pPosY) {
        // set position of element
        this.pPosX = pPosX;
        this.pPosY = pPosY;
        $(this.rSelector).css("left", pPosX * this.pScale + "px");
        $(this.rSelector).css("top", pPosY * this.pScale + "px");

        // limit position to contentFrame
        var contentFrame = $("#contentFrame");
        var contentFrameOffset = contentFrame.offset();
        var contentFrameWidth = contentFrame.width();
        var contentFrameHeight = contentFrame.height();
        var contentFrameLeft = contentFrameOffset.left;
        var contentFrameTop = contentFrameOffset.top;
        var contentFrameRight = contentFrameLeft + contentFrameWidth;
        var contentFrameBottom = contentFrameTop + contentFrameHeight;

        var thisOffset = $(this.rSelector).offset();
        var thisWidth = $(this.rSelector).width();
        var thisHeight = $(this.rSelector).height();
        var thisLeft = thisOffset.left;
        var thisTop = thisOffset.top;
        var thisRight = thisLeft + thisWidth;
        var thisBottom = thisTop + thisHeight;

        if (thisLeft < contentFrameLeft) {
            pPosX += contentFrameLeft - thisLeft;
            // console.log("left");
        } else if (thisRight > contentFrameRight) {
            pPosX -= thisRight - contentFrameRight;
            // console.log("right");
        }
        if (thisTop < contentFrameTop) {
            pPosY += contentFrameTop - thisTop;
            // console.log("top");
        } else if (thisBottom > contentFrameBottom) {
            pPosY -= thisBottom - contentFrameBottom;
            // console.log("bottom");
        }

        if (this.pIsMouseDown) {
            // set position of element
            this.pPosX = pPosX;
            this.pPosY = pPosY;
            $(this.rSelector).css("left", pPosX * this.pScale + "px");
            $(this.rSelector).css("top", pPosY * this.pScale + "px");
        }
    };

    /**
     * Setzt die letzte Position des Teils
     * @param {float} pPosX X-Position
     * @param {float} pPosY Y-Position
     */
    this.setLastPos = function (pPosX, pPosY) {
        this.pLastPosX = pPosX;
        this.pLastPosY = pPosY;
    };

    /**
     * Funktion wenn ein mousedown-Event stattfindet
     * @param {event} e
     */
    this.mousedown = function (e) {
        this.pMouseOffsetX = e.clientX - $(this.rSelector).offset().left;
        this.pMouseOffsetY = e.clientY - $(this.rSelector).offset().top;
        if (true) {
            this.pIsMouseDown = true;
            this.setLastPos(e.clientX, e.clientY);
            //$(this.rSelector).appendTo(this.rPatent);
            //$(this.rBox.rSelector).appendTo(this.rBox.rPatent);
            $(this.rSelector).css("z-index", 99);
        } else {
            e.preventDefault();
        }
    };

    /**
     * Funktion wenn ein mouseup-Event stattfindet
     * @param {event} e
     */
    this.mouseup = function (e) {
        if (this.pIsMouseDown) {
            if (e.clientX < $(this.rBox.rSelector).offset().left || e.clientX > $(this.rBox.rSelector).offset().left + parseFloat($(this.rBox.rSelector).css("width")) || e.clientY < $(this.rBox.rSelector).offset().top || e.clientY > $(this.rBox.rSelector).offset().top + parseFloat($(this.rBox.rSelector).css("height"))) {
                // console.log("outside");
                for (let i = 0; i < boards[activeBoard].rObjects.length; i++) {
                    if (boards[activeBoard].rObjects[i].pType == "Box") {
                        var newBox = boards[activeBoard].rObjects[i];
                        if (e.clientX < parseFloat($(newBox.rSelector).offset().left) + parseFloat($(newBox.rSelector).css("width")) && e.clientX >= parseFloat($(newBox.rSelector).offset().left)) {
                            if (e.clientY < parseFloat($(newBox.rSelector).offset().top) + parseFloat($(newBox.rSelector).css("height")) && e.clientY >= parseFloat($(newBox.rSelector).offset().top)) {
                                // console.log("box switched");
                                $(this.rSelector).appendTo($(newBox.rSelector));
                                this.rPatent = $(newBox.rSelector);

                                //console.log("off: " + e.clientX + ", " + $(newBox.rSelector).offset().left);
                                this.setPos((e.clientX - this.pMouseOffsetX - parseFloat($(newBox.rSelector).offset().left)) / this.pScale, (e.clientY - this.pMouseOffsetY - parseFloat($(newBox.rSelector).offset().top)) / this.pScale);
                                this.rBox = newBox;
                                //this.rPatent = newBox;
                            }
                        }
                    }
                }
            }

            var targetX = parseFloat($(this.targetElement).css("left")) / this.pScale;
            var targetY = parseFloat($(this.targetElement).css("top")) / this.pScale;
            var dist = Math.sqrt(Math.pow(this.pPosX - (this.pDestinationPosX + targetX), 2) + Math.pow(this.pPosY - (this.pDestinationPosY + targetY), 2));
            if (this.rDestinationBox == this.rBox && dist < 20) {
                this.solve();
                // console.log("PIECE SOLVED");
            } else {
                this.pSolved = false;
                for (let i = 0; i < this.rPositions.length; i++) {
                    var position = this.rPositions[i];
                    if (this.rBox == position.rDestinationBox) {
                        dist = Math.sqrt(Math.pow(this.pPosX - (position.pDestinationPosX + targetX), 2) + Math.pow(this.pPosY - (position.pDestinationPosY + targetY), 2));
                        if (dist < 20) {
                            this.setPos(position.pDestinationPosX + targetX, position.pDestinationPosY + targetY);
                        }
                    }
                }
            }
            $(this.rSelector).css("z-index", this.pZ);
        }
        this.pIsMouseDown = false;
    };

    /**
     * Funktion wenn ein mousemove-Event stattfindet
     * @param {event} e
     */
    this.mousemove = function (e) {
        if (this.pIsMouseDown) {
            var deltaX = this.pLastPosX - e.clientX;
            var deltaY = this.pLastPosY - e.clientY;
            this.setPos(this.pPosX - deltaX / this.pScale, this.pPosY - deltaY / this.pScale);
            //console.log((parseFloat($(this.rSelector).css("left")) - deltaX) + "px");

            this.setLastPos(e.clientX, e.clientY);
        }
    };

    /**
     * Erstellt das Objekt
     * @param {jqueryelement} rParent
     */
    this.create = function (rParent) {
        this.rPatent = rParent;
        this.rSelector = $('<img src="' + this.pImgSrc + '" class="puzzlePiece">').appendTo(this.rPatent);
        makeUnselectable(this.rSelector);

        $(this.rSelector).css("width", this.pWidth * this.pScale + "px");
        $(this.rSelector).css("height", this.pHeight * this.pScale + "px");
        $(this.rSelector).css("left", this.pPosX * this.pScale + "px");
        $(this.rSelector).css("top", this.pPosY * this.pScale + "px");

        var t = this;
        // mouse input
        $(this.rSelector).mousedown(function (e) {
            t.mousedown(e);
        });
        $("#body").mouseup(function (e) {
            t.mouseup(e);
        });
        $("#contentFrame").mousemove(function (e) {
            t.mousemove(e);
        });

        // touch input
        $(this.rSelector).on("touchstart", function (e) {
            e.preventDefault();
            var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
            e.clientX = touch.pageX;
            e.clientY = touch.pageY;
            t.mousedown(e);
        });
        $("#body").on("touchend", function (e) {
            // e.preventDefault();
            var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
            e.clientX = touch.pageX;
            e.clientY = touch.pageY;
            t.mouseup(e);
        });

        $("#contentFrame").on("touchmove", function (e) {
            e.preventDefault();
            var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
            e.clientX = touch.pageX;
            e.clientY = touch.pageY;
            t.mousemove(e);
        });

        this.reset();
        this.setZ(this.pZ);
    };

    /**
     * Setzt die Skalierung des Teils
     * @param {float} pScale Skalierung
     */
    this.setScale = function (pScale) {
        this.pScale = pScale;
        $(this.rSelector).css("width", this.pWidth * this.pScale + "px");
        $(this.rSelector).css("height", this.pHeight * this.pScale + "px");
        $(this.rSelector).css("left", this.pPosX * this.pScale + "px");
        $(this.rSelector).css("top", this.pPosY * this.pScale + "px");
    };

    /**
     * Stellt den Lösungszustand her
     */
    this.solve = function () {
        $(this.rSelector).appendTo($(this.rDestinationBox.rSelector));
        this.rBox = this.rDestinationBox;
        this.rPatent = this.rDestinationBox.rSelector;
        var targetX = parseFloat($(this.targetElement).css("left")) / this.pScale;
        var targetY = parseFloat($(this.targetElement).css("top")) / this.pScale;
        this.setPos(this.pDestinationPosX + targetX, this.pDestinationPosY + targetY);
        this.pSolved = true;
        this.pIsMouseDown = false;
    };

    /**
     * Gibt an ob der Lösungszustand vorliegt
     * @returns {boolean} true falls das Teil richtig geloest ist. Sonst false.
     */
    this.isSolved = function () {
        return this.pSolved;
    };

    /**
     * Stellt den Ausgangszustand her
     */
    this.reset = function () {
        $(this.rSelector).appendTo($(this.rStartBox.rSelector));
        this.rBox = this.rStartBox;
        this.rPatent = $(this.rStartBox.rSelector);
        this.setPos(this.pStartPosX, this.pStartPosY);
        this.pSolved = false;

        var t = this;
        $(this.rSelector).unbind("mousedown");
        $(this.rSelector).mousedown(function (e) {
            t.mousedown(e);
        });
        $(this.rSelector).unbind("mouseup");
        $(this.rSelector).mouseup(function (e) {
            t.mouseup(e);
        });
        $(this.rSelector).unbind("mousemove");
        $(this.rSelector).mousemove(function (e) {
            t.mousemove(e);
        });
        $(this.rSelector).unbind("touchstart");
        $(this.rSelector).on("touchstart", function (e) {
            e.preventDefault();
            var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
            e.clientX = touch.pageX;
            e.clientY = touch.pageY;
            t.mousedown(e);
        });
        $(this.rSelector).unbind("touchend");
        $(this.rSelector).on("touchend", function (e) {
            e.preventDefault();
            var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
            e.clientX = touch.pageX;
            e.clientY = touch.pageY;
            t.mouseup(e);
        });
        $(this.rSelector).unbind("touchmove");
        $("#contentFrame").on("touchmove", function (e) {
            e.preventDefault();
            var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
            e.clientX = touch.pageX;
            e.clientY = touch.pageY;
            t.mousemove(e);
        });
        this.pIsMouseDown = false;
    };

    /**
     * Speichert den Zustand des Teils in einem JSON Objekt
     * @returns {JSON} JSON Objekt
     */
    this.export = function () {
        var obj = {
            pJsonId: this.pJsonId,
            pType: this.pType,
            pSolved: this.pSolved,
            pPosX: this.pPosX,
            pPosY: this.pPosY,
            rBox: this.rBox.pJsonId,
        };

        return obj;
    };

    /**
     * Importiert den Zustand des Teils aus einem JSON Objekt
     * @param {JSON} rObj JSON-Objekt
     */
    this.import = function (rObj) {
        if (rObj.pType == this.pType && rObj.pJsonId == this.pJsonId) {
            var parentBox;
            block: {
                for (let i in boards) {
                    for (let j in boards[i].rObjects) {
                        if (boards[i].rObjects[j].pType == "Box") {
                            var box = boards[i].rObjects[j];
                            if (boards[i].rObjects[j].pJsonId == rObj.rBox) {
                                $(this.rSelector).appendTo($(box.rSelector));
                                this.rPatent = $(box.rSelector);
                                this.rBox = box;
                                break block;
                            }
                        }
                    }
                }
            }
            this.setPos(rObj.pPosX, rObj.pPosY);
            this.pSolved = rObj.pSolved;
        } else {
            console.log("ERROR importing puzzlePiece");
        }
    };

    /**
     * Ordnet das Teil einer neuen Box zu
     * @param {Box} newBox
     */
    this.switchBox = function (newBox) {};

    /**
     * Setzt den css Z-Index des Teils
     * @param {int} pZ
     */
    this.setZ = function (pZ) {
        $(this.rSelector).css("z-index", pZ);
        this.pZ = pZ;
    };
}

/**
 * Klasse Puzzle
 *
 * @constructor
 * @param {string} pTitle
 */
function Puzzle(pTitle) {
    this.pTitle = pTitle;
    this.pType = "Puzzle";
    this.pSolved = false;
    this.rPieces = [];
    this.pJsonId = newJsonId();

    /**
     * Fuegt dem Puzzle ein Hintergrundbild hinzu
     * @param {string} pImgSrc Pfad des Bildes
     * @param {float} pPosX Position.x des Bildes
     * @param {float} pPosY Postiion.y des Bildes
     * @param {float} pWidth Breite des Bildes
     * @param {float} pHeight Höhe des Bildes
     * @param {Box} rBox Box in dem sich das Bild befindet
     * @param {float} pBorderRadius Abrundung der Ecken
     */
    this.addBackgroundImage = function (pImgSrc, pPosX, pPosY, pWidth, pHeight, rBox, pBorderRadius) {
        this.background = $('<img src="' + pImgSrc + '" class="puzzleBackground">').prependTo(rBox.rSelector);
        $(this.background).css("width", pWidth + "px");
        $(this.background).css("height", pHeight + "px");
        $(this.background).css("left", pPosX + "px");
        $(this.background).css("top", pPosY + "px");
        $(this.background).attr("initialPosX", pPosX);
        $(this.background).attr("initialPosY", pPosY);
        $(this.background).attr("pInitialWidth", pWidth);
        $(this.background).attr("pInitialHeight", pHeight);
        $(this.background).attr("draggable", false);

        if (pBorderRadius != null) {
            $(this.background).css("border-radius", pBorderRadius + "px");
            $(this.background).attr("pInitialBorder-radius", pBorderRadius);
        } else {
            $(this.background).css("border-radius", 0 + "px");
            $(this.background).attr("pInitialBorder-radius", 0);
        }

        for (let i = 0; i < this.rPieces.length; i++) {
            this.rPieces[i].targetElement = this.background;
        }
    };

    /**
     * Fügt dem Puzzle ein neues Teil hinzu
     * @param {string} pImg Pfad des Bildes das das Teil darstellt
     * @param {float} pWidth Breite des Teils
     * @param {float} pHeight Höhe des Teils
     * @param {Box} rStartBox Box in der sich das Teil befindet
     * @param {float} pStartPosX Startposition.x des Teils
     * @param {float} pStartPosY Startpostion.y des Teils
     * @param {Box} rDestinationBox Zielbox der Lösungsposition
     * @param {float} pDestinationPosX Zielposition.x
     * @param {float} pDestinationPosY Zielposition.y
     */
    this.addPiece = function (pImg, pWidth, pHeight, rStartBox, pStartPosX, pStartPosY, rDestinationBox, pDestinationPosX, pDestinationPosY) {
        //this.addPiece = function (pStartPosX, pStartPosY, pDestinationPosX, pDestinationPosY, pWidth, pHeight, pImg, rStartBox, rDestinationBox) {
        this.rPieces.push(new PuzzlePiece(pStartPosX, pStartPosY, pDestinationPosX, pDestinationPosY, pWidth, pHeight, pImg, rStartBox, rDestinationBox));
        this.rPieces[this.rPieces.length - 1].setZ(this.rPieces.length - 1 + 4);
    };

    /**
     * Erstellt das Element
     * @param {jqueryelement} rParent
     */
    this.create = function (rParent) {
        this.rPatent = rParent;
        //this.rSelector = $('<div class="puzzle"></div>').appendTo(this.rPatent);
        //makeUnselectable(this.rSelector);
        for (let i = 0; i < this.rPieces.length; i++) {
            this.rPieces[i].create(this.rPatent);
        }

        // individual solve button
        var btns = boards[this.pBoardId].addSolvabel(this);
        this.rSolve = btns.solve;
        this.rReset = btns.reset;
    };

    /**
     * Setzt das Icon fuer den Solve-Knopf
     * @param {String} pIcon Pfad zum Icon-Bild
     */
    this.setSolveIcon = function (pIcon) {
        this.rSolve.css("background-image", "url(" + pIcon + ")");
    };

    /**
     * Setzt das Icon fuer den Reset-Knopf
     * @param {String} pIcon Pfad zum Icon-Bild
     */
    this.setResetIcon = function (pIcon) {
        this.rReset.css("background-image", "url(" + pIcon + ")");
        //.log(pIcon);
    };

    /**
     * Setzt die Skalierung
     * @param {float} pScale
     */
    this.setScale = function (pScale) {
        this.pScale = pScale;
        $(this.background).css("left", parseFloat($(this.background).attr("initialPosX")) * this.pScale + "px");
        $(this.background).css("top", parseFloat($(this.background).attr("initialPosY")) * this.pScale + "px");
        $(this.background).css("width", parseFloat($(this.background).attr("pInitialWidth")) * this.pScale + "px");
        $(this.background).css("height", parseFloat($(this.background).attr("pInitialHeight")) * this.pScale + "px");
        $(this.background).css("border-radius", parseFloat($(this.background).attr("pInitialBorder-radius")) * this.pScale + "px");
        for (let i = 0; i < this.rPieces.length; i++) {
            this.rPieces[i].setScale(this.pScale);
        }
    };

    /**
     * Stellt den Ausganszustand her
     */
    this.reset = function () {
        for (let i = 0; i < this.rPieces.length; i++) {
            this.rPieces[i].reset();
        }
    };

    /**
     * Stellt den Loesungszustand her
     */
    this.solve = function () {
        for (let i = 0; i < this.rPieces.length; i++) {
            this.rPieces[i].solve();
        }
        this.pSolved = true;
    };

    /**
     * Gibt an ob der Lösungszustand vorliegt
     * @returns {boolean}
     */
    this.isSolved = function () {
        var s = true;
        for (let i = 0; i < this.rPieces.length; i++) {
            if (!this.rPieces[i].isSolved()) {
                s = false;
            }
        }
        this.pSolved = s;
        return pSolved;
    };

    /**
     * Speichert den Zustand in einem JSON-Objekt
     * @returns {JSON} Zustand
     */
    this.export = function () {
        var obj = {
            pJsonId: this.pJsonId,
            pType: this.pType,
            rPieces: [],
            pSolved: this.pSolved,
        };

        for (let i = 0; i < this.rPieces.length; i++) {
            obj.rPieces.push(this.rPieces[i].export());
        }

        return obj;
    };

    /**
     * Importiert den Zustand aus einem JSON-Objekt
     * @param {JSON} obj
     */
    this.import = function (obj) {
        if ((obj.pType = this.pType && obj.pJsonId == this.pJsonId)) {
            this.pSolved = obj.pSolved;
            for (let i = 0; i < obj.rPieces.length; i++) {
                this.rPieces[i].import(obj.rPieces[i]);
            }
        } else {
            console.log("ERROR importing Puzzle");
        }
    };
}
