(function ($) {
    "use strict";

    var MIN_SIZE = 0.05;
    var DEFAULTS = { x: 0.25, y: 0.25, w: 0.5, h: 0.5 };

    function clamp(value, min, max) {
        return Math.max(min, Math.min(max, value));
    }

    function roundCoord(value) {
        return Math.round(value * 100) / 100;
    }

    function getField($editor, key) {
        var id = $editor.data("field-" + key);
        return id ? $("#" + id) : $();
    }

    function readArea($editor) {
        var x = parseFloat(String(getField($editor, "x").val() || "").replace(",", "."));
        var y = parseFloat(String(getField($editor, "y").val() || "").replace(",", "."));
        var w = parseFloat(String(getField($editor, "w").val() || "").replace(",", "."));
        var h = parseFloat(String(getField($editor, "h").val() || "").replace(",", "."));

        if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h) || w <= 0 || h <= 0) {
            return $.extend({}, DEFAULTS);
        }

        return {
            x: clamp(x, 0, 1),
            y: clamp(y, 0, 1),
            w: clamp(w, MIN_SIZE, 1),
            h: clamp(h, MIN_SIZE, 1),
        };
    }

    function writeArea($editor, area, skipRect) {
        getField($editor, "x").val(roundCoord(area.x));
        getField($editor, "y").val(roundCoord(area.y));
        getField($editor, "w").val(roundCoord(area.w));
        getField($editor, "h").val(roundCoord(area.h));

        if (!skipRect) {
            applyRect($editor, area);
        }
    }

    function applyRect($editor, area) {
        var $rect = $editor.find(".iapc-print-area-rect");
        $rect.css({
            left: area.x * 100 + "%",
            top: area.y * 100 + "%",
            width: area.w * 100 + "%",
            height: area.h * 100 + "%",
        });
    }

    function setImageVisible($editor, url) {
        var $stage = $editor.find(".iapc-print-area-stage");
        var $empty = $editor.find(".iapc-print-area-editor-empty");
        var $img = $editor.find(".iapc-print-area-image");

        if (url) {
            $img.attr("src", url);
            $empty.hide();
            $stage.show();
            $img.off("load.iapcPrintArea").on("load.iapcPrintArea", function () {
                applyRect($editor, readArea($editor));
            });
            if ($img[0] && $img[0].complete) {
                applyRect($editor, readArea($editor));
            }
        } else {
            $img.attr("src", "");
            $stage.hide();
            $empty.show();
        }
    }

    function bindEditor($editor) {
        if ($editor.data("iapcPrintAreaBound")) {
            return;
        }
        $editor.data("iapcPrintAreaBound", true);

        var imageField = $editor.data("image-field");
        var $imageInput = imageField ? $("#" + imageField) : $();
        var dragState = null;

        setImageVisible($editor, $imageInput.val() || $editor.find(".iapc-print-area-image").attr("src") || "");
        applyRect($editor, readArea($editor));

        $imageInput.on("change.iapcPrintArea input.iapcPrintArea", function () {
            setImageVisible($editor, $(this).val());
        });

        ["x", "y", "w", "h"].forEach(function (key) {
            getField($editor, key).on("change.iapcPrintArea input.iapcPrintArea", function () {
                applyRect($editor, readArea($editor));
            });
        });

        function onPointerMove(e) {
            if (!dragState) {
                return;
            }

            e.preventDefault();

            var stage = $editor.find(".iapc-print-area-stage")[0];
            if (!stage) {
                return;
            }

            var bounds = stage.getBoundingClientRect();
            if (!bounds.width || !bounds.height) {
                return;
            }

            var clientX = e.clientX;
            var clientY = e.clientY;
            if (e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0]) {
                clientX = e.originalEvent.touches[0].clientX;
                clientY = e.originalEvent.touches[0].clientY;
            }

            var nx = clamp((clientX - bounds.left) / bounds.width, 0, 1);
            var ny = clamp((clientY - bounds.top) / bounds.height, 0, 1);
            var start = dragState.startArea;
            var area = {
                x: start.x,
                y: start.y,
                w: start.w,
                h: start.h,
            };

            if (dragState.mode === "move") {
                area.x = clamp(start.x + (nx - dragState.originX), 0, 1 - start.w);
                area.y = clamp(start.y + (ny - dragState.originY), 0, 1 - start.h);
            } else {
                var handle = dragState.handle;
                var right = start.x + start.w;
                var bottom = start.y + start.h;

                if (handle.indexOf("w") !== -1) {
                    area.x = clamp(nx, 0, right - MIN_SIZE);
                    area.w = right - area.x;
                }
                if (handle.indexOf("e") !== -1) {
                    area.w = clamp(nx - start.x, MIN_SIZE, 1 - start.x);
                }
                if (handle.indexOf("n") !== -1) {
                    area.y = clamp(ny, 0, bottom - MIN_SIZE);
                    area.h = bottom - area.y;
                }
                if (handle.indexOf("s") !== -1) {
                    area.h = clamp(ny - start.y, MIN_SIZE, 1 - start.y);
                }
            }

            writeArea($editor, area);
        }

        function onPointerUp() {
            if (!dragState) {
                return;
            }
            dragState = null;
            $(document).off(".iapcPrintAreaDrag");
        }

        $editor.on("mousedown.iapcPrintArea touchstart.iapcPrintArea", ".iapc-print-area-rect", function (e) {
            if ($(e.target).hasClass("iapc-print-area-handle")) {
                return;
            }

            var stage = $editor.find(".iapc-print-area-stage")[0];
            if (!stage) {
                return;
            }

            var bounds = stage.getBoundingClientRect();
            var clientX = e.clientX;
            var clientY = e.clientY;
            if (e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0]) {
                clientX = e.originalEvent.touches[0].clientX;
                clientY = e.originalEvent.touches[0].clientY;
            }

            dragState = {
                mode: "move",
                startArea: readArea($editor),
                originX: clamp((clientX - bounds.left) / bounds.width, 0, 1),
                originY: clamp((clientY - bounds.top) / bounds.height, 0, 1),
            };

            $(document).on("mousemove.iapcPrintAreaDrag touchmove.iapcPrintAreaDrag", onPointerMove);
            $(document).on("mouseup.iapcPrintAreaDrag touchend.iapcPrintAreaDrag touchcancel.iapcPrintAreaDrag", onPointerUp);
            e.preventDefault();
        });

        $editor.on("mousedown.iapcPrintArea touchstart.iapcPrintArea", ".iapc-print-area-handle", function (e) {
            var stage = $editor.find(".iapc-print-area-stage")[0];
            if (!stage) {
                return;
            }

            var bounds = stage.getBoundingClientRect();
            var clientX = e.clientX;
            var clientY = e.clientY;
            if (e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0]) {
                clientX = e.originalEvent.touches[0].clientX;
                clientY = e.originalEvent.touches[0].clientY;
            }

            dragState = {
                mode: "resize",
                handle: $(this).data("handle"),
                startArea: readArea($editor),
                originX: clamp((clientX - bounds.left) / bounds.width, 0, 1),
                originY: clamp((clientY - bounds.top) / bounds.height, 0, 1),
            };

            $(document).on("mousemove.iapcPrintAreaDrag touchmove.iapcPrintAreaDrag", onPointerMove);
            $(document).on("mouseup.iapcPrintAreaDrag touchend.iapcPrintAreaDrag touchcancel.iapcPrintAreaDrag", onPointerUp);
            e.preventDefault();
            e.stopPropagation();
        });

        $(window).on("resize.iapcPrintArea", function () {
            applyRect($editor, readArea($editor));
        });
    }

    function initAll() {
        $(".iapc-print-area-editor").each(function () {
            bindEditor($(this));
        });
    }

    window.iapcPrintAreaEditor = {
        init: initAll,
        refreshFromImageField: function (fieldId, url) {
            $('.iapc-print-area-editor[data-image-field="' + fieldId + '"]').each(function () {
                var $editor = $(this);
                bindEditor($editor);
                setImageVisible($editor, url || "");
                if (url) {
                    applyRect($editor, readArea($editor));
                }
            });
        },
    };

    $(initAll);
})(jQuery);
