import {
  Service,
  Formats,
  PlatformAccessory,
  CharacteristicValue,
  CharacteristicSetCallback,
} from 'homebridge'

import { ScreenLogicPlatform, AccessoryAdaptor } from './platform'

export interface ThermostatAccessoryContext {
  displayName: string
  bodyType: number
  minSetPoint: number
  maxSetPoint: number
}

export class ThermostatAccessory {
  static makeAdaptor(): AccessoryAdaptor<ThermostatAccessory> {
    return {
      generateUUID: ThermostatAccessory.generateUUID,
      sameContext: ThermostatAccessory.sameContext,
      factory: function(platform: ScreenLogicPlatform, accessory: PlatformAccessory) {
        return new ThermostatAccessory(platform, accessory)
      },
    }
  }

  static generateUUID(platform: ScreenLogicPlatform, context: ThermostatAccessoryContext): string {
    return platform.generateUUID('switch:' + context.displayName)
  }

  public static sameContext(a: ThermostatAccessoryContext, b: ThermostatAccessoryContext): boolean {
    return (
      a.displayName === b.displayName &&
      a.bodyType === b.bodyType &&
      a.minSetPoint === b.minSetPoint &&
      a.maxSetPoint === b.maxSetPoint
    )
  }

  private service: Service

  constructor(
    private readonly platform: ScreenLogicPlatform,
    private readonly accessory: PlatformAccessory,
  ) {
    // set accessory information
    const accessoryInfo = platform.accessoryInfo()
    this.accessory
      .getService(this.platform.Service.AccessoryInformation)!
      .setCharacteristic(this.platform.Characteristic.Manufacturer, accessoryInfo.manufacturer)
      .setCharacteristic(this.platform.Characteristic.Model, accessoryInfo.model)
      .setCharacteristic(this.platform.Characteristic.SerialNumber, accessoryInfo.serialNumber)

    // get the Thermostat service if it exists, otherwise create a new Thermostat service
    this.service =
      this.accessory.getService(this.platform.Service.Thermostat) ||
      this.accessory.addService(this.platform.Service.Thermostat)

    // set the service name, this is what is displayed as the default name on the Home app
    this.service.setCharacteristic(this.platform.Characteristic.Name, this.context.displayName)

    // register handlers for the TargetTemperature Characteristic
    this.service
      .getCharacteristic(this.platform.Characteristic.TargetTemperature)
      .on('set', this.setTargetTemperature.bind(this))

    // register handlers for the TargetTemperature Characteristic
    this.service
      .getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState)
      .on('set', this.setTargetHeatingCoolingState.bind(this))

    this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature).setProps({
      format: Formats.FLOAT,
      minValue: this.context.minSetPoint,
      maxValue: this.context.maxSetPoint,
    })

    this.service.getCharacteristic(this.platform.Characteristic.CurrentTemperature).setProps({
      format: Formats.FLOAT,
      minValue: -18,
      maxValue: 60,
    })

    this.service.getCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits).setValue(1)

    // trigger refresh if needed when HomeKit asks for this value
    this.platform.triggersRefreshIfNeded(
      this.service,
      this.platform.Characteristic.CurrentTemperature,
    )

    // trigger refresh if needed when HomeKit asks for this value
    this.platform.triggersRefreshIfNeded(
      this.service,
      this.platform.Characteristic.TargetTemperature,
    )
  }

  public get UUID(): string {
    return this.accessory.UUID
  }

  private get context(): ThermostatAccessoryContext {
    return this.accessory.context as ThermostatAccessoryContext
  }

  setTargetTemperature(value: CharacteristicValue, callback: CharacteristicSetCallback) {
    this.platform.log.debug('setTargetTemperature:', value, this.context)
    this.platform.setTargetTemperature(this.context, value as number)
    callback(null, value)
  }

  setTargetHeatingCoolingState(value: CharacteristicValue, callback: CharacteristicSetCallback) {
    this.platform.log.debug('setTargetHeatingCoolingState:', value, this.context)
    this.platform.setTargetHeatingCoolingState(this.context, value as number)
    callback(null, value)
  }

  public updateCurrentTemperature(temperature: number) {
    this.platform.log.debug('updateCurrentTemperature:', temperature, this.context)
    this.service.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, temperature)
  }

  public updateTargetTemperature(temperature: number) {
    // clamp the value
    temperature = Math.min(
      Math.max(this.context.minSetPoint + 0.1, temperature),
      this.context.maxSetPoint - 0.1,
    )
    this.platform.log.debug('updateTargetTemperature:', temperature, this.context)
    this.service.updateCharacteristic(this.platform.Characteristic.TargetTemperature, temperature)
  }

  public updateCurrentHeatingCoolingState(state: number) {
    this.platform.log.debug('updateCurrentHeatingCoolingState:', state, this.context)
    this.service.updateCharacteristic(
      this.platform.Characteristic.CurrentHeatingCoolingState,
      state,
    )
  }

  public updateTargetHeatingCoolingState(state: number) {
    this.platform.log.debug('updateTargetHeatingCoolingState:', state, this.context)
    this.service.updateCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState, state)
  }
}