'use strict'

const debug = require('debug')('client')
const Transport = require('./transport.js')

const protobuf = require('protobufjs')

//TODO may not be suitable for production
const root = protobuf.loadSync('./doc/apollo.proto')

function type(typeName) {
  return root.lookup(typeName)
}

class Client {
  constructor(connectParams, credentials) {
    this.transport = new Transport(connectParams, credentials)
    this.tid = 0
    this.messageMap = new Map()
  }

  handleIncomingMessage(message) {
    debug('Incoming', message)
    this.parse(message)
  }

  waitForMessageWithId(tId, timeout=1000) {
    return new Promise((resolve, reject) => {
      this.messageMap.set(tId, data => {
        resolve(data)
      })
      setTimeout(() => {
        this.messageMap.delete(tId)
        reject(new Error('Timeout waiting for message with id', tId))
      }, timeout)
    })
  }

  parse(buf) {
    if (buf.length < 6) {
      debug('Invalid incoming buf', buf)
      return
    }

    let len = buf.readUInt32BE(0) - 2
    if (len < 0) {
      debug('Invalid incoming buf len', len)
      return
    }
    //const cid = buf.readUInt16BE(4) //TODO not used
    const data = buf.slice(6)

    //TODO not checking buf size at the moment
    const pdu = type('ApolloPdu').decode(data)

    if (pdu) {
      debug('Incoming message', pdu)
      const waitCb = this.messageMap.get(pdu.transactionId)
      if (waitCb) {
        waitCb(pdu)
      } else {
        debug('Discarding incoming message with tId', pdu.transactionId)
      }
    }
  }

  writePdu(_data) {
    const data = type('ApolloPdu').encode(_data).finish()
    //TODO what is cid?
    const cId = 0

    debug('Decode before sending', type('ApolloPdu').decode(data))

    const header = Buffer.alloc(6)
    header.writeUInt32BE(data.length + 2, 0)
    header.writeUInt16BE(cId, 4)
    debug('Header', header, data.length)

    const buf = Buffer.concat([header, data])

    debug('Sending buffer', buf)
    return this.transport.send(buf).then(() => this.waitForMessageWithId(_data.transactionId))
  }

  connect() {
    return this.transport.connect(this.handleIncomingMessage.bind(this))
    .then(() => debug('Connection is successful!'))
  }

  //procedure should be provided in an object ex: {createTable: {...}}
  _makePdu(procedure) {
    const ApolloPdu = type('ApolloPdu')

    const pdu = ApolloPdu.create({
      version: {major: 0, minor: 1},
      transactionId: this.tid++
    })

    Object.assign(pdu, procedure)

    const res = ApolloPdu.verify(pdu)
    if (res) {
      throw new Error(res)
    }

    return pdu
  }

  tableInfo(tableName, attributes) {
    const pdu = this._makePdu({tableInfo: {tableName, attributes}})
    return this.writePdu(pdu)
  }

  createTable(tableName, keys) {

    const tableOptions = [type('TableOption').create({type: 0})]

    const pdu = this._makePdu({createTable: {tableName, keys, tableOptions}})
    return this.writePdu(pdu)
  }

}

module.exports = Client

if (require.main === module) {
  //TODO passing constructor params
  const client = new Client({
    host: 'localhost',
    port: '32768',
    rejectUnauthorized: false
  }, {
    username: 'admin',
    password: 'admin'
  })

  client.connect().then(() => {
    return client.createTable('dodo-table15', ['dodo-key'])
  })
  .then(() => {
    return client.tableInfo('dodo-table15', ['type', 'key'])
  })
}

@@ -2776,17 +2776,16 @@
 keys) %7B%0A
-%0A
     cons
@@ -2955,16 +2955,249 @@
 u)%0A  %7D%0A%0A
+  openTable(tableName) %7B%0A    const pdu = this._makePdu(%7BopenTable: %7BtableName%7D%7D)%0A    return this.writePdu(pdu)%0A  %7D%0A%0A  closeTable(tableName) %7B%0A    const pdu = this._makePdu(%7BcloseTable: %7BtableName%7D%7D)%0A    return this.writePdu(pdu)%0A  %7D%0A
 %7D%0A%0Amodul
@@ -3450,47 +3450,124 @@
 %0A  c
-lient.connect().then(() =%3E %7B%0A    return
+onst co = require('co')%0A  co(function*() %7B%0A    const tableName = 'dodo-table16'%0A    yield client.connect()%0A    yield
  cli
@@ -3582,30 +3582,25 @@
 teTable(
-'dodo-
 table
-15'
+Name
 , %5B'dodo
@@ -3613,37 +3613,15 @@
 )%0A  
-%7D)%0A  .then(() =%3E %7B%0A    return
+  yield
  cli
@@ -3638,22 +3638,17 @@
 nfo(
-'dodo-
 table
-15'
+Name
 , %5B'
@@ -3662,15 +3662,92 @@
 'key'%5D)%0A
+    yield client.openTable(tableName)%0A    yield client.closeTable(tableName)%0A
   %7D)%0A%7D%0A

Add open/close table