const fs = require('fs')

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

const JSUnit = require('./src/jsunit.js')
let isStateKey = JSUnit.isStateKey

var socket = {}

function newProgram(name) {
    var program = {
        description: {
            name: name,
            id: name
        },
        modules: {}
    }

    return program
}

function loadModuleFromSource(program, path, id) {
    // source -> heap
    if (fs.existsSync(path)) {
        // compile a new object based on definition in path
        var src = require(path)
        var mod = new src()

        // wants unique module id's 
        if (program.description.counter == null) {
            program.description.counter = 0
        } else {
            program.description.counter++
        }

        // make unique name, or recall from program save 
        if (id) {
            mod.description.id = id
        } else {
            mod.description.id = mod.description.name + '-' + program.description.counter
        }
        mod.description.path = path

        // add to program object 
        program.modules[mod.description.id] = mod

        // input need references for later hookup
        for (key in mod.inputs) {
            mod.inputs[key].parentId = mod.description.id
            mod.inputs[key].key = key
        }

        // state items get wrapped in a getter / setter
        // so that changes from internal modules can
        // push to UI 
        mod.state.init(mod.description.id, socket)

        mod.ui.init(mod.description.id, socket)
        
        /*
        for (key in mod.state) {
            if(isStateKey(key)){
                writeStateObject(mod, key)
            }
        }
        */

        console.log('ADDING', mod.description.id, 'to', program.description.id)

        /* ---------------------------------- */
        // WARN! Corner Case should Go Away or Improve at next spiral 
        if (mod.description.isLink) {
            for (mdlName in program.modules) {
                if (program.modules[mdlName].description.isLink) {
                    console.log("PRGMEM ONLY BIG ENOUGH FOR ONE LINK")
                    //process.exit()
                }
            }
        }
        // end corner case code 
        /* ---------------------------------- */

        /* ---------------------------------- */
        // WARN! Corner Case should Go Away or Improve at next spiral 
        // hardware corner case is hardware corner case
        // here's what we'll do
        // if it's hardware, and we're not loading from some saved program (no id)
        // 
        if (mod.description.isHardware && !mod.description.isLink /* && id == null */ ) {
            // make sure we haven't already done this, thx 
            var lnk = null
            for (mdlName in program.modules) {
                if (program.modules[mdlName].description.isLink) {
                    lnk = mdlName
                }
            }
            if (lnk) {
                console.log('CORNER CASE: LOADING HW MODULE, LINKING TO LINK')
                program.modules[lnk].attach(mod.route)
            } else {
                console.log('CORNER CASE: LOADING HW MODULE, ALSO LOADING LINK')
                var link = loadModuleFromSource(program, './modules/hardware/atkseriallink.js')
                // hook it up auto-like 
                link.attach(mod.route)
            }
        }

        // also return it so that we can write programs without the UI 
        return mod
    } else {
        console.log('ERR no module found at ', path)
    }
}

/*
function writeStateObject(mod, key) {
    mod.state['_' + key] = mod.state[key]
    mod.state[key] = {}

    Object.defineProperty(mod.state, key, {
        set: function(x) {
            // update internal value 
            this['_' + key] = x
            // console.log('SET', key, this['_' + key])
            // push to external view
            if (socket) {
                pushState(mod, key)
            }
        }
    })
    Object.defineProperty(mod.state, key, {
        get: function() {
            console.log('KEY', key)
            console.log("IS", this['_' + key])
            //console.log('GET', key, this['_' + key])
            return this['_' + key]
        }
    })
}
*/

function removeModuleFromProgram(program, id) {
    // this simple? 
    delete program.modules[id]

    for (key in program.modules) {
        var mdl = program.modules[key]
        for (otKey in mdl.outputs) {
            mdl.outputs[otKey].checkLinks(id)
        }
    }
}

/*

EXTERNAL HOOKS

*/

function assignSocket(sckt) {
    // we can pass this object 'down' by reference, once it loads 
    socket.send = sckt.send 
}

function saveProgram(prgmem, path) {
    // ok, and we're interested in just copying the relevant things ... 
    var svprgmem = {
        description: {
            name: prgmem.description.name,
            counter: prgmem.description.counter
        },
        modules: {}
    }

    var mdls = prgmem.modules

    for (key in mdls) {
        var mdl = mdls[key]
        var og = Reps.makeFromModule(mdl)
        svprgmem.modules[mdl.description.id] = og
    }

    fs.writeFileSync(path, JSON.stringify(svprgmem, null, 2), 'utf8')

    console.log('PROGRAM SAVED AT', path)
}

function openProgram(path) {
    var program = {}

    // get the .json file as an object 
    var prgRep = JSON.parse(fs.readFileSync(path, 'utf8'))
    console.log('OPENING THIS PRGRAM REP', prgRep)

    // copy-pasta the program descro, 
    program.description = {
        name: prgRep.description.name,
        counter: 0,
        id: prgRep.description.name + 1, // in another world, we count
        path: path
    }

    // gonna get those modules from source
    program.modules = {}

    for (key in prgRep.modules) {
        var mdlRep = prgRep.modules[key]
        loadModuleFromSource(program, mdlRep.description.path, mdlRep.description.id)
    }

    // restore saved state and links 
    for (modName in prgRep.modules) {
        // keys should be identical for rep and heap
        // this is the representation that we're opening up from 
        var mdlRep = prgRep.modules[modName]

        // this is the actual object, having functions and whatnot 
        var mdl = program.modules[modName]

        if (mdl == null) {
            console.log('-------------------------------- NULL MDL at openProgram')
            console.log('prgRep modules', prgRep.modules)
            console.log('program modules', program.modules)
        }

        // hooking outputs -> inputs 
        for (outName in mdlRep.outputs) {
            var outRep = mdlRep.outputs[outName]
            // each has some caller ids 
            for (nestedInputRep in outRep.calls) {
                // conn from tl program -> this hookup 
                var nIRParent = outRep.calls[nestedInputRep].parentId
                var nIRKey = outRep.calls[nestedInputRep].key
                var nI = program.modules[nIRParent].inputs[nIRKey]
                console.log("ATTACHING", nIRKey, 'to', outName)
                mdl.outputs[outName].attach(nI)
            }
        }

        // restoring state 
        /* the first wrap, only here to prevent link not-reconnecting on program restart, should go away */
        if (!mdlRep.description.isLink) {
            for (key in mdlRep.state) {
                if (isStateKey(key)) {
                    // I think this is OK?
                    // would prefer to do this before we write getters and setters
                    // for now we walk-around to secret key ... 
                    if (mdl.state[key].type == 'button' || mdl.state[key].type == 'multiline') {
                        // defaul vals
                    } else if (key == 'route'){
                        // absolutely does not belong here 
                        // TODO: states: sometimes we load, we want to run the change emitter ... sometimes we don't
                        // what choice ? 
                        mdl.state['_' + key] = mdlRep.state[key]
                        mdl.state.emitUIChange('route')
                    } else {
                        mdl.state['_' + key] = mdlRep.state[key]
                    }
                }
            }
        }

        // and let's run init if it's there
        if(mdl.init != null){
            mdl.init()
        }

        //console.log('mdlRep', mdlRep)
        //console.log('mdl', mdl)
        // restore position / UI state
        if (mdlRep.description.position != null) {
            // ??
            mdl.description.position = {}
            mdl.description.position.left = mdlRep.description.position.left
            mdl.description.position.top = mdlRep.description.position.top
        }
    }

    // once modules exist, link inputs / outputs / copy state ? 

    return program
}

module.exports = {
    new: newProgram,
    open: openProgram,
    save: saveProgram,
    loadModuleFromSource: loadModuleFromSource,
    removeModule: removeModuleFromProgram,
    assignSocket: assignSocket
}