'use strict'
var ethJSABI = require('ethereumjs-abi')
var txHelper = require('./txHelper')

/**
  * Register to txListener and extract events
  *
  */
class EventsDecoder {
  constructor (opt = {}) {
    this._api = opt.api
  }

/**
  * use Transaction Receipt to decode logs. assume that the transaction as already been resolved by txListener.
  * logs are decoded only if the contract if known by remix.
  *
  * @param {Object} tx - transaction object
  * @param {Function} cb - callback
  */
  parseLogs (tx, contractName, compiledContracts, cb) {
    if (tx.isCall) return cb(null, { decoded: [], raw: [] })
    this._api.resolveReceipt(tx, (error, receipt) => {
      if (error) return cb(error)
      this._decodeLogs(tx, receipt, contractName, compiledContracts, cb)
    })
  }

  _decodeLogs (tx, receipt, contract, contracts, cb) {
    if (!contract || !receipt) {
      return cb('cannot decode logs - contract or receipt not resolved ')
    }
    if (!receipt.logs) {
      return cb(null, { decoded: [], raw: [] })
    }
    this._decodeEvents(tx, receipt.logs, contract, contracts, cb)
  }

  _eventABI (contract) {
    var eventABI = {}
    contract.abi.forEach(function (funABI, i) {
      if (funABI.type !== 'event') {
        return
      }
      var hash = ethJSABI.eventID(funABI.name, funABI.inputs.map(function (item) { return item.type }))
      eventABI[hash.toString('hex')] = { event: funABI.name, inputs: funABI.inputs }
    })
    return eventABI
  }

  _eventsABI (compiledContracts) {
    var eventsABI = {}
    txHelper.visitContracts(compiledContracts, (contract) => {
      eventsABI[contract.name] = this._eventABI(contract.object)
    })
    return eventsABI
  }

  _event (hash, eventsABI) {
    for (var k in eventsABI) {
      if (eventsABI[k][hash]) {
        return eventsABI[k][hash]
      }
    }
    return null
  }

  _decodeEvents (tx, logs, contractName, compiledContracts, cb) {
    var eventsABI = this._eventsABI(compiledContracts)
    var events = []
    for (var i in logs) {
      // [address, topics, mem]
      var log = logs[i]
      var topicId = log.topics[0]
      var abi = this._event(topicId.replace('0x', ''), eventsABI)
      if (abi) {
        var event
        try {
          var decoded = new Array(abi.inputs.length)
          event = abi.event
          var indexed = 1
          var nonindexed = []
          // decode indexed param
          abi.inputs.map(function (item, index) {
            if (item.indexed) {
              var encodedData = log.topics[indexed].replace('0x', '')
              try {
                decoded[index] = ethJSABI.rawDecode([item.type], new Buffer(encodedData, 'hex'))[0]
                if (typeof decoded[index] !== 'string') {
                  decoded[index] = ethJSABI.stringify([item.type], decoded[index])
                }
              } catch (e) {
                decoded[index] = encodedData
              }
              indexed++
            } else {
              nonindexed.push(item.type)
            }
          })
          // decode non indexed param
          var nonindexededResult = ethJSABI.rawDecode(nonindexed, new Buffer(log.data.replace('0x', ''), 'hex'))
          nonindexed = ethJSABI.stringify(nonindexed, nonindexededResult)
          // ordering
          var j = 0
          abi.inputs.map(function (item, index) {
            if (!item.indexed) {
              decoded[index] = nonindexed[j]
              j++
            }
          })
        } catch (e) {
          decoded = log.data
        }
        events.push({ topic: topicId, event: event, args: decoded })
      } else {
        events.push({ data: log.data, topics: log.topics })
      }
    }
    cb(null, { decoded: events, raw: logs })
  }
}

module.exports = EventsDecoder
