package productconfig

import (
	"encoding/json"
	"errors"
	"fmt"
	"sparrow/pkg/protocol"
	"sparrow/pkg/tlv"
)

// CommandOrEventParam command or a event param struct
type CommandOrEventParam struct {
	ValueType int32 `json:"value_type"`
	Name      string
}

// ProductCommandOrEvent “
type ProductCommandOrEvent struct {
	No       int
	Part     int
	Name     string
	Priority int
	Params   []CommandOrEventParam
}

// StatusParam 上报类型的参数
type StatusParam struct {
	ValueType int32  `json:"value_type"`
	Name      string `json:"name"`
	ID        uint   `json:"pid"` //protocal id 与后台对应
}

// ProductObject 产品对象
type ProductObject struct {
	Id     int
	No     int
	Label  string
	Part   int
	Status []StatusParam
}

// ProductConfig product config parses the JSON product config string.
type ProductConfig struct {
	Objects  []ProductObject         `json:"objects"`
	Commands []ProductCommandOrEvent `json:"commands"`
	Events   []ProductCommandOrEvent `json:"events"`
}

// New create a new productConfig
func New(config string) (*ProductConfig, error) {
	v := &ProductConfig{}
	err := json.Unmarshal([]byte(config), v)
	if err != nil {
		return nil, err
	}
	return v, nil
}

// ValidateStatus validate the status
func (config *ProductConfig) ValidateStatus(label string, params []interface{}) (*ProductObject, []interface{}, error) {
	// search for status name
	var paramInfo []StatusParam
	var status *ProductObject
	found := false
	for _, obj := range config.Objects {
		if obj.Label == label {
			paramInfo = obj.Status
			status = &obj
			found = true
			break
		}
	}
	if found == false {
		return nil, []interface{}{}, errors.New("object not found.")
	}
	if len(paramInfo) != len(params) {
		return nil, []interface{}{}, errors.New("wrong status parameters.")
	}
	realParams := make([]interface{}, len(params))
	for idx, para := range paramInfo {
		realParams[idx] = tlv.CastTLV(params[idx], para.ValueType)
	}
	return status, realParams, nil
}

// ValidateCommandOrEvent validate command or event
func (config *ProductConfig) ValidateCommandOrEvent(name string, params []interface{}, typ string) (*ProductCommandOrEvent, []interface{}, error) {
	var target []ProductCommandOrEvent
	if typ == "command" {
		target = config.Commands
	} else if typ == "event" {
		target = config.Events
	} else {
		return nil, []interface{}{}, errors.New("wrong target type")
	}

	// search for name
	var paramInfo []CommandOrEventParam
	var coe *ProductCommandOrEvent
	found := false
	for _, one := range target {
		if one.Name == name {
			paramInfo = one.Params
			coe = &one
			found = true
			break
		}
	}
	if found == false {
		return nil, []interface{}{}, errors.New("command or event not found.")
	}
	if len(paramInfo) != len(params) {
		return nil, []interface{}{}, errors.New("wrong parameters.")
	}
	realParams := make([]interface{}, len(params))
	for idx, para := range paramInfo {
		realParams[idx] = tlv.CastTLV(params[idx], para.ValueType)
	}
	return coe, realParams, nil
}

// StatusToMap struct to map
func (config *ProductConfig) StatusToMap(status []protocol.SubData) (map[string]interface{}, error) {
	result := make(map[string]interface{})
	for _, sub := range status {
		val, err := tlv.ReadTLVs(sub.Params)
		if err != nil {
			return nil, err
		}
		label := ""
		values := make(map[string]interface{})
		for _, obj := range config.Objects {
			if obj.No == int(sub.Head.PropertyNum) {
				label = obj.Label
				for k, v := range obj.Status {
					values[v.Name] = val[k]
				}
			}
		}
		result[label] = values
		result["device_id"] = sub.Head.SubDeviceid
	}

	return result, nil
}

// EventToMap event ot map
func (config *ProductConfig) EventToMap(event *protocol.Event) (map[string]interface{}, error) {
	result := make(map[string]interface{})
	val, err := tlv.ReadTLVs(event.Params)
	if err != nil {
		return nil, err
	}
	label := ""
	values := make(map[string]interface{})
	for _, obj := range config.Events {
		if obj.No == int(event.Head.No) {
			label = obj.Name
			for k, v := range obj.Params {
				values[v.Name] = val[k]
			}
		}
	}

	result[label] = values
	result["device_id"] = event.Head.SubDeviceid
	return result, nil
}

// MapToStatus map to status
func (config *ProductConfig) MapToStatus(data map[string]interface{}) ([]protocol.SubData, error) {
	var result []protocol.SubData

	for label, one := range data {
		params, ok := one.([]interface{})
		if !ok {
			return nil, fmt.Errorf("status format error: %v", one)
		}
		obj, realParams, err := config.ValidateStatus(label, params)
		if err != nil {
			return nil, err
		}

		tlvs, err := tlv.MakeTLVs(realParams)
		if err != nil {
			return nil, err
		}

		result = append(result, protocol.SubData{
			Head: protocol.SubDataHead{
				SubDeviceid: uint16(obj.Part),
				PropertyNum: uint16(obj.No),
				ParamsCount: uint16(len(realParams)),
			},
			Params: tlvs,
		})
	}

	return result, nil
}

func (config *ProductConfig) MapToCommand(cmd map[string]interface{}) (*protocol.Command, error) {
	result := &protocol.Command{}

	for name, one := range cmd {
		params, ok := one.([]interface{})
		if !ok {
			return nil, fmt.Errorf("command format error: %v", one)
		}

		c, realParams, err := config.ValidateCommandOrEvent(name, params, "command")
		if err != nil {
			return nil, err
		}

		tlvs, err := tlv.MakeTLVs(realParams)
		if err != nil {
			return nil, err
		}

		result.Head.No = uint16(c.No)
		result.Head.Priority = uint16(c.Priority)
		result.Head.SubDeviceid = uint16(c.Part)
		result.Head.ParamsCount = uint16(len(realParams))
		result.Params = tlvs

	}

	return result, nil
}
