const express = require('express')
const http = require('http')
const WebSocket = require('ws')
const osc = require('osc')
const readline = require('readline')
const fs = require('fs')

const port = 6676
// Vzemi iz argumenta
const tty = process.argv[2]

let eulerRotation = [0, 0, 0]

let baudrate = parseInt(process.argv[3])
if (!baudrate) {
  baudrate = 115200
}

const include_files = [
  '/anim.js',
  '/control.js',
  '/osctl.js',
  '/test.js',
    '/node_modules/three/build/three.min.js',
    '/node_modules/nouislider/distribute/nouislider.min.js',
  '/node_modules/nouislider/distribute/nouislider.min.css',
  '/node_modules/osc/dist/osc-browser.js'
];

const app = express();
const server = http.Server(app);

const DEBUG = true

// Odprti serijski OSC link
let scon = null

let buttonHeld = [false, false, false, false]
let buttonValue = [false, false, false, false]

let switchHeld = [false, false, false, false]
let switchValue = [false, false, false, false]



function openSerial() {
  console.log('opening ', tty, baudrate)

  scon = new osc.SerialPort({
    devicePath: tty,
    bitrate: baudrate,
    autoOpen: true,
    useSLIP: true
  })
  scon.open()

  scon.on('open', e => {
    console.log('serial connection opened')
    //console.log(scon)
  })
  scon.on('error', e => {
    console.error('tty error', e)
    //scon.close()
  })
  scon.on('close', e => {
    console.warn('serial connection closed, restarting in 1 second')
  })
  
  // Arduino OSC gre v web
  scon.on('message', msg => {
    // Debug incoming osc
    if (DEBUG) {
      //console.log('osc msg', msg)
    }

    sendAll(msg, null, null, osclients)
  })

  if (scon._closeCode) {
    scon = null
    console.log('restarting serial connection')
    setTimeout(openSerial, 1000)
  }
}

if (tty) {
  openSerial(baudrate)
}

app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
});

app.get('/ctl', (req, res) => {
    res.sendFile(__dirname + '/control.html');
});

app.get('/test', (req, res) => {
    res.sendFile(__dirname + '/test.html');
});

let settings = {};
app.get('/settings', function(req, res) {
    res.send(settings);
});

include_files.map(function(file) {
    app.get(file, function(req, res){
        res.sendFile(__dirname + file);
    });
});

server.listen(port, () => console.log('listening on *:' + port))

// Websocket init
const wss = new WebSocket.Server({ server })


// Relay multicast to websockets
// @TODO still sends to supercollider? Do we need two sockets?
//var dgram = require('dgram');
//var sss = dgram.createSocket('udp4');
//sss.on('listening', () => {
//  sss.addMembership('224.0.1.9');
//})
//sss.bind(57120);

const scudp = new osc.UDPPort({
  //socket: sss
  remotePort: 57121,
  localPort: 57100
})

scudp.on('open', () => {
  console.log("UDP to OSC open")
})

scudp.on('message', (msg) => {
  console.log('got UDP msg', msg);
  osclients.forEach( client => {
    if (client) {
      //console.log("sending", msg, info)
      client.send(msg)
    }
  })
})
scudp.on('error', (e) => {
  console.log('UDP OSC error!', e)
})
scudp.open()

function eulerFromQuaternion(quaternion) {
    // Quaternion to matrix.
    const w = quaternion[0], x = quaternion[1], y = quaternion[2], z = quaternion[3];
    const x2 = x + x, y2 = y + y, z2 = z + z;
    const xx = x * x2, xy = x * y2, xz = x * z2;
    const yy = y * y2, yz = y * z2, zz = z * z2;
    const wx = w * x2, wy = w * y2, wz = w * z2;
    const matrix = [
        1 - ( yy + zz ), xy + wz,         xz - wy,         0,
        xy - wz,         1 - ( xx + zz ), yz + wx,         0,
        xz + wy,         yz - wx,         1 - ( xx + yy ), 0,
        0,               0,               0,               1
    ];
    // Matrix to euler
    function clamp( value, min, max ) {
        return Math.max( min, Math.min( max, value ) );
    }
    const m11 = matrix[ 0 ], m12 = matrix[ 4 ], m13 = matrix[ 8 ];
    const m21 = matrix[ 1 ], m22 = matrix[ 5 ], m23 = matrix[ 9 ];
    const m31 = matrix[ 2 ], m32 = matrix[ 6 ], m33 = matrix[ 10 ];
    var euler = [ 0, 0, 0 ];

    euler[1] = Math.asin( clamp( m13, - 1, 1 ) );
    if ( Math.abs( m13 ) < 0.9999999 ) {
        euler[0] = Math.atan2( - m23, m33 );
        euler[2] = Math.atan2( - m12, m11 );
    } else {
        euler[0] = Math.atan2( m32, m22 );
        euler[2] = 0;
    }
    return euler;
}


var threshold = 20;

const sendAll = (msg, info, oscWS, osclients) => {

    if (msg.address == '/keys') {
	// keys 0 + 3 = euler reset
	if (msg.args[0] <= threshold && msg.args[3] <= threshold  ) {	    
	    eulerRotation = [0, 0, 0];
	    console.log("EULER ROTATION RESET");
	}
    }

    var waiting = false; 

    function waitFalse() {
        waiting = false;
    }

    
    function wait4Second() {
	waiting = true;
	setTimeout(waitFalse, 1000);
	
    }     


    // LOGIC FOR SHIFT BUTTON 0
    for (let i = 0; i < 1; i++) {
        // momentary state between 0 and 1
        if (msg.address == '/keys') {
	    if (msg.args[i] <= threshold ) {
		buttonValue[i] = 1;
	    } else {
		buttonValue[i] = 0;
	    }
	    msg.args.push(buttonValue[i] ? 1 : 0)
	}	    
    }

    // LOGIC FOR MOMENTARY BUTTONS (1 -  3)
	for (let i = 1; i < 8; i++) {
            // momentary state between 0 and 1
            if (msg.address == '/keys') {
		buttonValue[i] = 0;
		if (msg.args[i] <= threshold ) {
		    if (buttonHeld[i] == false) {
			buttonHeld[i] = true
			buttonValue[i] = 1;
		    }
		} else if (msg.args[i] >= threshold+5) {
		    buttonHeld[i] = false;
		}
		msg.args.push(buttonValue[i] ? 1 : 0)
	    }	    
	}
   
    
    // LOGIC FOR TOGGLE SWITCHES (4-7)
    
    // for (let i = 4; i < 8; i++) {
    // 	// toggle button 2 state between 0 and 1
    // 	if (msg.address == '/keys') {
    // 	    if (msg.args[i] <= threshold ) {
    // 		if (switchHeld[i-4] == false) {
    // 		    switchValue[i-4] = !switchValue[i-4]
    // 		    switchHeld[i-4] = true
    // 		}
    // 	    } else {

    // 		if (switchHeld[i-4]) {
    // 		    switchHeld[i-4] = false
    // 		}
    // 	    }
    // 	    msg.args.push(switchValue[i-4] ? 1 : 0)
    // 	}
    // }
	//------------------------------------------------
    
  // Convert quaternion diff to euler angle diff
  if (msg.address == '/quaternionDiff') {
    const euler = eulerFromQuaternion(msg.args, 'XYZ');
    sendAll({
      address: '/eulerDiff',
      args: euler
    }, info, oscWS, osclients)

    eulerRotation[0] += euler[0]
    eulerRotation[1] += euler[1]
    eulerRotation[2] += euler[2]

    sendAll({
      address: '/euler',
      args: eulerRotation
    }, info, oscWS, osclients)
  }

  osclients.forEach( client => {
    if (client && oscWS != client) {
      // console.log("sending", msg, info)
      client.send(msg)
    }
  })

  if (scudp) {
      console.log("SENDING UDP", msg)
      scudp.send(msg)
      //msg.slice(7,15);
      //scudp.send(msg.slice(7,15))
  }
}

const osclients = []

wss.on('connection', function (ws) {
  console.log('new client connection')
  const oscWS = new osc.WebSocketPort({
    socket: ws,
    metadata: false
  });

  // Vsi OSC sem grejo naprej na kliente OSC
  oscWS.on('packet', (packet, info) => {
    // Broadcast adjust msg
    const [address, args] = packet
    sendAll({ address, args}, info, oscWS, osclients)
  })

  oscWS.on('error', error => {
    console.warn('Ignoring invalid OSC')
    console.warn(error)
  })
  osclients.push(oscWS)
})

