// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import "/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js";
import "/node_modules/@perspective-dev/viewer-datagrid/dist/cdn/perspective-viewer-datagrid.js";
import "/node_modules/@perspective-dev/viewer-charts/dist/cdn/perspective-viewer-charts.js";

import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";

function generate_mandelbrot(params) {
    return `
// color
var resolution := ${params.resolution};
var xmin := ${params.xmin};
var xmax := ${params.xmax};
var ymin := ${params.ymin};
var ymax := ${params.ymax};
var iterations := ${params.iterations};

var x := floor("index" / resolution);
var y := "index" % resolution;
var c := iterations;

var cx := xmin + ((xmax - xmin) * x) / (resolution - 1);
var cy := ymin + ((ymax - ymin) * y) / (resolution - 1);

var vx := 0;
var vy := 0;
var vxx := 0;
var vyy := 0;
var vxy := 0;

for (var ii := 0; ii < iterations; ii += 1) {
    if (vxx + vyy <= float(4)) {
        vxy := vx * vy;
        vxx := vx * vx;
        vyy := vy * vy;
        vx := vxx - vyy + cx;
        vy := vxy + vxy + cy;
        c -= 1;
    }
};

c`;
}

function generate_layout(params) {
    return {
        plugin: "Heatmap",
        table: "raw_data",
        settings: true,
        group_by: [`floor("index" / ${params.resolution})`],
        split_by: [`"index" % ${params.resolution}`],
        columns: ["color"],
        expressions: {
            color: generate_mandelbrot(params).trim(),
            [`floor("index" / ${params.resolution})`]: `floor("index" / ${params.resolution})`,
            [`"index" % ${params.resolution}`]: `"index" % ${params.resolution}`,
        },
    };
}

async function generate_data(table) {
    const run = document.getElementById("run");
    let json = new Array(Math.pow(resolution, 2));
    for (let x = 0; x < resolution; ++x) {
        for (let y = 0; y < resolution; ++y) {
            const index = x * resolution + y;
            json[index] = {
                index,
            };
        }
    }

    await table.replace(json);
    run.innerHTML = `Run`;
}

// GUI

function get_gui_params() {
    return ["xmin", "xmax", "ymin", "ymax", "resolution", "iterations"].reduce(
        (acc, x) => {
            acc[x] = window[x].valueAsNumber;
            return acc;
        },
        {},
    );
}

function make_range(x, y, name) {
    x.addEventListener("input", () => {
        window.run.disabled = false;
        x.value = Math.min(x.valueAsNumber, y.valueAsNumber - 0.1);
    });

    y.addEventListener("input", () => {
        window.run.disabled = false;
        y.value = Math.max(x.valueAsNumber + 0.1, y.valueAsNumber);
    });
}

const make_run_click_callback = (worker, state) => async () => {
    if (window.run.innerHTML.trim() !== "Run") {
        window.run.innerHTML = "Run";
        return;
    }

    window.run.disabled = true;
    if (!state.table) {
        state.table = await worker.table(
            {
                index: "integer",
            },
            {
                name: "raw_data",
            },
        );

        window.viewer.load(worker);
    }

    const run = document.getElementById("run");
    const params = get_gui_params();
    const new_size = Math.pow(params.resolution, 2);
    if (!state.size || state.size !== new_size) {
        let json = { index: new Array(new_size) };
        for (let x = 0; x < new_size; ++x) {
            json.index[x] = x;
        }

        state.table.replace(json);
    }

    state.size = new_size;
    run.innerHTML = `Run`;
    window.viewer.restore(generate_layout(params));
};

function set_runnable() {
    window.run.disabled = false;
}

make_range(xmin, xmax, "X");
make_range(ymin, ymax, "Y");
window.resolution.addEventListener("input", set_runnable);
window.iterations.addEventListener("input", set_runnable);

run.addEventListener(
    "click",
    make_run_click_callback(await perspective.worker(), {}),
);

run.dispatchEvent(new Event("click"));
