// -----------------------
// TypeScript [2.6.2]
// USB Library for Node.JS: https://github.com/tessel/node-usb
// -----------------------
import * as USB from 'usb'
import MSeriesReceiverParser from './mSeriesReceiverParser'

const TRANS_ERROR_THRESHOLD = 10

export default class Device {
  bulkInEndpoint: USB.InEndpoint | null
  bulkInEndpointTestCount: number
  callback: ((broadcast: Broadcast) => void)
  coreDevice: USB.Device

  constructor (device: USB.Device, callback: ((broadcast: Broadcast) => void)) {
    this.bulkInEndpoint = null
    this.bulkInEndpointTestCount = 0
    this.callback = callback
    this.coreDevice = device
    this.initialize()
  }

  /*****************************************
  *  Exposed Controls
  *****************************************/

  isSameDevice (device: USB.Device) {
    return device.busNumber === this.coreDevice.busNumber &&
      device.deviceAddress === this.coreDevice.deviceAddress
  }

  async disconnect () {
    try {
      await this.stopTransfer()
      this.coreDevice.close()
      if (process.env.NODE_ENV !== 'production') {
        console.log('Device closed')
      }
    } catch (error) {
      if (process.env.NODE_ENV !== 'production') {
        console.error('Error closing device\n', error)
      }
    }
  }

  /*****************************************
  *  Control Flow
  *****************************************/

  async initialize () {
    this.coreDevice.open()
    this.mapEndpoints()
    this.startTransfer()
  }

  /*****************************************
  *  Interface and Endpoints
  *****************************************/

  mapEndpoints () {
    let inf = this.coreDevice.interfaces.find(i => i.endpoints.length === 2)
    if (!inf) {
      throw new Error('Device interfaces unavailable')
    }
    inf.claim()
    this.bulkInEndpoint = inf.endpoints.find(e => e.direction === 'in' && e.transferType === USB.LIBUSB_TRANSFER_TYPE_BULK) as USB.InEndpoint
  }

  /*****************************************
  *  Transfer
  *****************************************/

  startTransfer () {
    if (!this.bulkInEndpoint) {
      throw new Error('Device endpoint not assigned')
    }
    this.bulkInEndpoint.timeout = 50
    this.bulkInEndpointTestCount = 0
    this.bulkInEndpoint.on('data', (data: Buffer) => {
      if (data.length > 0) {
        this.parse(data)
        if (this.bulkInEndpointTestCount < TRANS_ERROR_THRESHOLD) {
          this.bulkInEndpointTestCount++
        }
      }
    })

    this.bulkInEndpoint.on('error', (error) => {
      if (process.env.NODE_ENV !== 'production') {
        console.error('Device error\n', error)
      }
    })

    this.bulkInEndpoint.startPoll(0, 4096)

    setTimeout(() => { this.checkTransfer() }, 5000)
  }

  async stopTransfer () {
    return new Promise((resolve, reject) => {
      if (!this.bulkInEndpoint) {
        return reject(Error('Device endpoint not assigned'))
      }

      this.bulkInEndpoint.stopPoll(() => {
        resolve()
      })
    })
  }

  checkTransfer () {
    if (!this.bulkInEndpoint) {
      throw new Error('Device endpoint not assigned')
    }

    if (this.coreDevice && this.bulkInEndpointTestCount < TRANS_ERROR_THRESHOLD) {
      if (process.env.NODE_ENV !== 'production') {
        console.error('Device not transmitting data')
      }
    }
  }

  /*****************************************
  *  Parse
  *****************************************/

  parse (data: Buffer) {
    let rawStream = new Uint8Array(data)
    let segments = String.fromCharCode.apply(null, rawStream).split(' ')
    let firstSegmentValue = parseInt(segments[0], 10)
    if (firstSegmentValue <= 200) {
      let swValue = parseFloat(segments[6])
      if (swValue >= 6.0 && swValue < 7.0) {
        try {
          let broadcast = MSeriesReceiverParser(segments)
          this.callback(broadcast)
        } catch (error) {
          console.error('M Series parse error\n', error)
        }
      } else if (swValue >= 8.0 && swValue < 9.0) {
        console.error('Incorrect device connected')
      }
    }
  }
}