package models

import (
	"fmt"

	"github.com/fiskerinc/cloud-services/pkg/dbc/state"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/digitaltwin"
)

type CANMessageInterface interface {
	AddSignal(signal CANSignal) CANMessageInterface
	GetID() int
	GetLength() int
	ParseState(keyvaluesOut map[string]interface{}, vin string, timestampUSec int, incomingSignals []common.CANSignal, cached digitaltwin.DigitalTwinCacheInterface) error
	Signal(position int, name string, convert func(value float64) interface{}) CANMessageInterface
}

// NewCANMessage creates a top level instance for parsing a command. The id is the can signal in which individual signals exists
// length is the number of messages inside the message, i.e. 1318 TBOX_0x526 has 2 different signals: TBOX_GPSLongi and TBOX_GPSLati
func NewCANMessage(id int, length int) CANMessageInterface {
	return &CANMessage{
		id:     id,
		length: length,
	}
}

type CANMessage struct {
	id         int
	length     int
	signals    map[int]CANSignal
	diagnostic map[int]DiagnosticSignal
}

func (c *CANMessage) GetID() int {
	return c.id
}

func (c *CANMessage) GetLength() int {
	return c.length
}

// position is the index of the signal in its list. Starting at zero
func (c *CANMessage) Signal(position int, name string, convert func(value float64) interface{}) CANMessageInterface {
	c.AddSignal(NewCANSignal(position, name, convert))
	return c
}

func (c *CANMessage) AddSignal(signal CANSignal) CANMessageInterface {
	m := c.getSignalsMap()
	if _, ok := m[signal.Position]; ok {
		panic(fmt.Sprintf("signal position %d already exists", signal.Position))
	}

	m[signal.Position] = signal

	return c
}

func (c *CANMessage) Diagnostic(position int, name string, alert float64) CANMessageInterface {
	c.AddDiagnostic(NewDiagnosticSignal(position, name, alert))
	return c
}

func (c *CANMessage) AddDiagnostic(signal DiagnosticSignal) CANMessageInterface {
	m := c.getDiagnosticMap()
	if _, ok := m[signal.Position]; ok {
		panic(fmt.Sprintf("diagnositic position %d already exists", signal.Position))
	}

	m[signal.Position] = signal

	return c
}

func (c *CANMessage) getDiagnosticMap() map[int]DiagnosticSignal {
	if c.diagnostic == nil {
		c.diagnostic = map[int]DiagnosticSignal{}
	}

	return c.diagnostic
}

func (c *CANMessage) getSignalsMap() map[int]CANSignal {
	if c.signals == nil {
		c.signals = map[int]CANSignal{}
	}

	return c.signals
}

// cached: Checks if the new signal has the same value as it did the last time, if so don't update it
func (c *CANMessage) ParseState(keyvaluesOut map[string]interface{}, vin string, timestampUSec int, incomingSignals []common.CANSignal, cached digitaltwin.DigitalTwinCacheInterface) error {
	var can CANSignal
	var ok bool

	s := c.getSignalsMap()

	for i, signal := range incomingSignals {
		if can, ok = s[i]; !ok {
			continue
		}

		if can.Name != signal.Name {
			return IncorrectSignalError(can.Name, signal.Name)
		}

		val := can.ParseState(incomingSignals[i])

		if cached != nil && cached.Exists(vin, signal.Name, val) {
			continue
		}
		keyvaluesOut[signal.Name] = val

		// Added updated time for each signals. Key format -> "${signal_name}:updated"
		updatedAt, _ := state.SerializeTimestampUSec(timestampUSec)
		keyvaluesOut[fmt.Sprintf("%s:%s", signal.Name, "updated")] = updatedAt
	}
	return nil
}

/*
func (c *CANMessage) DiagnosticAlert(vin string, timestampUSec int, signals []common.CANSignal) ([]DiagnosticSignal, error) {
	var flags []DiagnosticSignal

	m := c.getDiagnosticMap()

	for i, signal := range signals {
		if diag, ok := m[i]; !ok {
			continue
		}

		if diag.Name != signal.Name {
			return nil, IncorrectSignalError(diag.Name, signal.Name)
		}

		append(flags, diag)
	}

	return nil, nil
}
*/
