
# #Sensibo plugin
module.exports = (env) ->

  Promise = env.require 'bluebird'
  types = env.require('decl-api').types
  _ = env.require 'lodash'
  commons = require('pimatic-plugin-commons')(env)
  rest = require('restler-promise')(Promise)
  url = require 'url'
  deviceConfigTemplates =
    "SensiboControl":
      name: "Sensibo"
      class: "SensiboControl"
    "SensiboFanControl":
      name: "Sensibo"
      class: "SensiboFanControl"
    "SensiboSensor":
      name: "Sensibo"
      class: "SensiboSensor"

  # ###SensiboPlugin class
  class SensiboPlugin extends env.plugins.Plugin

    # ####init()
    # The `init` function is called by the framework to ask your plugin to initialise.
    #
    # #####params:
    #  * `app` is the [express] instance the framework is using.
    #  * `framework` the framework itself
    #  * `config` the properties the user specified as config for your plugin in the `plugins`
    #     section of the config.json file
    #
    #
    init: (app, @framework, @config) =>
      # register devices
      deviceConfigDef = require("./device-config-schema")
      @base = commons.base @, 'SensiboPlugin'
      @baseUrl = @config.baseUrl
      @apiKey = @config.apiKey
      @options = {
        timeout: 1000 * @base.normalize @config.timeout ? @config.__proto__.timeout, 5, 86400
      }

      @framework.deviceManager.registerDeviceClass("SensiboSensor", {
        configDef: deviceConfigDef.SensiboSensor,
        createCallback: (config, lastState) =>
          return new SensiboSensor config, @, lastState
      })
      @framework.deviceManager.registerDeviceClass("SensiboControl", {
        configDef: deviceConfigDef.SensiboControl,
        createCallback: (config, lastState) =>
          return new SensiboControl config, @, lastState
      })
      @framework.deviceManager.registerDeviceClass("SensiboFanControl", {
        configDef: deviceConfigDef.SensiboFanControl,
        createCallback: (config, lastState) =>
          return new SensiboFanControl config, @, lastState
      })

      @framework.deviceManager.on('discover', (eventData) =>
        @framework.deviceManager.discoverMessage 'pimatic-sensibo', 'Searching for devices'

        @getPods().then( (podIds) =>
            id=null
            for podUid in podIds
              for own templateName of deviceConfigTemplates
                configTemplate = deviceConfigTemplates[templateName]
                id = @base.generateDeviceId @framework, "sensibo", id
                if id?
                  config = _.cloneDeep
                    class: configTemplate.class
                    id: id
                    name:  "#{configTemplate.name} #{id.substr(1 + id.indexOf '-')}"
                    podUid: podUid

                  @framework.deviceManager.discoveredDevice(
                    'pimatic-sensibo', "#{config.name}", config
                  )
        ).catch (errorMessage) =>
          @framework.deviceManager.discoverMessage 'pimatic-sensibo', errorMessage
      )

    getPods: () ->
      urlObject = url.parse @baseUrl, false, true
      urlObject.pathname = @addToUrlPath urlObject.pathname, "users/me/pods"
      urlObject.query =
        apiKey: "#{@apiKey}"
      @base.info url.format(urlObject).replace(/apiKey=[^&]+/i, "apiKey=XXX");

      rest.get(url.format(urlObject), @options).then((result) =>
        @base.info "response:", result.data
        data = result.data
        if data.status is 'success' and _.isArray data.result
          podIds = []
          for pod in data.result
            podIds.push pod.id || pod.name
          return Promise.resolve podIds
      ).catch (errorResult) =>
        @base.rejectWithErrorString Promise.reject,  errorResult.error ? errorResult, "Unable to query pods"

    addToUrlPath: (baseUrlString, path) ->
      return baseUrlString.replace(/\/$/,"") + '/' + path.replace(/^\//,"")

  class SensiboSensor extends env.devices.TemperatureSensor
    attributes:
      temperature:
        description: "Temperature"
        type: types.number
        unit: '°C'
        acronym: 'T'
      humidity:
        description: "Relative humidity"
        type: types.number
        unit: '%'
        acronym: 'RH'

    constructor: (@config, @plugin, @service) ->
      @debug = @plugin.config.debug ? false
      @base = commons.base @, @config.class unless @base?

      @base.debug "Device Initialization"
      @id = @config.id
      @name = @config.name
      @serviceUrlGet = @_createServiceUrl()
      @base.info @serviceUrlGet.replace(/apiKey=[^&]+/i, "apiKey=XXX");
      @options = @plugin.options
      intervalSeconds = (@config.interval or (@plugin.config.interval ? @plugin.config.__proto__.interval))
      @interval = 1000 * @base.normalize intervalSeconds, 10, 86400
      @_temperature = lastState?.temperature?.value or null
      @_humidity = lastState?.humidity?.value or null
      super()
      @_requestUpdate()

    destroy: () ->
      @base.cancelUpdate()
      super()

    _setHumidity: (value) ->
      @_humidity = value
      @emit 'humidity', value

    _createServiceUrl: ->
      urlObject = url.parse @plugin.baseUrl, false, true
      urlObject.pathname = @plugin.addToUrlPath urlObject.pathname, "pods/#{@config.podUid}/measurements"
      urlObject.query =
        apiKey: "#{@plugin.apiKey}"
        fields: "temperature,humidity"
      return url.format urlObject

    _requestUpdate: ->
      rest.get(@serviceUrlGet, @options).then((result) =>
        @base.info "response:", result.data
        data = result.data
        if data.status is 'success' and _.isArray data.result
          @_setHumidity data.result[0].humidity
          @_setTemperature data.result[0].temperature
      ).catch((errorResult) =>
        @base.error "Unable to get status values of device: ", errorResult.error ? errorResult
      ).finally () =>
        @base.scheduleUpdate @_requestUpdate, @interval

    getHumidity: -> Promise.resolve @_humidity

  class SensiboFanControl extends env.devices.ButtonsDevice

    constructor: (@config, @plugin, lastState) ->
      @id = @config.id
      @name = @config.name
      @base = commons.base @, @config.class
      for b in @config.buttons
        b.text = b.id unless b.text?
      @serviceUrlGet = @_createServiceUrl(
        limit:  1
        fields: "status,reason,acState,limit=10"
      )
      @serviceUrlPost = @_createServiceUrl()
      @options = @plugin.options
      super @config

    destroy: () ->
      super()

    _createServiceUrl: (queryParams) ->
      urlObject = url.parse @plugin.baseUrl, false, true
      urlObject.pathname = @plugin.addToUrlPath urlObject.pathname, "pods/#{@config.podUid}/acStates"
      urlObject.query =
        apiKey: "#{@plugin.apiKey}"
      if queryParams?
        for own k, v of queryParams
          urlObject.query[k] = v
      return url.format urlObject

    _getValues: ->
      new Promise (resolve, reject) =>
        rest.get(@serviceUrlGet, @options).then((result) =>
          @base.info "response:", result.data
          data = result.data
          if data.status is 'success' and _.isArray data.result
            @base.info "data:", data.result
            acState = data.result[0].acState
            resolve(acState)
          else
            reject new Error "Invalid response status: #{data.status}"
        ).catch (errorResult) =>
          reject errorResult.error ? errorResult

    buttonPressed: (buttonId) ->
      for b in @config.buttons
        if b.id is buttonId
          return @_getValues().then (acState) =>
            acState.fanLevel = b.id
            data =
              acState: acState
            rest.postJson(@serviceUrlPost, data, @options).then((result) =>
              @base.debug "post response:", result.data
              @_lastPressedButton = b.id
              @emit 'button', b.id
              Promise.resolve()
            ).catch((errorResult) =>
              @base.error "Unable to change status values of device: " + errorResult.error ? errorResult, errorResult.response || ''
              Promise.reject(errorResult.error ? errorResult)
            )

      throw new Error("No button with the id #{buttonId} found")

  class SensiboControl extends env.devices.PowerSwitch
    attributes:
      state:
        description: "Current State"
        type: types.boolean
        labels: ['on', 'off']
      targetTemperature:
        description: "Target Temperature"
        type: types.number
        unit: '°C'
        acronym: 'Tt'
      fanLevel:
        description: "The current fan level"
        type: types.string
        acronym: "fan"
      mode:
        description: "The current mode of operation"
        type: types.string
        acronym: "mode"

    # Initialize device by reading entity definition from middleware
    constructor: (@config, @plugin, @service) ->
      @debug = @plugin.config.debug ? false
      @base = commons.base @, @config.class unless @base?

      @base.debug "Device Initialization"
      @id = @config.id
      @name = @config.name
      @serviceUrlGet = @_createServiceUrl(
        limit:  1
        fields: "status,reason,acState,limit=10"
      )
      @serviceUrlPost = @_createServiceUrl()
      @base.debug @serviceUrlGet.replace(/apiKey=[^&]+/i, "apiKey=XXX");
      @options = @plugin.options
      intervalSeconds = (@config.interval or (@plugin.config.interval ? @plugin.config.__proto__.interval))
      @interval = 1000 * @base.normalize intervalSeconds, 10, 86400
      @_fanLevel = lastState?.fanLevel?.value or 'low'
      @_mode = lastState?.mode?.value or 'fan'
      @_targetTemperature = lastState?.targetTemperature?.value or 20.0
      super()
      @_requestUpdate()

    destroy: () ->
      @base.cancelUpdate()
      super()

    _createServiceUrl: (queryParams) ->
      urlObject = url.parse @plugin.baseUrl, false, true
      urlObject.pathname = @plugin.addToUrlPath urlObject.pathname, "pods/#{@config.podUid}/acStates"
      urlObject.query =
        apiKey: "#{@plugin.apiKey}"
      if queryParams?
        for own k, v of queryParams
          urlObject.query[k] = v
      return url.format urlObject

    _getValues: ->
      new Promise (resolve, reject) =>
        rest.get(@serviceUrlGet, @options).then((result) =>
          @base.debug "response:", result.data
          data = result.data
          if data.status is 'success' and _.isArray data.result
            @base.debug "data:", data.result
            acState = data.result[0].acState
            @base.setAttribute "fanLevel", acState.fanLevel
            @base.setAttribute "mode", acState.mode
            @base.setAttribute "targetTemperature", acState.targetTemperature
            @_setState acState.on
            resolve()
          else
            reject new Error "Invalid response status: #{data.status}"
        ).catch (errorResult) =>
          reject errorResult.error ? errorResult

    _requestUpdate: ->
      @_getValues().catch((error) =>
        @base.error "Unable to get status values of device: " + error
      ).finally () =>
        @base.scheduleUpdate @_requestUpdate, @interval

    getFanLevel: -> Promise.resolve @_fanLevel
    getMode: -> Promise.resolve @_mode
    getTargetTemperature: -> Promise.resolve @_targetTemperature
    changeStateTo: (newState) ->
      @_getValues().then =>
        data =
          acState:
            on: newState
            mode: @_mode
            fanLevel: @_fanLevel
            targetTemperature: @_targetTemperature
        rest.postJson(@serviceUrlPost, data, @options).then((result) =>
          @base.debug "post response:", result.data
          @_setState newState
          Promise.resolve()
        ).catch((errorResult) =>
          @base.error "Unable to change status values of device: " + errorResult.error ? errorResult, errorResult.response || ''
          Promise.reject(errorResult.error ? errorResult)
        )

  # ###Finally
  # Create a instance of my plugin
  myPlugin = new SensiboPlugin()
  # and return it to the framework.
  return myPlugin
