// Functions to draw a HTML-based coordinate system

class CoordinateSystem {
    constructor(
        xCanvas, xMin, xMax,
        yCanvas, yMin, yMax,
        nTicks = 10,
        tickHeight = 25,
        tickLength = 25,
        x = 0,
        y = 0,
        noUpperHalf = true,
        noRightHalf = true
    ) {
        this.xCanvas = xCanvas;
        this.xCanvasContext2D = this.xCanvas.getContext("2d");
        this.xMin = xMin;
        this.xMax = xMax;
        this.tickHeight = tickHeight;
        this.y = y;
        this.noUpperHalf = noUpperHalf;

        this.yCanvas = yCanvas;
        this.yCanvasContext2D = this.yCanvas.getContext("2d");
        this.yMin = yMin;
        this.yMax = yMax;
        this.tickLength = tickLength;
        this.x = x;
        this.noRightHalf = noRightHalf;

        this.nTicks = nTicks;

        this.lineWidth = 1;
        this.font = "14px Arial";
    }

    draw(dim0, dim1, limits) {
        this.xMin = limits.min[dim0];
        this.xMax = limits.max[dim0];
        this.yMin = limits.min[dim1];
        this.yMax = limits.max[dim1];

        drawHorizontalAxis(this.xCanvas, this.xMin, this.xMax, this.y, this.tickHeight, this.nTicks, this.noUpperHalf, this.lineWidth, this.font);
        drawVerticalAxis(this.yCanvas, this.yMin, this.yMax, this.x, this.tickLength, this.nTicks, this.noRightHalf, this.lineWidth, this.font);
    }
}

function getFontSize(fontString) {
    return parseInt(fontString.split('px')[0])
}

function getTickLocationsAndLabels(minVal, maxVal, minTicks = 5, maxTicks = 12) {
    let d = maxVal - minVal;
    let delta = Math.pow(10, Math.floor(Math.log10(d)));

    // The ticks will surely be between lower and upper.
    // Afterwards, we will remove the ones outside the [minVal, maxVal] range.
    let lower = Math.floor(minVal / delta) * delta;
    let upper = Math.ceil(maxVal / delta) * delta;

    let computeTickLocations = function (delta) {
        let tickLocations = [];
        let tickLoc = lower;
        while (tickLoc <= upper) {
            tickLocations.push(tickLoc);
            tickLoc += delta;
        }
        // Remove tick locations outside [xMin, xMax]
        tickLocations = tickLocations.filter((v) => (v >= minVal) && (v <= maxVal))
        return tickLocations
    }

    // Create base tick locations
    let tickLocations = computeTickLocations(delta);


    // Adjust tickLocations until the number of locations is in [minTicks, maxTicks]
    let iterationCounter = 0;
    while (((tickLocations.length < minTicks) || (tickLocations.length > maxTicks)) && (iterationCounter < 10)) {
        if (tickLocations.length < minTicks) {
            // Add intermediate ticks. This actually means we halve delta and recompute new ticks.
            delta = delta / 2;
            tickLocations = computeTickLocations(delta);
        }
        if (tickLocations.length > maxTicks) {
            // Remove intermediate ticks. This actually means we double delta and recompute new ticks.
            delta = delta * 2;
            tickLocations = computeTickLocations(delta)
        }
        iterationCounter++;
    }

    return {
        loc: tickLocations.map((v) => (v - minVal) / (maxVal - minVal)),
        lab: tickLocations.map((v) => maxVal >= 1000 ? v.toExponential(1) : v)
    }
}

function getTickLabel(n, delta) {
    // Prepares the label for a tick value n
    if (delta === 0) {
        console.log("Got delta 0")
        return n.toString();
    }

    if (delta >= 10) {
        let significantDigits = Math.floor(Math.log10(n)) - Math.floor(Math.log10(delta)) + 1;
        return n.toPrecision(significantDigits).toString();
    } else {
        let significantDecimals = Math.ceil(Math.log10(1 / delta)) + 1;
        return n.toFixed(significantDecimals);
    }
}

function drawHorizontalAxis(canvas, xMin, xMax, y = 10, tickHeight = 25, nTicks = 10, noUpperHalf = true, lineWidth = 3, font = "14px Arial") {
    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;

    // Setup context
    let ctx = canvas.getContext("2d");
    ctx.lineWidth = lineWidth;
    ctx.font = font;
    ctx.textBaseline = 'top';
    // ctx.textAlign = "center";
    let fontSize = getFontSize(ctx.font);

    // Create the main line
    ctx.beginPath();
    ctx.moveTo(0, y);
    ctx.lineTo(canvas.width, y);
    ctx.stroke();

    let ticks = getTickLocationsAndLabels(xMin, xMax);
    for (let i = 0; i < ticks.loc.length; i++) {
        let tickX = ticks.loc[i] * canvas.width;
        ctx.beginPath();
        ctx.moveTo(tickX, noUpperHalf ? y : y - tickHeight / 2);
        ctx.lineTo(tickX, y + tickHeight / 2);
        ctx.stroke();
        ctx.fillText(ticks.lab[i], tickX, y + tickHeight)
    }

    // Create ticks
    // let delta = canvas.width / nTicks;
    // let glDelta = (xMax - xMin) / nTicks;
    // let tickIndex = 0;
    // while (tickIndex <= nTicks) {
    //     let tickX = tickIndex * delta;
    //
    //     ctx.beginPath();
    //     ctx.moveTo(tickX, noUpperHalf ? y : y - tickHeight / 2);
    //     ctx.lineTo(tickX, y + tickHeight / 2);
    //     ctx.stroke();
    //
    //     let xVal = xMin + tickIndex * glDelta;
    //     ctx.fillText(getTickLabel(xVal, glDelta), tickX, y + 2 * fontSize)
    //
    //     tickIndex += 1;
    // }
}

function drawVerticalAxis(canvas, yMin, yMax, x = 10, tickLength = 25, nTicks = 10, noRightHalf = true, lineWidth = 3, font = "14px Arial") {
    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;

    // Setup context
    let ctx = canvas.getContext("2d");
    ctx.lineWidth = lineWidth;
    ctx.font = font;
    let fontSize = getFontSize(ctx.font);
    // ctx.textBaseline = 'top';
    ctx.textAlign = "left";

    x = canvas.width - x;


    // Create the main line
    ctx.beginPath();
    ctx.moveTo(x, 0);
    ctx.lineTo(x, canvas.height);
    ctx.stroke();

    // Create ticks
    let ticks = getTickLocationsAndLabels(yMin, yMax);
    for (let i = 0; i < ticks.loc.length; i++) {
        let tickY = (1 - ticks.loc[i]) * canvas.height;  // TODO fix y positions
        ctx.beginPath();
        ctx.moveTo(x - tickLength / 2, tickY);
        ctx.lineTo(noRightHalf ? x : x + tickLength / 2, tickY);
        ctx.stroke();
        ctx.fillText(ticks.lab[i], x - tickLength - ctx.measureText(ticks.lab[i]).width, tickY)
    }

    // let delta = canvas.height / nTicks;
    // let glDelta = (yMax - yMin) / nTicks;
    // let tickIndex = 0;
    // while (tickIndex <= nTicks) {
    //     let tickY = tickIndex * delta;
    //
    //     ctx.beginPath();
    //     ctx.moveTo(x - tickLength / 2, tickY);
    //     ctx.lineTo(noRightHalf ? x : x + tickLength / 2, tickY);
    //     ctx.stroke();
    //
    //     let yVal = yMax - tickIndex * glDelta;
    //     ctx.fillText(getTickLabel(yVal, glDelta), x - tickLength / 2 - 2 * fontSize, tickY)
    //
    //     tickIndex += 1;
    // }
}