/*

line input

*/

// HEADER
import {
  Hunkify,
  Input,
  Output,
  State
} from './hunks.js'
// END HEADER

function Link() {
  Hunkify(this, 'Link')

  // data in/out
  // assumed that whatever is on the other end of bytes
  // does byte / packet level flowcontrol

  // links can be assumed to have data ports, these ferry byteArrays always ...
  this.inputs.data = new Input('byteArray', 'data')
  this.outputs.data = new Output('byteArray', 'data')

  // default, 0th ip and op are messages for managers
  this.state.inputList = new State('string', 'inputList', "msgs (byteArray)")
  this.state.outputList = new State('string', 'outputList', "msgs (byteArray)")

  // ip and op,
  let inports = [this.inputs.zero, this.inputs.one, this.inputs.two]
  let outports = [this.outputs.zero, this.outputs.one]
  
  // ok, on init look at state list

  // these are *special link inputs* keeping track of downstream status
  this.inputs.zero = new Input('any', 'zero')
  this.inputs.zero.dss = 'open'

  this.inputs.one = new Input('any', 'one')
  this.inputs.zero.dss = 'open'

  this.inputs.two = new Input('any', 'two')
  this.inputs.two.dss = 'open'


  // special outputs having upstream buffers
  this.outputs.zero = new Output('any', 'zero')
  this.outputs.zero.hold = {}
  this.outputs.zero.hold.status = 'open'
  this.outputs.zero.hold.msg = {}

  this.outputs.one = new Output('any', 'one')
  this.outputs.one.hold = {}
  this.outputs.one.hold.status = 'open'
  this.outputs.one.hold.msg = {}

  this.init = () => {
    // manager calls this once
    // it is loaded and state is updated (from program)
    this.log('hello Link')
    // HERE write those inputs via that list
  }

  // so far we won't ack at the link layer,
  // i.e. we will assume that we can send all ports across,
  // without getting an ack back from the link
  // but we will need to do this for the dmarippers
  // perhaps that should live in the layer that 'websocket' is at now


  let outbuffer = new Array()

  // I think we just need to buffer
  // the outputs that we pull but can't send

  /*
  let msg = {
      msg: content,
      port: portnum,
      isAck: false
  }
  // or
  let msg = {
      port: portnum,
      isAck: true
  }
  */

  // the link level will flow control across bytes,
  // so we just flow control across ports

  this.loop = () => {
    // for everything we're holding, check our outputs
    for (let i in outports) {
      if (outports[i].hold.status === 'occupied' && outports[i].ie) {
        // gr8 news, we can ship it
        outports[i].put(outports.hold.msg)
        outports[i].hold.status = 'clear'
        let ack = {
          isAck: true,
          port: i
        }
        outbuffer.push(ack)
      } else {
        // we can't do anything, waiting for outside world
      }
    }

    // then check for messages from the data link
    if (this.inputs.data.io()) {
      // we pull every time
      let msg = this.inputs.data.get()
      // if it's an ack, we can clear an input
      if (msg.isAck) {
        if (inports[msg.port].dss !== 'await ack') {
          console.log("LINK ERROR: ACK FROM NON WAIT")
          throw new Error('link panic', msg)
        } else {
          inports[msg.port].dss = 'open'
        }
      } else {
        // otherwise we have a message for one of our outputs
        let dsport = outports[msg.port]
        // if we have one
        if (dsport !== null && dsport !== undefined) {
          if (dsport.hold.status === 'occupied') {
            // bad news, we are already waiting to send
            // but we have this new thing, so
            console.log('LINK ERROR: 2ND MSG TO NON ACKED PORT')
            throw new Error('link panic', msg)
          } else if (dsport.ie) {
            // this is easy, we can just ship it
            dsport.put(msg.msg)
            let ack = {
              isAck: true,
              port: msg.port
            }
            outbuffer.push(ack)
          } else {
            // well, we already pulled it off stream, so
            dsport.hold.status = 'occupied'
            dsport.hold.msg = msg.msg
            // store it locally, but don't ack
            // so we should not get another message on this port until we ack ...
            // if we do, the 1st if statement in this block will be triggered
          }
        } else {
          console.log('LINK ERROR: RECEIVES MESSAGE FOR PORT IT DOTH NOT HAVE')
          console.log(msg.port, typeof msg.port)
          // TODO: make one, and report to manager that we have done so
        }
      } // end message-not-ack
    } // end if input has bytes

    // now let's run over our inputs,
    for (let i in inports) {
      // if there's a message on the input, and the downstream is clear,
      if (inports[i].io() && inports[i].dss === 'open') {
        // we can send it, and reset to await an ack
        this.log('i', typeof i)
        let dsmsg = {
          msg: inports[i].get(),
          port: parseInt(i),
          isAck: false,
        }
        this.log('LNK MSG OUT', dsmsg)
        inports[i].dss = 'await ack'
        outbuffer.push(dsmsg)
      } else {
        // otherwise, there's nothing we can do but let it sit there
      }
    }

    // flow control outgoing messages
    // one per loop !
    if (this.outputs.data.ie && outbuffer.length > 0) {
      this.outputs.data.put(outbuffer.shift())
    }
  } // end loop
}

// FOOTER
export default Link
// END FOOTER