<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Board monitor</title>
        <link rel="stylesheet" type="text/css" href="{{url_for('static', filename='pure-min.css')}}">
        <!--link rel="icon" type="image/png" href="{{url_for('static', filename='favicon-512.png')}}"-->
        <!--link rel="apple-touch-icon" href="{{url_for('static', filename='favicon-512.png')}}"-->
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="{{url_for('static', filename='plotly-2.35.2.min.js')}}" charset="utf-8"></script>
        <script src="{{url_for('static', filename='socket.io.min.js')}}" charset="utf-8"></script>
<style>
#plot-grid {
    display: grid;
    width: 100%;
    grid-template-columns: repeat(2, 1fr);
    /* grid-template-rows: repeat(6, 600px); */
}
</style>
    </head>
    <body>
        <h4>Device settings</h4>
        <div>
            <label for="sampling_phase_inp">
                Sampling phase <input type="number" id="sampling_phase_inp" min="0" max="180" step="1" value="6" data-dev-setting="sampling_phase_var"/>
            </label>
            <label for="sampling_width_inp">
                Sampling pulse width <input type="number" id="sampling_width_inp" min="2" max="180" step="1" value="12" data-dev-setting="sampling_width"/>
            </label>
            <label for="stim_offset_inp">
                Stimulus pulse offset <input type="number" id="stim_offset_inp" min="0" max="180" step="1" value="104" data-dev-setting="stimulus_offset"/>
            </label>
            <label for="oversampling_inp">
                Oversampling <input type="number" id="oversampling_inp" min="1" max="511" step="1" value="256" data-dev-setting="oversampling"/>
            </label>
            <label for="wait_for_trigger_inp">
                <input type="checkbox" id="wait_for_trigger_inp" data-dev-setting="wait_for_trigger"/>
                Trigger mode 
            </label>
        </div>

        <h4>Download data</h4>
        <div class="download">
            <form id="download_form">
                <div>
                    <label for="dl_meta_info">Meta info: <input type="text" id="dl_meta_info"/></label>
                    <label for="dl_meta_specimen">Specimen: <input type="text" id="dl_meta_specimen"/></label>
                    <label for="dl_meta_mcu">MCU Serial: <input type="text" id="dl_meta_mcu" readonly/></label>
                    <label for="dl_meta_chip">Driver IC: <input type="text" id="dl_meta_chip" readonly/></label>
                    <button type="submit" disabled style="display: none" aria-hidden="true"></button>
                    <input type="submit" value="Download"/>
                </div>
            </form>
        </div>

        <div id="plot-grid">
            <adc-plot adc-index="1" playbook-item="open_fwd,load_fwd,short_fwd,cal"></adc-plot>
            <adc-plot adc-index="1" playbook-item="open_flip,load_flip,short_flip,cal"></adc-plot>

            <adc-plot adc-index="2" playbook-item="open_fwd,load_fwd,short_fwd,cal"></adc-plot>
            <adc-plot adc-index="2" playbook-item="open_flip,load_flip,short_flip,cal"></adc-plot>
        </div>

        <template id="adc_plot_template">
            <h4 id="heading"></h4>
            <div id="plot"></div>
        </template>
        <template id="measurement_template">
            <div class="measurement" id="measurement">
                <h5>Threshold</h5>

                <label for="thr_v_spin">
                    Voltage <input type="number" id="thr_v_spin" class="v_spin" min="-0.3" max="3.6" step="0.01"/> V
                </label>

                <label for="thr_t_spin">
                    Anchor time: <input type="number" id="thr_t_spin" class="t_spin" min="0" max="500" step="1"/> ns
                </label>

                <div class="stats" id="table">
                    <table>
                        <tr><td></td><th>10</th><th>100</th><th>1000</th></tr>
                        <tr><th>Mean [ns]</th>
                            <td class="mean_10"></td>
                            <td class="mean_100"></td>
                            <td class="mean_1000"></td>
                        </tr>
                        <tr><th>Stdev [ps]</th>
                            <td class="stdev_10"></td>
                            <td class="stdev_100"></td>
                            <td class="stdev_1000"></td>
                        </tr>
                        <tr><th>Min [ns]</th>
                            <td class="min_10"></td>
                            <td class="min_100"></td>
                            <td class="min_1000"></td>
                        </tr>
                        <tr><th>Max [ns]</th>
                            <td class="max_10"></td>
                            <td class="max_100"></td>
                            <td class="max_1000"></td>
                        </tr>
                    </table>
                </div>
            </div>
        </template>

        <script>
            class ThresholdStats {
                constructor(table_div, control_div) {
                    this.table_div = table_div;
                    this.control_div = control_div;
                    this.history = [];
                    this.thr_v = null;
                    this.thr_t = null;
                }

                update(t_arr, v_arr) {
                    let crossing_t = null;
                    for (var i=1; i<t_arr.length; i++) {
                        if ((v_arr[i-1] <= this.thr_v && v_arr[i] >= this.thr_v) ||
                            (v_arr[i-1] >= this.thr_v && v_arr[i] <= this.thr_v)) {
                            let this_crossing_t = t_arr[i-1] + (t_arr[i] - t_arr[i-1]) * (this.thr_v - v_arr[i-1]) / (v_arr[i] - v_arr[i-1]);
                            if (crossing_t === null || Math.abs(crossing_t - this.thr_t) > Math.abs(this_crossing_t - this.thr_t)) {
                                crossing_t = this_crossing_t;
                            }
                        }
                    }
                    if (crossing_t != null) {
                        this.history.push(crossing_t);
                    }

                    this.update_table(10);
                    this.update_table(100);
                    this.update_table(1000);
                }

                update_table(period) {
                    if (period > this.history.length) {
                        this.table_div.querySelector(`.min_${period}`).innerHTML = '';
                        this.table_div.querySelector(`.max_${period}`).innerHTML = '';
                        this.table_div.querySelector(`.mean_${period}`).innerHTML = '';
                        this.table_div.querySelector(`.stdev_${period}`).innerHTML = '';
                        return;
                    }

                    let min = null;
                    let max = null; 
                    let mean = 0;
                    let stdev = 0;

                    for (let i=this.history.length-period; i<this.history.length; i++) {
                        if (min === null || this.history[i] < min) {
                            min = this.history[i];
                        }
                        if (max === null || this.history[i] > max) {
                            max = this.history[i];
                        }
                        mean += this.history[i];
                        stdev += this.history[i]*this.history[i];
                    }

                    mean /= period;
                    stdev = Math.sqrt(stdev/period - mean*mean);

                    this.table_div.querySelector(`.min_${period}`).innerHTML = min.toFixed(3);
                    this.table_div.querySelector(`.max_${period}`).innerHTML = max.toFixed(3);
                    this.table_div.querySelector(`.mean_${period}`).innerHTML = mean.toFixed(3);
                    this.table_div.querySelector(`.stdev_${period}`).innerHTML = (stdev * 1000).toFixed(0);
                }

                set_thr(thr_v, thr_t) {
                    this.thr_v = thr_v;
                    this.thr_t = thr_t;
                    this.control_div.querySelector('.v_spin').value = thr_v;
                    this.control_div.querySelector('.t_spin').value = thr_t;
                    this.history = [];
                }
            }

            class DataPlot {
                constructor(plot_div, table_div, heading_div, num_data) {
                    this.lock = false;
                    this.heading = heading_div;
                    this.div = plot_div;
                    this.layout_settings = {
                        margin: {t: 20, l: 60, r: 20, b: 40},
                        hovermode: 'closest',
                        //yaxis: {title: {text: 'V [V]'}},
                        //xaxis: {title: {text: 't [ns]'}},
                        showlegend: false};

                    let plot_arr = [];
                    for (let i=0; i<num_data; i++) {
                        plot_arr.push({'x': [], 'y': [], 'type': 'scatter'});
                    }
                    this.plot = Plotly.newPlot(this.div, plot_arr, this.layout_settings);
                    /*
                    this.stats = new ThresholdStats(table_div, meas_div);

                    this.div.on('plotly_click', (evt) => {
                        this.stats.set_thr(evt.points[0].y, evt.points[0].x);
                    });
                    */
                }

                update_plot (data_arr) {
                    /* 168 MHz system clock, 32 delay-mapped subdivisions per system clock cycle in HRTIM */
                    if (this.lock) {
                        return;
                    }

                    const color_palette = [
                        ['rgba(181,0,0,0.2)',   'rgba(181,0,0,0.0)',    'rgba(181,0,0,1.0)'],
                        ['rgba(255,102,0,0.2)', 'rgba(255,102,0,0.0)',  'rgba(255,102,0,1.0)'],
                        ['rgba(0,92,148,0.2)',  'rgba(0,92,148,0.0)',   'rgba(0,92,148,1.0)'],
                        ['rgba(0,0,0,0.1)',     'rgba(0,0,0,0.0)',      'rgba(0,0,0,0.4)'],
                        ];

                    let plot_data = []; 
                    data_arr.reverse(); /* ensure we draw the cal trace first, so everything else is on top */
                    for (const [in_adc, in_var] of data_arr) {
                        const sampling_period_ns = 1000 / 168 / 32;
                        const data_len = in_adc.length;

                        let adc_t = Array(data_len);
                        let var_t = Array(2*data_len);
                        let var_data = Array(2*data_len);
                        for (var i=0; i<data_len; i++) {
                            adc_t[i] = i * sampling_period_ns;

                            const i2 = 2*data_len - 1 - i;
                            var_t[i] = i * sampling_period_ns;
                            var_t[i2] = i * sampling_period_ns;

                            var_data[i2] = in_adc[i] + Math.sqrt(in_var[i]);
                            var_data[i] = in_adc[i] - Math.sqrt(in_var[i]);
                        }

                        const [var_fill, var_stroke, stroke] = color_palette.pop();
                        /* This cursed way of doing variance shading is from plotly's own docs lol.
                           https://plotly.com/python/continuous-error-bars/ */
                        plot_data.push({
                                'x': var_t,
                                'y': var_data,
                                'type': 'scatter',
                                'fill': 'toself',
                                'fillcolor': var_fill,
                                'line': {'color': var_stroke},
                                'hoverinfo': 'skip',
                                'showlegend': false,
                            });
                        plot_data.push({
                                'x': adc_t,
                                'y': in_adc,
                                'type': 'scatter',
                                'line': {'color': stroke}
                            });
                    }

                    this.plot = Plotly.react(this.div, plot_data, this.layout_settings);
                    //this.stats.update(adc_t, in_adc);
                    this.heading.style.backgroundColor = 'red';
                    window.setTimeout(() => {this.heading.style.backgroundColor = null;}, 100);
                }
            };

            adc_update_listeners = [];

            customElements.define("adc-plot",
                class extends HTMLElement {
                    constructor () {
                        super();
                        let template = document.getElementById("adc_plot_template");
                        const shadowRoot = this.attachShadow({"mode": "open"});
                        shadowRoot.appendChild(template.content.cloneNode(true));

                        let heading = shadowRoot.getElementById("heading");
                        let adc_index = this.getAttribute("adc-index");
                        let playbook_item = this.getAttribute("playbook-item");
                        heading.innerText = `ADC ${adc_index} ${playbook_item}`;

                        let playbook_item_list = playbook_item.split(',');
                        const plot = new DataPlot(
                            shadowRoot.getElementById("plot"),
                            shadowRoot.getElementById("table"),
                            //shadowRoot.getElementById("measurement"),
                            heading,
                            playbook_item_list.length
                            );
                        this.plot = plot;

                        adc_update_listeners.push((evt) => {
                            let data_arr = [];
                            for (const entry of playbook_item_list) {
                                data_arr.push([
                                    evt['data'][entry][`adc${adc_index}`],
                                    evt['data'][entry][`var${adc_index}`]
                                    ]);
                            }
                            plot.update_plot(data_arr);
                        });
                    }
                });

            function download_data() {
                let a = document.createElement('a');
                let spec_inp = document.querySelector('#dl_meta_specimen');
                const meta = document.querySelector('#dl_meta_info').value;
                const specimen = spec_inp.value;
                const mcu = document.querySelector('#dl_meta_mcu').value;
                const chip = document.querySelector('#dl_meta_chip').value;
                const timestamp = (new Date).toISOString();

                const blob = new Blob([JSON.stringify({
                    meta: `${meta}_${mcu}_${chip}_${specimen}`,
                    info: {
                        meta: meta,
                        specimen: specimen,
                        mcu: mcu,
                        chip: chip,
                        timestamp: timestamp,
                    },
                    settings: dbg_settings(),
                    data: last_data,
                })], {'type': 'application/json;charset=utf-8'});

                a.href = URL.createObjectURL(blob);
                a.download = 'multisample-' + timestamp + '.json';
                a.style.display = 'none';
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
            }

            document.querySelector('#download_form').addEventListener('submit', (evt) => {
                evt.preventDefault();

                download_data();

                let spec_inp = document.querySelector('#dl_meta_specimen');
                spec_inp.value = "";
                spec_inp.focus();
            });

            let last_data = null;
            const socket = io();
            socket.on('graph_update', (arg, callback) => {
                last_data = structuredClone(arg['data']);
                document.getElementById('dl_meta_mcu').value = arg['mcu_serial'];
                document.getElementById('dl_meta_chip').value = arg['chip_type'];
                for (const listener of adc_update_listeners) {
                    listener(arg);
                }
                if (document.getElementById('wait_for_trigger_inp').checked) {
                    download_data();
                }
            });

            function dbg_settings() {
                let out = {};
                document.querySelectorAll('input[data-dev-setting]').forEach((inp) => {
                    const key = inp.getAttribute('data-dev-setting');
                    const value = inp.type == 'checkbox' ? inp.checked : inp.value;
                    out[key] = value;
                });
                return out;
            }
            
            document.querySelectorAll('input[data-dev-setting]').forEach((inp) => {
                const key = inp.getAttribute('data-dev-setting');
                inp.addEventListener('change', (evt) => {
                    const value = inp.type == 'checkbox' ? inp.checked : inp.value;
                    socket.emit('dev_settings', {[key]: value});
                });
            });
            console.log("Sending defaults");
            socket.emit('dev_settings', dbg_settings());
        </script>
    </body>
</html>
