package main

import (
	"fmt"
	"math"

	"gitlab.imt-atlantique.fr/xaal/code/go/core/schemas"
	"gitlab.imt-atlantique.fr/xaal/code/go/core/uuid"
	"gitlab.imt-atlantique.fr/xaal/code/go/core/xaal"
)

type XAALDevice struct {
	*xaal.Device
}

type Contact struct {
	XAALDevice
}

type Thermometer struct {
	XAALDevice
}

type Hygrometer struct {
	XAALDevice
}

type LinkQuality struct {
	XAALDevice
}

type Battery struct {
	XAALDevice
}

type PowerRelay struct {
	XAALDevice
	Property  string
	Z2MDevice Z2MDevice
}

type PowerMeter struct {
	XAALDevice
}

type Lamp struct {
	XAALDevice
	Z2MDevice Z2MDevice
}

type XAALDeviceInterface interface {
	update(map[string]interface{})
	getXAALDevice() *xaal.Device
}

func (dev *XAALDevice) setup(zDev *Z2MDevice) {
	dev.VendorID = zDev.Definition.Vendor
	dev.ProductID = zDev.Definition.Model
	dev.HWID = zDev.IeeeAddress
	dev.Version = zDev.SwBuildID
	dev.Info = "z2m:" + zDev.Type + ":" + zDev.FriendlyName
}

func (dev *XAALDevice) getXAALDevice() *xaal.Device {
	return dev.Device
}

// ----------------------------------------------------------------------------
// Contact
// ----------------------------------------------------------------------------
func NewContact(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &Contact{XAALDevice{schemas.NewContact(addr)}}
	dev.setup(zDev)
	return dev
}

func (dev *Contact) update(payload map[string]interface{}) {
	value, exists := payload["contact"].(bool)
	if exists {
		dev.GetAttribute("detected").SetValue(!value)
	}
}

// ----------------------------------------------------------------------------
// Thermometer
// ----------------------------------------------------------------------------
func NewThermometer(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &Thermometer{XAALDevice{schemas.NewThermometer(addr)}}
	dev.setup(zDev)
	return dev
}

func (dev *Thermometer) update(payload map[string]interface{}) {
	value, exists := payload["temperature"].(float64)
	if exists {
		dev.GetAttribute("temperature").SetValue(value)
	}
}

// ----------------------------------------------------------------------------
// Hygrometer
// ----------------------------------------------------------------------------
func NewHygrometer(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &Hygrometer{XAALDevice{schemas.NewHygrometer(addr)}}
	dev.setup(zDev)
	return dev
}

func (dev *Hygrometer) update(payload map[string]interface{}) {
	value, exists := payload["humidity"].(float64)
	if exists {
		dev.GetAttribute("humidity").SetValue(value)
	}
}

// ----------------------------------------------------------------------------
// LinkQuality
// ----------------------------------------------------------------------------
func NewLinkQuality(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &LinkQuality{XAALDevice{schemas.NewLinkquality(addr)}}
	dev.setup(zDev)
	dev.RemoveAttribute("devices")
	dev.UnsupportedAttributes = []string{"devices"}
	return dev
}

func (dev *LinkQuality) update(payload map[string]interface{}) {
	value, exists := payload["linkquality"].(float64)
	if exists {
		value = math.Round(value / 255 * 100)
		dev.GetAttribute("level").SetValue(value)
	}
}

// ----------------------------------------------------------------------------
// Battery
// ----------------------------------------------------------------------------
func NewBattery(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &Battery{XAALDevice{schemas.NewBattery(addr)}}
	dev.setup(zDev)
	dev.RemoveAttribute("devices")
	dev.UnsupportedAttributes = []string{"devices"}
	return dev
}

func (dev *Battery) update(payload map[string]interface{}) {
	value, exists := payload["battery"].(float64)
	if exists {
		dev.GetAttribute("level").SetValue(value)
	}
}

// ----------------------------------------------------------------------------
// PowerRelay
// ----------------------------------------------------------------------------
func NewPowerRelay(addr uuid.UUID, zDev *Z2MDevice, property string) XAALDeviceInterface {
	dev := &PowerRelay{XAALDevice{schemas.NewPowerrelayToggle(addr)}, property, *zDev}
	dev.SetMethod("turn_on", dev.On)
	dev.SetMethod("turn_off", dev.Off)
	dev.SetMethod("toggle", dev.Toggle)
	dev.setup(zDev)
	return dev
}

func (dev *PowerRelay) update(payload map[string]interface{}) {
	value, exists := payload[dev.Property].(string)
	if exists {
		power := dev.GetAttribute("power")
		switch value {
		case "ON":
			power.SetValue(true)
		case "OFF":
			power.SetValue(false)
		}
	}
}

func (dev *PowerRelay) SetProperty(value string) {
	body := fmt.Sprintf(`{"%s": "%s"}`, dev.Property, value)
	dev.Z2MDevice.Set(body)
}

func (dev *PowerRelay) On(xaal.MessageBody) *xaal.MessageBody {
	dev.SetProperty("ON")
	return nil
}

func (dev *PowerRelay) Off(xaal.MessageBody) *xaal.MessageBody {
	dev.SetProperty("OFF")
	return nil
}

func (dev *PowerRelay) Toggle(xaal.MessageBody) *xaal.MessageBody {
	dev.SetProperty("TOGGLE")
	return nil
}

// ----------------------------------------------------------------------------
// PowerMeter
// ----------------------------------------------------------------------------
func NewPowerMeter(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &PowerMeter{XAALDevice{schemas.NewPowermeter(addr)}}
	dev.setup(zDev)
	dev.RemoveAttribute("devices")
	dev.UnsupportedAttributes = []string{"devices"}
	return dev
}

func (dev *PowerMeter) update(payload map[string]interface{}) {
	// power
	value, exists := payload["power"].(float64)
	if exists {
		dev.GetAttribute("power").SetValue(value)
	}
	// energy
	value, exists = payload["energy"].(float64)
	if exists {
		dev.GetAttribute("energy").SetValue(value)
	}
}

// ----------------------------------------------------------------------------
// Lamp
// ----------------------------------------------------------------------------
func NewLamp(addr uuid.UUID, zDev *Z2MDevice) XAALDeviceInterface {
	dev := &Lamp{XAALDevice{schemas.NewLamp(addr)}, *zDev}
	dev.SetMethod("turn_on", dev.On)
	dev.SetMethod("turn_off", dev.Off)
	dev.SetMethod("toggle", dev.Toggle)
	dev.setup(zDev)
	return dev
}

func (dev *Lamp) update(payload map[string]interface{}) {
	value, exists := payload["state"].(string)
	if exists {
		light := dev.GetAttribute("light")
		switch value {
		case "ON":
			light.SetValue(true)
		case "OFF":
			light.SetValue(false)
		}
	}
}

func (dev *Lamp) On(xaal.MessageBody) *xaal.MessageBody {
	dev.Z2MDevice.Set(`{"state": "ON"}`)
	return nil
}

func (dev *Lamp) Off(xaal.MessageBody) *xaal.MessageBody {
	dev.Z2MDevice.Set(`{"state": "OFF"}`)
	return nil
}

func (dev *Lamp) Toggle(xaal.MessageBody) *xaal.MessageBody {
	dev.Z2MDevice.Set(`{"state": "TOGGLE"}`)
	return nil
}