// boilerplate atkapi header
const InOut = require('../../lib/jsunit.js')
let Input = InOut.Input
let Output = InOut.Output

let State = InOut.State
let Button = InOut.Button

const SerialPort = require('serialport')

function ATKSerialLink() {
    var atkSerialLink = {
        description: {
            isHardware: true,
            isLink: true,
            name: 'Serialport ATK Link',
            alt: 'window into hardware world'
        },
        routes: new Array()
    }

    var state = State()
    atkSerialLink.state = state

    state.portName = '---'
    state.connect = Button('click to find and connect', findSerialPort)
    state.portStatus = 'closed' // or we hope it will be 

    /*
    ------------------------------------------------------
    HOOKING UP 
    ------------------------------------------------------
    */

    atkSerialLink.attach = function(route) {
        console.log('PORT->LINK HOOKUP', route.route)
        this.routes.push(route)
        route.link = this
    }

    /*
    ------------------------------------------------------
    SERIALPORT MANAGEMENT 
    ------------------------------------------------------
    */

    // instance of SerialPort 
    serialport = null

    function findSerialPort() {
        state.portStatus = 'searching for CP2102 device'
        var found = false 
        SerialPort.list(function(err, ports) {
            ports.forEach(function(serialport) {
                if (serialport.manufacturer == 'Silicon Labs') {
                    console.log('found cp2102 serialport')
                    found = true 
                    state.portName = serialport.comName
                    openSerialPort()
                }
            })
        })
        if(!found){
            console.log('no CP2102 device found, try serialport-list to make sure it is available to the system')
            state.portStatus = 'no CP2102 device found'
        }
    }

    function openSerialPort() {
        if (state.portName == '---') {
            findSerialPort()
        } else {
            if (serialport == null) {
                serialport = new SerialPort(state.portName, {
                    baudRate: 250000
                })
                serialport.on('open', function() {
                    state.portStatus = 'open'
                })
                serialport.on('error', function(err) {
                    state.portStatus = err.message
                })
                serialport.on('data', onSerialPortData)
            }
        }
    }

    /*
    ------------------------------------------------------
    PACKETS TO HARDWARE
    ------------------------------------------------------
    */

    atkSerialLink.send = function(msg, route) {
        // it would be responsible to check this over now, but hey
        console.log('send', msg, 'on', route.route)
        // dereference this 
        var pckt = JSON.parse(JSON.stringify(msg))
        if (Array.isArray(pckt)) {
            pckt.unshift(255) // end delimiter 
            pckt.unshift(254) // ptr 
            var literalRoute = route.route.split(',')
            pckt = literalRoute.concat(pckt) // add route 
            pckt.unshift(pckt.length + 1) // add length byte 
            if (writeToSerialPort(pckt)) {
                console.log('PCKT OUT >>', pckt.toString(), '---------------')
            } else {
                // try to open ? 
                openSerialPort()
                if (writeToSerialPort(pckt)) {
                    console.log('PCKT OOT >>', pckt.toString())
                } else {
                    console.log('ERR: attempt to send to hardware, port not writable')
                }
            }
        } else {
            console.log('non-array on atkSerialLink input')
        }
    }

    // HERE this module is a hot mess 

    function writeToSerialPort(pckt) {
        if (serialport != null && serialport.writable) {
            if (serialport.write(pckt)) {
                return true
            } else {
                console.log('------------------ !ACHTUNG! -------------------')
                console.log('------------------ !ACHTUNG! -------------------')
                console.log('---------- serialport.write(pckt) false --------------')
                // https://nodejs.org/api/stream.html#stream_event_drain
                // https://serialport.io/docs/api-stream
                return false
            }
        } else {
            return false
        }
    }

    /*
    ------------------------------------------------------
    PACKETS FROM HARDWARE 
    ------------------------------------------------------
    */

    var thisPacket = new Array()

    function onSerialPortData(data) {
        // we'll make sure it's what we think it will be
        // console.log("PORT DATA")
        var dtArray = new Array()
        if (Buffer.isBuffer(data)) {
            for (var i = 0; i < data.length; i++) {
                dtArray[i] = data[i]
            }
        } else {
            console.log("ERR: port data non-buffer")
        }

        //console.log('DATA IN', dtArray.toString())

        thisPacket = thisPacket.concat(dtArray)
        if (thisPacket[0] <= 0) {
            thisPacket = []
            console.log('throwing packet with leading zero')
        }

        while (thisPacket.length >= thisPacket[0]) {
            if (thisPacket.length == thisPacket[0]) {
                var packetCopy = thisPacket.slice(0) // copy, deref
                thisPacket = []
                onPacket(packetCopy)
            } else { // rare case of two packets saddling break 
                var fullPacket = thisPacket.slice(0, thisPacket[0])
                onPacket(fullPacket)
                thisPacket = thisPacket.slice(thisPacket[0])
            }

        }
    }

    function onPacket(pckt) {
        var debug = false
        // we're the last link, shift that pointer
        shiftPacketPointer(pckt)
        // log it
        console.log('PCKT IN <<', pckt.toString(), '-----------------------')
        // grab the routing header
        var incomingRoute = pckt.slice(2, pckt.indexOf(255))
        // flip to match outgoing 
        var returnRoute = new Array()
        for (i in incomingRoute) {
            returnRoute[i] = incomingRoute[incomingRoute.length - 1 - i]
        }
        // now we'll look for a reciprocal port from our list 
        var match = false
        for (key in atkSerialLink.routes) {
            if (returnRoute.toString() === atkSerialLink.routes[key].route.toString()) {
                // strip header and return message 
                var msg = pckt.slice(pckt.indexOf(255) + 1)
                // this slices down to the keys ... doesn't take the keys away 
                match = true 
                atkSerialLink.routes[key].onMessage(msg)
            }
        }
        if (!match) {
            console.log("PACKET RETURN AND NO KEY FOUND")
            console.log(pckt)
        }
    }

    function shiftPacketPointer(pckt) {
        var end = 0
        var i = 0
        while (end == 0) {
            if (pckt[i] === 255) {
                end = i
            } else if (i >= pckt.length) {
                break
            }
            i++
        }
        //array[1] = 254
        for (var j = 1; j < end - 1; j++) {
            pckt[j] = pckt[j + 1]
        }
        pckt[end - 1] = 0
        // console.log('shifted', pckt)
    }

    return atkSerialLink
}

module.exports = ATKSerialLink