<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Flow dashboard</title>

    <!-- Bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

    <!-- Page styling -->
    <link rel="stylesheet" type="text/css" href="{{ url_for('static',filename='css/style.css') }}">

    <!-- THREE.js only (local) -->
    <script src="{{ url_for('static',filename='js/three.js') }}"></script>

    <!-- Drawing the coordinate system in HTML -->
    <script src="{{ url_for('static', filename='js/coordinateSystemHTML.js') }}"></script>

    <!-- Custom animation code -->
    <script src="{{ url_for('static', filename='js/animations.js') }}"></script>

    <!-- Custom flow utils -->
    <script src="{{ url_for('static', filename='js/flow_utils.js') }}"></script>
</head>
<body>
<div class="container-fluid">
    <div class="jumbotron text-center">
        <h1>Normalizing flow dashboard</h1>
    </div>
    <hr>
    <div class="container-fluid">
        <div class="row">
            <div class="col-sm-8">
                <h3>Sample paths animation</h3>
                <p>
                    The animation shows how a normalizing flow transforms data points (orange) to latent space
                    (green).
                    The green ellipses correspond to the base diagonal standard normal distribution (sigma = 1 and sigma
                    = 2).
                </p>

                <div id="canvasContainer" style="height: 70vh">
                    <canvas id="topLeftCanvas" style="background:transparent; width: 10%; height: 90%"></canvas>
                    <canvas id="glCanvas" style="background:transparent; width: 90%; height: 90%"></canvas>
                    <canvas id="bottomLeftCanvas" style="background:transparent; width: 10%; height: 10%"></canvas>
                    <canvas id="bottomRightCanvas" style="background:transparent; width: 90%; height: 10%"></canvas>
                </div>

                <!-- Animation controls (play/pause/stop) -->
                <div class="container-fluid card card-body mb-1">  <!--  style="float:right; width: 90%" -->
                    <h6>Training step <span id="TrainingStepSpan"></span></h6>
                    <div style="display: flex; justify-content: space-between" class="mb-3">
                        <span style="padding-right: 1%">1</span>
                        <input type="range" class="form-range" id="stepRange" min="0" max="1" step="1" value="0"
                               disabled>
                        <span id="TrainingMaxStep" style="padding-left: 1%"></span>
                    </div>
                    <div class="btn-group">
                        <button type="button" class="btn btn-default btn-outline-secondary" id="buttonPlay">
                            Play
                        </button>
                        <button type="button" class="btn btn-default btn-outline-secondary" id="buttonPause">
                            Pause
                        </button>
                        <button type="button" class="btn btn-default btn-outline-secondary" id="buttonStop">
                            Stop
                        </button>
                    </div>
                </div>

            </div>
            <div class="container col-sm-4">

                <div>
                    <form action="{{ url_for('reload') }}" method="POST">
                        <h3>Experiments
                            <button
                                    type="submit"
                                    class="btn btn-default btn-outline-secondary"
                                    id="buttonReload"
                                    style="float: right">Reload
                            </button>
                        </h3>
                    </form>
                </div>


                <div class="container-fluid card card-body mb-1">
                    <h6>Experiment selection</h6>
                    <form action="{{ url_for('home') }}" method="POST">

                        <div class="overflow-auto"
                             style="max-height: 30vh; display:flex; flex-direction:column-reverse;">
                            <!-- The last twoo style elements are so we start with the scrollbar at the bottom when loading up the page -->
                            <!-- https://stackoverflow.com/questions/18614301/keep-overflow-div-scrolled-to-bottom-unless-user-scrolls-up -->
                            {% for experiment_path in experiment_paths %}
                            <div class="form-check">
                                <input
                                        class="form-check-input"
                                        type="radio"
                                        name="experimentRadio"
                                        value="{{ loop.index }}"
                                        onchange="this.form.submit();"
                                        id="experimentRadio{{ loop.index }}" {% if loop.index==
                                        meta_info.default_experiment_id_flask %} checked {% endif %}>
                                <label class="form-check-label" for="experimentRadio{{ loop.index }}">
                                    {{ experiment_path }}
                                </label>
                            </div>
                            {% endfor %}
                        </div>
                    </form>
                </div>

                <div class="container-fluid card card-body">
                    <h6>Experiment information</h6>
                    <ul>
                        <li>Number of dimensions: {{ experiment_info.nDimensions | safe }}</li>
                        <li>Number of training steps: {{ experiment_info.nSteps | safe }}</li>
                        <li>Number of training samples: {{ experiment_info.nTrainingSamples | safe }}</li>
                        <li>Number of validation samples: {{ experiment_info.nValidationSamples | safe }}</li>
                        <li>Number of flow layers: {{ experiment_info.nLayers | safe }}</li>
                    </ul>
                </div>

                <hr>

                <h3>Settings and controls</h3>
                <div id="settings" class="form">  <!-- We probably don't need the form class here -->
                    <!-- Dim0 selector -->
                    <div class="input-group mb-1">
                        <div class="input-group-prepend">
                            <span class="input-group-text">First dimension (x)</span>
                        </div>
                        <input type="number" class="form-control text-end" id="dim0Selector" min="0" max="1"
                               value="0">
                    </div>

                    <!-- Dim1 selector -->
                    <div class="input-group mb-1">
                        <div class="input-group-prepend">
                            <span class="input-group-text">Second dimension (y)</span>
                        </div>
                        <input type="number" class="form-control text-end" id="dim1Selector" min="0" max="1"
                               value="1">
                    </div>

                    <!-- Camera selector -->
                    <div class="container-fluid card card-body mb-1">
                        <h6>Camera selection</h6>
                        <div class="form-check" data-toggle="tooltip" data-placement="top"
                             title="Use a fixed camera that uses data limits across the whole training process.">
                            <input class="form-check-input" type="radio" name="cameraRadio" id="cameraRadio1" checked>
                            <label class="form-check-label" for="cameraRadio1">
                                Static
                            </label>
                        </div>
                        <div class="form-check" data-toggle="tooltip" data-placement="top"
                             title="Use a dynamic camera that snaps to data limits at each training step.">
                            <input class="form-check-input" type="radio" name="cameraRadio" id="cameraRadio2">
                            <label class="form-check-label" for="cameraRadio2">
                                Dynamic
                            </label>
                        </div>
                    </div>

                    <!-- FPS selector -->
                    <div class="container-fluid card card-body mb-1">
                        <h6>FPS (animation speed)</h6>
                        <div style="display: flex; justify-content: space-between">
                            <span style="padding-right: 1%">0</span>
                            <input type="range" class="form-range" id="fpsRange" min="1" max="300" step="1" value="300">
                            <span style="padding-left: 1%">300</span>
                        </div>
                    </div>

                    <!-- Choose between training and validation paths -->
                    <div class="container-fluid card card-body mb-1">
                        <h6>Dataset selection</h6>
                        <div class="form-check" data-toggle="tooltip" data-placement="top"
                             title="Show training sample paths from data space to latent space.">
                            <input class="form-check-input" type="radio" name="datasetRadio" id="datasetRadio1" checked>
                            <label class="form-check-label" for="datasetRadio1">
                                Training
                            </label>
                        </div>
                        <div class="form-check" data-toggle="tooltip" data-placement="top"
                             title="Show validation sample paths from data space to latent space.">
                            <input class="form-check-input" type="radio" name="datasetRadio" id="datasetRadio2">
                            <label class="form-check-label" for="datasetRadio2">
                                Validation
                            </label>
                        </div>
                        <div class="form-check" data-toggle="tooltip" data-placement="top"
                             title="Show sample paths from latent space to data space. Samples are taken from the base diagonal standard normal distribution.">
                            <input class="form-check-input" type="radio" name="datasetRadio" id="datasetRadio3">
                            <label class="form-check-label" for="datasetRadio3">
                                Generative
                            </label>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script>
        // Create the renderer
        const renderer = new THREE.WebGLRenderer({canvas: document.getElementById("glCanvas")});

        // Rendering parameters
        let limitScaling = 1.1;
        let particleSize = 25;

        // Coordinate system parameters
        let nTicks = 10;
        let tickHeight = 25;
        let tickWidth = 25;

        // Things that stay constant during initialization
        let dim0 = parseInt(document.getElementById('dim0Selector').value);
        let dim1 = parseInt(document.getElementById('dim1Selector').value);

        // Sprites
        const particleSprite = new THREE.TextureLoader().load('{{ url_for("static", filename="textures/sprites/disc.png") }}');

        // Meta info and experiment info
        let experimentID = parseInt("{{ meta_info.experiment_id | safe }}");
        let nSteps = parseInt("{{ experiment_info.nSteps | safe }}")
        let nDimensions = parseInt("{{ experiment_info.nDimensions | safe }}")
        let nTrainingSamples = parseInt("{{ experiment_info.nTrainingSamples | safe }}")
        let nValidationSamples = parseInt("{{ experiment_info.nValidationSamples | safe }}")
        let nLayers = parseInt("{{ experiment_info.nLayers | safe }}")

        // Process data
        let data = {{data | safe}}

        let datasetManager = new FlowDatasetManager(data);

        if (datasetManager.validationDatasets.length === 0) {
            document.getElementById("datasetRadio2").disabled = true;
        }
        if (datasetManager.generativeDatasets.length === 0) {
            document.getElementById("datasetRadio3").disabled = true;
        }

        document.getElementById("datasetRadio1").onclick = function () {
            datasetManager.setTrainActive();
            updateDimensions();
        }
        document.getElementById("datasetRadio2").onclick = function () {
            datasetManager.setValidationActive();
            updateDimensions();
        }
        document.getElementById("datasetRadio3").onclick = function () {
            datasetManager.setGenerativeActive();
            updateDimensions();
        }


        document.getElementById('stepRange').setAttribute('max', nSteps.toString());
        document.getElementById("dim0Selector").setAttribute("max", (nDimensions - 1).toString());
        document.getElementById("dim1Selector").setAttribute("max", (nDimensions - 1).toString());
        document.getElementById("TrainingMaxStep").textContent = nSteps.toString();

        // Additional objects to be rendered
        let ellipse1 = new CovarianceEllipse(1)
        let ellipse2 = new CovarianceEllipse(2)

        // Set up animation controls
        class AnimationControls {
            constructor(nSteps) {
                this.state = 0;  // 0 - paused, 1 - playing, 2 - stopped
                this.previousStep = -1;
                this.activeStep = 0;
                this.nSteps = nSteps;
            }

            stepChanged() {
                return this.previousStep !== this.activeStep;
            }

            setStep(step) {
                this.previousStep = this.activeStep;
                this.activeStep = step;

                // Out of bounds check
                if (this.activeStep < 0) {
                    this.activeStep = 0;
                }
                if (this.activeStep >= nSteps) {
                    this.activeStep = nSteps - 1;
                }
                document.getElementById("stepRange").value = this.activeStep.toString();
            }

            incrementStep() {
                this.setStep((this.activeStep + 1) % this.nSteps);
            }

            play() {
                this.state = 1;
                document.getElementById("stepRange").disabled = true;
            }

            pause() {
                this.state = 0;
                this.setStep(this.activeStep);  // Sets the previous step to the active step too => step has not changed
                document.getElementById("stepRange").disabled = false;
            }

            stop() {
                this.setStep(0);
                this.pause();
                this.state = 2;
            }

            isPlaying() {
                return this.state === 1;
            }

            isPaused() {
                return this.state === 0;
            }

            isStopped() {
                return this.state === 2;
            }
        }

        let animationControls = new AnimationControls(nSteps);
        document.getElementById("buttonPlay").onclick = function () {
            animationControls.play();
        };
        document.getElementById("buttonPause").onclick = function () {
            animationControls.pause();
        };
        document.getElementById("buttonStop").onclick = function () {
            animationControls.stop();
        };
        document.getElementById("stepRange").oninput = function (e) {
            animationControls.setStep(parseInt(e.target.value));
        };
        animationControls.play();


        // Set up the coordinate system
        let coordinateSystem = new CoordinateSystem(
            document.getElementById("bottomRightCanvas"),
            datasetManager.limits().min[dim0], datasetManager.limits().max[dim0],
            document.getElementById("topLeftCanvas"),
            datasetManager.limits().min[dim1], datasetManager.limits().max[dim1],
            nTicks, tickHeight, tickWidth
        );
        coordinateSystem.draw(dim0, dim1, datasetManager.limits());

        // Redraw the coordinate system each time the window is resized
        document.body.onresize = function () {
            coordinateSystem.draw(dim0, dim1, datasetManager.limits(
                document.getElementById("cameraRadio1").checked ? null : animationControls.activeStep
            ));
        };

        // We are displaying 2D data with third component equal to 0, so having [-1, 1] as z limits is fine.
        const fixedCamera = new THREE.OrthographicCamera(-100, 100, -100, 100, -1, 1);
        let cameraController = new CameraController(fixedCamera, null)
        cameraController.setFixedCameraParameters(dim0, dim1, datasetManager.limits(), limitScaling);

        function updateCameraAndCoordinateSystem() {
            cameraController.setFixedCameraParameters(dim0, dim1, datasetManager.limits(
                document.getElementById("cameraRadio1").checked ? null : animationControls.activeStep
            ), limitScaling);

            // Redraw the coordinate system
            coordinateSystem.draw(dim0, dim1, datasetManager.limits(
                document.getElementById("cameraRadio1").checked ? null : animationControls.activeStep
            ));
        }

        function updateDimensions() {
            dim0 = parseInt(document.getElementById('dim0Selector').value);
            dim1 = parseInt(document.getElementById('dim1Selector').value);

            for (let i = 0; i < datasetManager.datasets().length; i++) {
                datasetManager.datasets()[i].setDimensions(dim0, dim1)
            }
            updateCameraAndCoordinateSystem()
        }

        document.getElementById('dim0Selector').addEventListener('input', updateDimensions)
        document.getElementById('dim1Selector').addEventListener('input', updateDimensions)

        // Setting up the scene
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0xffffff);

        // Add objects to the scene
        for (let i = 0; i < datasetManager.trainDatasets.length; i++) {
            scene.add(datasetManager.trainDatasets[i]);
        }
        for (let i = 0; i < datasetManager.validationDatasets.length; i++) {
            scene.add(datasetManager.validationDatasets[i]);
        }
        for (let i = 0; i < datasetManager.generativeDatasets.length; i++) {
            scene.add(datasetManager.generativeDatasets[i]);
        }
        scene.add(ellipse1)
        scene.add(ellipse2)

        function resizeCanvasToDisplaySize(camera) {
            const canvas = renderer.domElement;
            // look up the size the canvas is being displayed
            const width = canvas.clientWidth;
            const height = canvas.clientHeight;

            // adjust displayBuffer size to match
            if (canvas.width !== width || canvas.height !== height) {
                // you must pass false here or three.js sadly fights the browser
                renderer.setSize(width, height, false);
                camera.aspect = width / height;
                camera.updateProjectionMatrix();

                // update any render target sizes here
            }
        }

        // FPS limiter variables
        let previousDelta = 0;
        let fpsTolerance = 0.001;

        function animate(currentDelta) {
            requestAnimationFrame(animate);

            resizeCanvasToDisplaySize(cameraController.getActiveCamera());

            // FPS lock related
            let delta = currentDelta - previousDelta;

            // FPS lock related
            let fpsLimit = parseInt(document.getElementById("fpsRange").value);
            if (fpsLimit && delta < (1000 / fpsLimit) - fpsTolerance) {
                return;
            }

            document.getElementById("TrainingStepSpan").textContent = animationControls.activeStep.toString();

            if (!animationControls.isPlaying()) {
                // Either stopped or paused
                if (animationControls.isPaused() && !animationControls.stepChanged()) {
                    return;
                } else if (animationControls.isStopped()) {
                    animationControls.pause();  // Transition to paused state
                }
            } else {
                animationControls.incrementStep();
            }

            // Update camera and coordinate system
            updateCameraAndCoordinateSystem()

            for (let i = 0; i < datasetManager.trainDatasets.length; i++) {
                datasetManager.trainDatasets[i].visible = (i === animationControls.activeStep) && datasetManager.isTrainActive();
            }
            for (let i = 0; i < datasetManager.validationDatasets.length; i++) {
                datasetManager.validationDatasets[i].visible = (i === animationControls.activeStep) && datasetManager.isValidationActive();
            }
            for (let i = 0; i < datasetManager.generativeDatasets.length; i++) {
                datasetManager.generativeDatasets[i].visible = (i === animationControls.activeStep) && datasetManager.isGenerativeActive();
            }
            renderer.render(scene, cameraController.getActiveCamera());

            previousDelta = currentDelta;  // FPS lock related
        }

        animate();
    </script>
</div>
</body>
</html>