// file system to load / look at our available modules / edit them 
const fs = require('fs')

// express serves files, http makes the connection
const app = require('express')()
const http = require('http').Server(app)

// websocket, to share program representations with the client (and back)
const WebSocket = require('ws')

const Reps = require('./reps.js')
const Programs = require('./programs.js')

/*

SERVER AND WS SETUP --------------------------------------------------

*/

var program = null
var sckt = null
var verbose = false 

function startHttp() {
    // serving this handful of static files
    app.get('/', (req, res) => {
        console.log('client req /')
        res.sendFile(__dirname + '/client/index.html')
    })

    app.get('/:file', (req, res) => {
        console.log('client req', req.params.file)
        res.sendFile(__dirname + '/client/' + req.params.file)
    })

    app.get('/ui/:file', (req, res) => {
        console.log('client req ui', req.params.file)
        res.sendFile(__dirname + '/client/ui/' + req.params.file)
    })

    app.get('/ui/libs/:file', (req, res) => {
        console.log('client req ui lib', req.params.file)
        res.sendFile(__dirname + '/client/ui/libs/' + req.params.file)
    })

    // through this window
    http.listen(8080, () => {
        console.log('RNDMC is listening on localhost:8080')
    })
}


function startWs() {
    // and listening for requests here
    const wss = new WebSocket.Server({ port: 8081 })

    wss.on('connection', (ws) => {
        sckt = ws
        // say hello
        socketSend('console', 'hello client')
        // send current config as list of all modules
        console.log('socket open on 8081')
        ws.on('message', (evt) => {
            socketRecv(evt)
        })
    })
}

/*

HOOKUP REF TO TL ------------------------------------------------------

*/

function assignProgram(prgm) {
    program = prgm
}

/*

RECV / SEND PORTALS ---------------------------------------------------

*/

function socketRecv(evt) {
    var recv = JSON.parse(evt)
    var type = recv.type
    var data = recv.data
    //console.log('RECV', recv)
    // bang thru
    switch (type) {
        case 'console':
            console.log('RECV CONSOLE:', data)
            break
        case 'get current program':
            uiRequestCurrentProgram()
            break
        case 'get module menu':
            uiRequestModuleMenu()
            break
        case 'get program menu':
            uiRequestProgramMenu()
            break
        case 'load program':
            uiRequestLoadProgram(data)
            break
        case 'save program':
            uiRequestSaveProgram(data)
            break
        case 'put module':
            uiRequestNewModule(data)
            break
        case 'remove module':
            uiRequestRemoveModule(data)
            break
        case 'put state change':
            uiRequestStateChange(data)
            break
        case 'put link change':
            uiRequestLinkChange(data)
            // id:output > id:input
            break
        case 'put ui change':
            uiRequestUiChange(data)
            break 
        case 'put position change':
            uiRequestMdlPositionChange(data)
            break
        default:
            console.log('ERR server recv with non recognized type', recv)
            break
    }
}

function socketSend(type, data) {
    if (sckt != null && sckt.readyState === 1) {
        var msg = {
            type: type,
            data: data
        }
        //console.log('SEND', msg)
        sckt.send(JSON.stringify(msg))
    } else {
        console.log('on socketSend, ws not ready')
    }
}

/*

HEAP -> UI HANDLES ---------------------------------------------------

*/

// runs when browser first loads
function uiRequestCurrentProgram() {
    // make reps from program and send the list 
    console.log('SEND PROGRAMS TO UI')
    var prgRep = {
        description: {
            name: program.description.name,
            id: program.description.id,
            path: program.description.path
        },
        modules: {}
    }
    if(program.description.view != null){
        prgRep.description.view = program.description.view
    }
    for (mdlName in program.modules) {
        var mdlRep = Reps.makeFromModule(program.modules[mdlName])
        prgRep.modules[mdlName] = mdlRep
    }
    socketSend('put program', prgRep)
}


// TODO: proper heirarchy, with both of these ... 

function uiRequestModuleMenu() {
    var availableSourceRep = {}
    var dir = fs.readdirSync('./modules')
    for (i in dir) {
        availableSourceRep[dir[i]] = {}
        var subdir = fs.readdirSync('./modules/' + dir[i])
        for (j in subdir) {
            // find js files
            if (subdir[j].slice(-3) === '.js') {
                var obj = {}
                obj.path = './modules/' + dir[i] + '/' + subdir[j]
                availableSourceRep[dir[i]][subdir[j].slice(0, -3)] = obj
            }
        }
    }
    socketSend('put module menu', availableSourceRep)
}

function uiRequestProgramMenu() {
    var availableProgramRep = {}
    var paths = fs.readdirSync('./programs')
    for (i in paths) {
        if (paths[i].slice(-5) === '.json') {
            var name = paths[i].slice(0, -5)
            var path = './programs/' + paths[i]
            availableProgramRep[paths[i]] = {
                name: name,
                path: path
            }
        }
    }
    socketSend('put program menu', availableProgramRep)
}

/*

UI -> HEAP HANDLES ---------------------------------------------------

*/

function uiRequestLoadProgram(data) {
    console.log('UI REQUEST TO OPEN NEW PROGRAM', data)
    // gonna tear it doooown, maybe just kick page afterwards to restart it?
    program = null
    program = Programs.open(data)

    socketSend('restart', '')
}

function uiRequestSaveProgram(data) {
    console.log('UI REQUEST TO SAVE PROGRAM', data)
    // is data a path? add .json ? 
    if (data) {
        if (!data.includes('.json')) {
            data = data + '.json'
        }
        path = 'programs/' + data
        Programs.save(program, path)
        socketSend('console', ('saved program at' + path))
    }
}

function uiRequestNewModule(data) {
    console.log('UI REQUEST ADD MODULE TO PROGRAM', data)
    Programs.loadModuleFromSource(program, data)
    // bit of a mess to pick out the last entered module 
    var keys = Object.keys(program.modules)
    var latest = keys[keys.length - 1]
    if (program.modules[latest].description.isLink && data != './modules/hardware/atkseriallink.js') {
        // we just added hardware, so added a link, so we've added two 
        // just burn it down 
        socketSend('restart', '')
    }
    // TODO: questionable init - here and in loadProgram, should be better handled across board 
    if (program.modules[latest].init != null) {
        program.modules[latest].init()
    }
    socketSend('put module', Reps.makeFromModule(program.modules[keys[keys.length - 1]]))
}

function uiRequestRemoveModule(data) {
    console.log('UI REQUEST TO REMOVE MODULE', data.id)
    Programs.removeModule(program, data.id)
    socketSend('restart', '')
}

function uiRequestStateChange(data) {
    console.log('UI REQUEST CHANGE STATE IN MODULE', data)

    var mdlState = program.modules[data.id].state

    if (mdlState[data.key] != null) {
        mdlState[data.key] = data.val
        mdlState.emitUIChange(data.key)
    } else {
        console.log("ERR no state key,", data.key, "found here", data)
        console.log("THE KEY", data.key)
        console.log("THE MDL", mdlState)
        console.log("THE STATE", mdlState[data.key])
    }
}

function uiRequestLinkChange(data) {
    console.log('UI REQUEST ADD EVENT LINK', data)
    var fromId = data.from.id
    var outputName = data.from.output
    var toId = data.to.id
    var inputName = data.to.input

    var fromMdl = program.modules[fromId]
    var toMdl = program.modules[toId]

    // if it's already attached, we rm
    if (fromMdl.outputs[outputName].isLinked(toMdl.inputs[inputName])) {
        fromMdl.outputs[outputName].remove(toMdl.inputs[inputName])
    } else {
        fromMdl.outputs[outputName].attach(toMdl.inputs[inputName])
    }

    var nRep = Reps.makeFromModule(fromMdl)
    socketSend('put module change', nRep)
}

function uiRequestUiChange(data){
    if(verbose) console.log('UI PUSH UI DATA DOWN', data)
    var mdlUiElem = program.modules[data.id].ui[data.key]
    mdlUiElem.onMessage(data.msg)
}

function uiRequestMdlPositionChange(data) {
    if(verbose) console.log('UI REQUEST ADD / CHANGE UI INFO TO MODULE', data)
    var mod = program.modules[data.description.id]
    mod.description.position = data.description.position
}

/*

EXPORTS --------------------------------------------------------------

*/

module.exports = {
    startHttp: startHttp,
    startWs: startWs,
    uiSocket: {
        send: socketSend
    },
    assignProgram: assignProgram
}