// Modules
var i2c         = require('i2c-bus')
var async       = require('async')
var log         = require('npmlog')
var sleep       = require('sleep')
var fs          = require('fs')
var commands    = require('./commands')

var I2CCMD    = 1
var debugMode = false
var i2c0Path  = '/dev/i2c-0'
var i2c1Path  = '/dev/i2c-1'
var bus
var busNumber

var initWait  = 1     // in seconds

var isInit    = false
var isHalt    = false
var isBusy    = false

var ADDRESS   = 0x04

var onError, onInit

function GrovePi(opts) {
  this.BYTESLEN = 4
  this.INPUT = 'input'
  this.OUTPUT = 'output'

  if (typeof opts == 'undefined')
    opts = {}

  if (typeof opts.debug != 'undefined')
    this.debugMode = opts.debug
  else
    this.debugMode = debugMode

  // TODO: Dispatch an error event instead
  if (typeof opts.onError == 'function')
    onError = opts.onError

  // TODO: Dispatch a init event instead
  if (typeof opts.onInit == 'function')
    onInit = opts.onInit

  if (fs.existsSync(i2c0Path)) {
    isHalt = false
    busNumber = 0
  } else if (fs.existsSync(i2c1Path)) {
    isHalt = false
    busNumber = 1
  } else {
    var err = new Error('GrovePI could not determine your i2c device')
    isHalt = true
    if (typeof onError == 'function')
      onError(err)
    this.debug(err)
  }
}

GrovePi.prototype.init = function() {
  if (!isHalt) {
    bus = i2c.openSync(busNumber)

    if (!isInit) {
      this.debug('GrovePi is initing')
      sleep.sleep(initWait)
      isInit = true

      if (typeof onInit == 'function')
        onInit(true)
    } else {
      var err = new Error('GrovePI is already initialized')
      if (typeof onInit == 'function')
        onInit(false)
      onError(err)
    }
  } else {
    var err = new Error('GrovePI cannot be initialized')
    if (typeof onInit == 'function')
      onInit(false)
    onError(err)
  }
}
GrovePi.prototype.close = function() {
  if (typeof bus != 'undefined') {
    this.debug('GrovePi is closing')
    bus.closeSync()
  } else {
    this.debug('The device is not defined')
  }
}
GrovePi.prototype.run = function(tasks) {
  this.debug('GrovePi is about to execute ' + tasks.length + ' tasks')
  async.waterfall(tasks)
}
GrovePi.prototype.checkStatus = function() {
  if (!isInit || isHalt){
    if (!isHalt) {
      this.debug('GrovePi needs to be initialized.')
    } else {
      this.debug('GrovePi is not operative because halted')
    }
    return false
  }
  return true
}
GrovePi.prototype.readByte = function() {
  var isOperative = this.checkStatus()
  if (!isOperative)
    return false

  var length = 1
  var buffer = new Buffer.alloc(length)
  var ret = bus.i2cReadSync(ADDRESS, length, buffer)
  return ret > 0 ? buffer : false
}
GrovePi.prototype.readBytes = function(length) {
  if (typeof length == 'undefined')
    length = this.BYTESLEN

  var isOperative = this.checkStatus()
  if (!isOperative)
    return false

  var buffer = new Buffer.alloc(length)
  var ret = false
  try {
    var val = bus.i2cReadSync(ADDRESS, length, buffer)
    ret = val > 0 ? buffer : false
  } catch (err) {
    ret = false
  } finally {
    return ret
  }
}
GrovePi.prototype.writeBytes = function(bytes) {
  var isOperative = this.checkStatus()
  if (!isOperative)
    return false

  var buffer = new Buffer.from(bytes)
  var ret = false
  try {
    var val = bus.i2cWriteSync(ADDRESS, buffer.length, buffer)
    ret = val > 0 ? true : false
  } catch (err) {
    ret = false
  } finally {
    return ret
  }
}
GrovePi.prototype.pinMode = function(pin, mode) {
  var isOperative = this.checkStatus()
  if (!isOperative)
    return false

  if (mode == this.OUTPUT) {
    return this.writeBytes(commands.pMode.concat([pin, 1, commands.unused]))
  } else if (mode == this.INPUT) {
    return this.writeBytes(commands.pMode.concat([pin, 0, commands.unused]))
  } else {
    this.debug('Unknown pin mode')
  }
}
GrovePi.prototype.debug = function(msg) {
  if (this.debugMode)
    log.info('GrovePi.board', msg)
}
GrovePi.prototype.wait = function(ms) {
  sleep.usleep(1000 * ms)
}

// GrovePi functions
GrovePi.prototype.version = function() {
  var write = this.writeBytes(commands.version.concat([commands.unused, commands.unused, commands.unused]))
  if (write) {
    this.wait(100)
    this.readByte()
    var bytes = this.readBytes()
    if (typeof bytes == 'object')
      return (bytes[1] + '.' + bytes[2] + '.' + bytes[3])
    else
      return false
  } else {
    return false
  }
}

// export the class
module.exports = GrovePi
