let currentPoints = {};

function desiredStep(x) {
    // 0.1, 1, 10, 100, etc.
    const nearestExpOf10 = Math.pow(10, Math.floor(Math.log10(x)));
    const selection = [nearestExpOf10, nearestExpOf10 * 2, nearestExpOf10 * 5, nearestExpOf10 * 10]

    const deltas = selection.map(s => Math.abs(x - s));
    const closest = Math.min(...deltas);
    const index = deltas.indexOf(closest);

    return selection[index];
}

function parsePoint(point) {
    let [x, y] = point.split(",");
    return { x: parseFloat(x), y: parseFloat(y) };
}

const ticks = (start, end, step, f) => {
    let firstTick = Math.round(start / step) * step; // round towards step
    for (let value = firstTick; value < end; value += step) {
        value = parseFloat(value.toFixed(6));
        f(value);
    }
};

for (const canvas of document.getElementsByTagName("canvas")) {
    if (canvas.dataset.vec2d === undefined) continue;
    const defaultValue = parsePoint(canvas.dataset.vec2dDefault);

    let min = parsePoint(canvas.dataset.vec2dMin);
    let max = parsePoint(canvas.dataset.vec2dMax);

    const width = () => max.x - min.x;
    const height = () => max.y - min.x;

    currentPoints[canvas.id] = defaultValue;

    let ctx = canvas.getContext("2d");
    ctx.font = "0.8em sans";
    const line = (from, to) => {
        ctx.beginPath();
        ctx.moveTo(from[0], from[1]);
        ctx.lineTo(to[0], to[1]);
        ctx.stroke();
    }
    const text = (text, x, y) => {
        ctx.fillText(text, x, y);
    }
    const circle = (x, y, radius) => {
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2, false);
        ctx.fill();
    }

    const canvasWidth = canvas.width;
    const canvasHeight = canvas.height;

    const canvasScaleX = () => canvasWidth / width();
    const canvasScaleY = () => canvasHeight / height();

    const left = 0, right = canvasWidth, top = 0, bottom = canvasHeight;

    let isDragging = false;
    let currentDrag = { x: 0, y: 0 };
    let dragStart = { x: 0, y: 0 };

    // from [min.x, max.x] to [0, canvasWidth]
    const canvasX = x => ((x - currentDrag.x) - min.x) / (max.x - min.x) * canvasWidth;
    // from [min.y, max.y] to [canvasHeight, 0]
    const canvasY = y => ((y - currentDrag.y) - max.y) / (min.y - max.y) * canvasHeight;
    // [0, canvasWidth] to [min.x, max.x]
    const positionToCanvasX = x => (x / canvasWidth * (max.x - min.x) + min.x) + currentDrag.x;
    // [canvasHeight, 0] to [min.y, max.y
    const positionToCanvasY = y => (y / canvasHeight * (min.y - max.y) + max.y) + currentDrag.y;

    const positionFromCanvasEvent = (canvas, e, withDragOffset = true) => {
        const rect = canvas.getBoundingClientRect();
        const scaleX = canvas.width / rect.width;
        const scaleY = canvas.height / rect.height;

        let x = positionToCanvasX((e.clientX - rect.left) * scaleX);
        let y = positionToCanvasY((e.clientY - rect.top) * scaleY);

        if (!withDragOffset) {
            x -= currentDrag.x;
            y -= currentDrag.y;
        }

        return { x, y }
    }

    const drawCoordinateSystem = () => {
        ctx.clearRect(0, 0, canvasWidth, canvasHeight);

        ctx.lineWidth = 2;
        line([left, canvasY(0)], [right, canvasY(0)]);
        line([canvasX(0), top], [canvasX(0), bottom]);

        const tickStepX = desiredStep(width() / 8);
        const tickStepY = desiredStep(height() / 8);

        ctx.strokeStyle = "#888";
        ctx.lineWidth = 0.5;
        // vertical grid lines
        ticks(positionToCanvasX(left), positionToCanvasX(right), tickStepX, (x) => {
            const cx = canvasX(x);
            line([cx, top], [cx, bottom]);
        });
        // horizontal grid lines
        ticks(positionToCanvasY(bottom), positionToCanvasY(top), tickStepY, (y) => {
            const cy = canvasY(y);
            line([left, cy], [right, cy]);
        });

        ctx.strokeStyle = "#000";
        ctx.lineWidth = 1;

        ctx.textAlign = "center";
        ctx.textBaseline = "alphabetic";

        // x-axis ticks
        ticks(positionToCanvasX(left), positionToCanvasX(right), tickStepX, (x) => {
            let extentY = (tickStepY / 4) * canvasScaleY();
            let cx = canvasX(x);
            let cy0 = canvasY(0);
            line([cx, cy0 - extentY], [cx, cy0 + extentY]);

            if (x !== 0) text(x.toString(), canvasX(x), canvasY(0) - extentY * 1.5);
        });

        ctx.textAlign = "right";
        ctx.textBaseline = "middle";
        // y-axis ticks
        ticks(positionToCanvasY(bottom), positionToCanvasY(top), tickStepY, (y) => {
            let extentX = (tickStepX / 4) * canvasScaleX();
            let cy = canvasY(y);
            let cx0 = canvasX(0);
            line([cx0 - extentX, cy], [cx0 + extentX, cy]);

            if (y !== 0) text(y.toString(), canvasX(0) - extentX * 1.5, canvasY(y));
        });

        const mouse = currentPoints[canvas.id];
        circle(canvasX(mouse.x), canvasY(mouse.y), 5);
    }

    drawCoordinateSystem();

    const beginDrag = (e) => {
        const position = positionFromCanvasEvent(canvas, e, false);
        isDragging = true;
        dragStart = position;
    }

    const endDrag = () => {
        isDragging = false;
        dragStart = { x: 0, y: 0 };
    }

    const handleDrag = (e) => {
        const mousePoint = positionFromCanvasEvent(canvas, e, false);
        let diff = { x: dragStart.x - mousePoint.x, y: dragStart.y - mousePoint.y }
        currentDrag.x += diff.x;
        currentDrag.y += diff.y;
        dragStart = mousePoint;

        drawCoordinateSystem();
    };

    const handlePointSelection = (e) => {
        const position = positionFromCanvasEvent(canvas, e);

        currentPoints[e.target.id] = position;
        drawCoordinateSystem();
        canvas.dispatchEvent(new CustomEvent("vec2d-data", { detail: position }));
    };

    const zoom = (e) => {
        e.preventDefault();

        const factor = 1 + e.deltaY * 0.05;
        const { x, y } = positionFromCanvasEvent(canvas, e);

        // the percentage of the point from start to end stays the same (I)
        // and sizeBefore * factor = sizeAfter (II);
        // Applies for both x and y;

        // WHY CANNOT WOLFRAM ALPHA DO MULTICHARATER VARIABLES AAAHRRG
        // minBeforeX = a, maxBeforeX = b
        // minAfterX = g, maxAfterX = h
        // minBeforeY = c, maxBeforeY = d
        // minAfterY = i, maxAfterY = j

        // I(x,y):  (x-minBefore) / (maxBefore-minBefore) = (x-minAfter) / (maxAfter-minAfter)
        // II(x,y): (maxBefore-minBefore)*factor = (maxAfter-minAfter)
        // III: (widthAfter / widthBefore) = (heightAfter / heightBefore) = ((maxAfterX-maxBeforeX)/(maxBeforeX-minBeforeX)) = ((maxAfterY-maxBeforeY)/(maxBeforeY-minBeforeY))
        // solve(
        // (x-a)/(b-a)=(x-g)/(h-g),
        // (b-a)*f=(h-g),
        // (y-c)/(d-c)=(y-i)/(j-i),
        // (d-c)*f=(j-i),
        // (h-g)/(b-a)=(j-i)/(d-c) # III, x scales same as y
        // g, h, i, j)
        //
        // g = a f - f x + x and h = b f - f x + x
        // i = c f - f y + y and i = d f - f y + y

        const oldW = width();
        const oldH = height();

        min.x = min.x * factor - factor * x + x;
        max.x = max.x * factor - factor * x + x;

        min.y = min.y * factor - factor * y + y;
        max.y = max.y * factor - factor * y + y;

        const newW = width();
        const newH = height();

        console.log(newW / oldW, newH / oldH); // WHY ISN'T THIS EQUAL MATH SAYS IT IS EQUAL

        drawCoordinateSystem();
    };

    canvas.addEventListener("mousemove", (e) => {
        if (isDragging) {
            handleDrag(e);
        } else if (e.buttons === 1) {
            handlePointSelection(e);
        }
    });
    canvas.addEventListener("mousedown", (e) => {
        if (e.shiftKey) beginDrag(e);
        else handlePointSelection(e);
    });

    canvas.addEventListener("mouseup", endDrag);
    canvas.addEventListener("mouseout", endDrag);

    // canvas.addEventListener("wheel", zoom);
}
