
class ScannerManager {

    constructor(container, options = {}) {
        this.container = container;
        this.socket = null;
        this.debug_count = 0;
        
        this.ts = options.ts; 
        this.cx = options.cx; 
        this.cy = options.cy; 
        this.x  = options.x; 
        this.y  = options.y;        
        this.xbase= options.xbase; 
        this.ybase= options.ybase; 
        this.test = options.test; 
        this.halt = options.halt;      
        this.speed_px_s  = options.speed_px_s; 
        this.axial_speed = options.axial_speed; 
        this.axial_pos   = options.axial_pos; 
        this.area_px     = options.area_px; 
        this.frame_count = options.frame_count; 
        this.goto_0  = options.goto_0; 
        this.goto_xy = options.goto_xy; 
        this.xy_base = options.xy_base; 
        this.up      = options.up; 
        this.down    = options.down; 
        this.left    = options.left; 
        this.right   = options.right; 
        this.duration= options.duration; 
        this.feed    = options.feed; 
        this.step    = options.step; 
        this.well    = options.well; 
        this.debug   = options.debug; 
        this.calib_debug = options.calib_debug; 
        this.calib_center= options.calib_center; 
        this.previous    = options.previous;   
        this.next        = options.next;     
        this.set_well    = options.set_well; 
        this.well_btn    = options.well_btn;  
        this.median      = options.median; 
        this.crop        = options.crop; 
        this.crop_radius = options.crop_radius;
        this.calib_auto  = options.calib_auto;
        
        try {
            this.min_area_px = options.min_area_px;
            this.max_area_ratio = options.max_area_ratio;
            this.max_planarians = options.max_planarians;
            this.merge_kernel_size = options.merge_kernel_size;
            this.min_contour_dist_px = options.min_contour_dist_px;
            this.draw = options.draw;
        } catch(e) {}    
    }
    
    init_controls() {       
        this.up.addEventListener('mousedown',      (e) => { this._send({ type: 'calibrate', topic: "up" }); });
        this.down.addEventListener('mousedown',    (e) => { this._send({ type: 'calibrate', topic: "down" }); });
        this.left.addEventListener('mousedown',    (e) => { this._send({ type: 'calibrate', topic: "left" }); });
        this.right.addEventListener('mousedown',   (e) => { this._send({ type: 'calibrate', topic: "right" }); });

        this.goto_0.addEventListener('click',      (e) => { this.clear_buttons(); this._send({ type: 'calibrate', topic: "goto_0" }); });
        this.goto_xy.addEventListener('click',     (e) => { this.clear_buttons(); this._send({ type: 'calibrate', topic: "goto_xy" }); });
        this.xy_base.addEventListener('click',     (e) => { this._send({ type: 'calibrate', topic: "xy_base" }); });
        
        this.calib_debug.addEventListener('click', (e) => { this._send({ type: 'calibrate', topic: "calib_debug" }); });       
        this.previous.addEventListener('click',    (e) => { this._send({ type: 'calibrate', topic: "previous" }); });
        this.next.addEventListener('click',        (e) => { this._send({ type: 'calibrate', topic: "next" }); });       
        this.set_well.addEventListener('click',    (e) => { this._send({ type: 'calibrate', topic: "set_well" }); });       
        
        this.median.addEventListener('click',      (e) => { this._send({ type: 'calibrate', topic: "median" }); });
        this.crop.addEventListener('click',        (e) => { this._send({ type: 'calibrate', topic: "crop" }); });
        this.crop_radius.addEventListener('change',(e) => { this._send({ type: 'calibrate', topic: "crop_radius", value: this.crop_radius.value }); });
        this.well.addEventListener("change",       (e) => { this._send({ type: 'calibrate', topic: "position", value: e.target.value }); });
        this.step.addEventListener("change",       (e) => { this._send({ type: 'calibrate', topic: "step", value: e.target.value }); });
        this.feed.addEventListener("change",       (e) => { this._send({ type: 'calibrate', topic: "feed", value: e.target.value }); });
        this.duration.addEventListener("change",   (e) => { this._send({ type: 'calibrate', topic: "duration", value: e.target.value }); });

        this.test.addEventListener('click',         (e) => { this._send({ type: 'calibrate', topic: "test" }); });
        this.calib_center.addEventListener('click', (e) => { this._send({ type: 'calibrate', topic: "center" }); });
        this.calib_auto.addEventListener('click',   (e) => { this._send({ type: 'calibrate', topic: "auto" }); });
        this.halt.addEventListener('click',         (e) => { this._send({ type: 'calibrate', topic: "halt" }); });
        
        try { 
            this.min_area_px.addEventListener('change',         (e) => { this._send({ type: 'calibrate', topic: "min_area_px", value: e.target.value }); });
            this.max_area_ratio.addEventListener('change',      (e) => { this._send({ type: 'calibrate', topic: "max_area_ratio", value: e.target.value }); });
            this.max_planarians.addEventListener('change',      (e) => { this._send({ type: 'calibrate', topic: "max_planarians",  value: e.target.value}); });
            this.merge_kernel_size.addEventListener('change',   (e) => { this._send({ type: 'calibrate', topic: "merge_kernel_size",  value: e.target.value}); });
            this.min_contour_dist_px.addEventListener('change', (e) => { this._send({ type: 'calibrate', topic: "min_contour_dist_px", value: e.target.value }); });
            this.draw.addEventListener('click',                (e) => { this._send({ type: 'calibrate', topic: "draw", value: e.target.value }); });
        } catch(e) {}    
    }

    registerSocket(socket)  {
        this.socket = socket;
        this.init_controls();
    }

    update(payload) {
        try {            
            if (payload.jpeg)   { this.container.src = `data:image/jpeg;base64,${payload.jpeg}`; }
            if (payload.xbase)  { this.xbase.textContent = payload.xbase;  this.ybase.textContent = payload.ybase; }
            if (payload.xy)     { this.x.textContent=payload.x.toFixed(2); this.y.textContent=payload.y.toFixed(2); }
            if (payload.state)  { 
                if (payload.state == 'debug') {
                    const span = this.calib_debug.querySelector("span.debug"); span.style.color = payload.value ? '#f0f': '#fff';
                } else if (payload.state == 'median') {
                    const span = this.median.querySelector("span.median"); span.style.color = payload.value ? '#f0f' : '#fff';
                } else if (payload.state == 'crop') {
                    const span = this.crop.querySelector("span.crop"); span.style.color = payload.value ? '#f0f' : '#fff';
                }
                this.debug.insertAdjacentHTML('afterbegin', `<li>[ ${++this.debug_count} - ${payload.state} ]: ${payload.msg}</li>`); 
            }
            if (payload.ts)     { this.ts.textContent = timestampToLocalISOString(payload.ts); }
                        
            if (payload.buttons) { this.well_btn.innerHTML = payload.buttons; }
            if (payload.current >= 0) {                 
                document.querySelectorAll('button.w3-button.well').forEach(btn => {
                    if (btn.value==payload.current) { btn.classList.add('w3-green'); return; }
                    btn.classList.remove('w3-green'); 
                });
            }
        } catch(e) { console.log(e); }
    }
    
    clear_buttons() { document.querySelectorAll('button.w3-button.well').forEach(btn => {btn.classList.remove('w3-green'); }); }
    goto_well(b)    { this.clear_buttons(); b.classList.add('w3-green'); this._send({ type: 'calibrate', topic: "goto", value: b.value }); }
    init()          {
        this.clear_buttons();
        this._send({
            type: 'calibrate',
            topic: "init",
            feed: this.feed.value,
            step: this.step.value,
            position: this.well.value,
            duration: this.duration.value
        });
    }
    start()         { this._send({ type: 'scanner', topic: "start"}); }
    halt()          { this._send({ type: 'scanner', topic: "halt" }); }

    _send(message)  { this.socket.send(message); }
}

class MetadataSocket {
    constructor(url) {
        this.url = url;
        this.ws = null;
        this.manager = null;
        this.reconnectDelay = 1000;
        this.shouldReconnect = true;
        this.reconnect = false;
    }

    setManager(manager) { this.manager = manager; }

    connect()           {
        this.ws = new WebSocket(this.url);

        this.ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.manager.update(data);
        };

        this.ws.onopen = (event) => {
            if (this.manager && !this.reconnect)
                this.manager['init']();
            this.reconnect = false;
        };

        this.ws.onclose = () => {
            console.warn(`WebSocket closed...`);
            if (this.shouldReconnect) {
                this.reconnect = true;
                setTimeout(() => {
                    console.log("Reconnect WebSocket...");
                    this.connect();
                }, this.reconnectDelay);
            }
        };
    }

    send(obj) { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(obj));  } }
}