text: 'use strict'
  
  const Adapter = require.main.require('hubot/src/adapter')
  const Response = require.main.require('hubot/src/response')
  const { TextMessage, EnterMessage, LeaveMessage } = require.main.require('hubot/src/message')
  const { driver, api, methodCache, settings } = require('@rocket.chat/sdk')
  
  /** Extend default response with custom adapter methods */
  class RocketChatResponse extends Response {
    sendDirect (...strings) {
      this.robot.adapter.sendDirect(this.envelope, ...strings)
    }
    sendPrivate (...strings) {
      this.robot.adapter.sendDirect(this.envelope, ...strings)
    }
  }
  
  /** Define new message type for handling attachments */
  class AttachmentMessage extends TextMessage {
    constructor (user, attachment, text, id) {
      super(user, text, id)
      this.user = user
      this.attachment = attachment
      this.text = text
      this.id = id
    }
    toString () {
      return this.text
    }
  }
  
  /** Main API for Hubot on Rocket.Chat */
  class RocketChatBotAdapter extends Adapter {
    run () {
      this.robot.logger.info(`[startup] Rocket.Chat adapter in use`)
  
      // Make SDK modules available to scripts, via `adapter.`
      this.driver = driver
      this.methodCache = methodCache
      this.api = api
      this.settings = settings
  
      // Print logs with current configs
      this.robot.logger.info(`[startup] Respond to name: ${this.robot.name}`)
      this.robot.alias = (this.robot.name === settings.username || this.robot.alias)
        ? this.robot.alias
        : settings.username
      if (this.robot.alias) {
        this.robot.logger.info(`[startup] Respond to alias: ${this.robot.alias}`)
      }
  
      // Overwrite Robot's response class with Rocket.Chat custom one
      this.robot.Response = RocketChatResponse
  
      // Use RocketChat Bot Driver to connect, login and setup subscriptions
      // Joins single or array of rooms by name from room setting (comma separated)
      // Reactive message subscription uses callback to process every stream update
      driver.useLog(this.robot.logger)
      driver.connect()
        .catch((err) => {
          this.robot.logger.error(this.robot.logger.error(`Unable to connect: ${JSON.stringify(err)}`))
          throw err
        })
        .then(() => {
          return driver.login()
        })
        .catch((err) => {
          this.robot.logger.error(this.robot.logger.error(`Unable to login: ${JSON.stringify(err)}`))
          throw err
        })
        .then(() => {
          return driver.subscribeToMessages()
        })
        .catch((err) => {
          this.robot.logger.error(`Unable to subscribe ${JSON.stringify(err)}`)
          throw err
        })
        .then(() => {
          driver.respondToMessages(this.process.bind(this)) // reactive callback
          this.emit('connected') // tells hubot to load scripts
        })
    }
  
    /** Process every incoming message in subscription */
    async process (err, message, meta) {
      if (err) throw err
      // Prepare message type for Hubot to receive...
      this.robot.logger.info('Filters passed, will receive message')
  
      // Collect required attributes from message meta
      const isDM = (meta.roomType === 'd')
      const isLC = (meta.roomType === 'l')
      const user = this.robot.brain.userForId(message.u._id, {
        name: message.u.username,
        alias: message.alias
      })
      user.roomID = message.rid
      user.roomType = meta.roomType
      user.room = meta.roomName || message.rid
  
      // Room joins, receive without further detail
      if (message.t === 'uj') {
        this.robot.logger.debug('Message type EnterMessage')
        return this.robot.receive(new EnterMessage(user, null, message._id))
      }
  
      if ('au' === message.t) {
        this.robot.logger.debug('Message type EnterMessage', message)
        const joiningUser = await this.getUserByUsername(message.msg);
        joiningUser.roomID = message.rid
        joiningUser.roomType = meta.roomType
        joiningUser.room = meta.roomName || message.rid
        return this.robot.receive(new EnterMessage(joiningUser, null, message._id))
      }
  
      // Room exit, receive without further detail
      if (message.t === 'ul') {
        this.robot.logger.debug('Message type LeaveMessage')
        return this.robot.receive(new LeaveMessage(user, null, message._id))
      }
  
      if ('ru' === message.t) {
        this.robot.logger.debug('Message type LeaveMessage', message)
        const joiningUser = await this.getUserByUsername(message.msg);
        joiningUser.roomID = message.rid
        joiningUser.roomType = meta.roomType
        joiningUser.room = meta.roomName || message.rid
        return this.robot.receive(new LeaveMessage(joiningUser, null, message._id))
      }
  
      // Direct messages prepend bot's name so Hubot can `.respond`
      const startOfText = (message.msg.indexOf('@') === 0) ? 1 : 0
      const robotIsNamed = message.msg.indexOf(this.robot.name) === startOfText || message.msg.indexOf(this.robot.alias) === startOfText
      if ((isDM || isLC) && !robotIsNamed) message.msg = `${this.robot.name} ${message.msg}`
  
      // Attachments, format properties for Hubot
      if (Array.isArray(message.attachments) && message.attachments.length) {
        let attachment = message.attachments[0]
        if (attachment.image_url) {
          attachment.link = `${settings.host}${attachment.image_url}`
          attachment.type = 'image'
        } else if (attachment.audio_url) {
          attachment.link = `${settings.host}${attachment.audio_url}`
          attachment.type = 'audio'
        } else if (attachment.video_url) {
          attachment.link = `${settings.host}${attachment.video_url}`
          attachment.type = 'video'
        }
        this.robot.logger.debug('Message type AttachmentMessage')
        return this.robot.receive(new AttachmentMessage(user, attachment, message.msg, message._id))
      }
  
      // Standard text messages, receive as is
      let textMessage = new TextMessage(user, message.msg, message._id)
      this.robot.logger.debug(`TextMessage: ${textMessage.toString()}`)
      return this.robot.receive(textMessage)
    }
  
    /** Send messages to user addressed in envelope */
    send (envelope, ...strings) {
      return strings.map((text) => {
        if (envelope.user && envelope.user.roomID) driver.sendToRoomId(text, envelope.user.roomID)
        else driver.sendToRoom(text, envelope.room)
      })
    }
  
    /**
     * Emote message to user
     * @todo Improve this legacy method
     */
    emote (envelope, ...strings) {
      return strings.map((text) => this.send(envelope, `_${text}_`))
    }
  
    /** Send custom message to user */
    customMessage (data) {
      return driver.sendMessage(data)
    }
  
    /** Send DM to user */
    sendDirect (envelope, ...strings) {
      return strings.map((text) => driver.sendDirectToUser(text, envelope.user.name))
    }
  
    /** Reply to a user's message (mention them if not a DM) */
    reply (envelope, ...strings) {
      if (envelope.room.indexOf(envelope.user.id) === -1) {
        strings = strings.map((s) => `@${envelope.user.name} ${s}`)
      }
      return this.send(envelope, ...strings)
    }
  
    /** Get a room ID via driver */
    getRoomId (room) {
      return driver.getRoomId(room)
    }
  
    /** Call a server message via driver */
    callMethod (method, ...args) {
      return driver.callMethod(method, args)
    }
  
    async getUserByUsername(username) {
      const response = await this.api.get('users.info', { username })
      if (!response.success) throw new Error('user info response unsucessful.')
      return response.user;
    }
  }
  
  exports.use = (robot) => new RocketChatBotAdapter(robot)
  