package decode_test

import (
	"fmt"
	"testing"

	can "github.com/fiskerinc/cloud-services/pkg/can-go"
	"github.com/fiskerinc/cloud-services/pkg/can-go/internal/generate"
	"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
)

type signal struct {
	name        string
	value       float64
	description string
	unit        string
}

func getTestDatabase() descriptor.Database {
	dbc := []byte(`
VERSION ""
NS_ :
BS_:
BU_: DBG DRIVER IO MOTOR SENSOR

BO_ 1 EmptyMessage: 0 DBG
BO_ 100 DriverHeartbeat: 1 DRIVER
 SG_ Command : 0|8@1+ (1,0) [0|0] "" SENSOR,MOTOR
BO_ 101 MotorCommand: 1 DRIVER
 SG_ Steer : 0|4@1- (1,-5) [-5|5] "" MOTOR
 SG_ Drive : 4|4@1+ (1,0) [0|9] "" MOTOR
BO_ 400 MotorStatus: 3 MOTOR
 SG_ WheelError : 0|1@1+ (1,0) [0|0] "" DRIVER,IO
 SG_ SpeedKph : 8|16@1+ (0.001,0) [0|0] "km/h" DRIVER,IO

BO_ 200 SensorSonars: 8 SENSOR
 SG_ Mux M : 0|4@1+ (1,0) [0|0] "" DRIVER,IO
 SG_ ErrCount : 4|12@1+ (1,0) [0|0] "" DRIVER,IO
 SG_ Left m0 : 16|12@1+ (0.1,0) [0|0] "" DRIVER,IO
 SG_ Middle m0 : 28|12@1+ (0.1,0) [0|0] "" DRIVER,IO
 SG_ Right m0 : 40|12@1+ (0.1,0) [0|0] "" DRIVER,IO
 SG_ Rear m0 : 52|12@1+ (0.1,0) [0|0] "" DRIVER,IO
 SG_ NoFiltLeft m1 : 16|12@1+ (0.1,0) [0|0] "" DBG
 SG_ NoFiltMiddle m1 : 28|12@1+ (0.1,0) [0|0] "" DBG
 SG_ NoFiltRight m1 : 40|12@1+ (0.1,0) [0|0] "" DBG
 SG_ NoFiltRear m1 : 52|12@1+ (0.1,0) [0|0] "" DBG

BO_ 500 IODebug: 6 IO
 SG_ TestUnsigned : 0|8@1+ (1,0) [0|0] "" DBG
 SG_ TestEnum : 8|6@1+ (1,0) [0|0] "" DBG
 SG_ TestSigned : 16|8@1- (1,0) [0|0] "" DBG
 SG_ TestFloat : 24|8@1+ (0.5,0) [0|0] "" DBG
 SG_ TestBoolEnum : 32|1@1+ (1,0) [0|0] "" DBG
 SG_ TestScaledEnum : 40|2@1+ (2,0) [0|6] "" DBG

BO_ 1530 DisconnectState: 14 MOTOR
 SG_ LockCountRearRight : 91|20@0+ (1,0) [0|1048575] ""  IO
 SG_ DisconnectStateRearRight : 95|4@0+ (1,0) [0|5] ""  IO
 SG_ CurrentRearRight : 79|16@0+ (1,0) [0|65535] ""  IO
 SG_ DisconnectStateRearRightTarget : 64|1@0+ (1,0) [0|1] ""  IO
 SG_ TargetSpeedRearRight : 63|15@0+ (0.125,-2048) [-2048|2047.875] "rad/s"  IO
 SG_ LockCountRearLeft : 35|20@0+ (1,0) [0|1048575] ""  IO
 SG_ DisconnectStateRearLeft : 39|4@0+ (1,0) [0|5] ""  IO
 SG_ CurrentRearLeft : 23|16@0+ (1,0) [0|65535] ""  IO
 SG_ DisconnectStateRearLeftTarget : 8|1@0+ (1,0) [0|1] ""  IO
 SG_ TargetSpeedRearLeft : 7|15@0+ (0.125,-2048) [-2048|2047.875] "rad/s"  IO

BO_ 1927 EvaporatorVariables: 14 SENSOR
 SG_ EvaporatorAirTempIn : 96|8@0+ (0.4,-20) [-19.6|82.4] "degC"  IO
 SG_ CompressorSpeedActual : 80|16@0+ (1,-1) [0|8600] "RPM"  IO
 SG_ FBTargetCompressorSpeed : 64|16@0+ (1,-8600) [-8599|8600] "RPM"  IO
 SG_ FFTargetCompressorSpeed : 48|16@0+ (1,-1) [0|8600] "RPM"  IO
 SG_ FBTargetSuctionPressure : 32|16@0+ (0.05,0) [0.05|3276.8] "kPa"  IO
 SG_ FFTargetSuctionPressure : 16|16@0+ (0.05,0) [0.05|3276.8] "kPa"  IO
 SG_ ChillerCoolantOutTempTarget : 8|8@0+ (0.4,-20) [-19.6|82.4] "degC"  IO
 SG_ EvaporatorAirOutTempTarget : 0|8@0+ (0.4,-20) [-19.6|82.4] "degC"  IO

EV_ BrakeEngaged: 0 [0|1] "" 0 10 DUMMY_NODE_VECTOR0 Vector__XXX;
EV_ Torque: 1 [0|30000] "mNm" 500 16 DUMMY_NODE_VECTOR0 Vector__XXX;
CM_ EV_ BrakeEngaged "Brake fully engaged";
CM_ BU_ DRIVER "The driver controller driving the car";
CM_ BU_ MOTOR "The motor controller of the car";
CM_ BU_ SENSOR "The sensor controller of the car";
CM_ BO_ 100 "Sync message used to synchronize the controllers";

BA_DEF_ "BusType" STRING ;
BA_DEF_ BO_  "GenMsgSendType" ENUM  "None","Cyclic","OnEvent";
BA_DEF_ BO_ "GenMsgCycleTime" INT 0 0;
BA_DEF_ SG_  "FieldType" STRING ;
BA_DEF_ SG_  "GenSigStartValue" INT 0 10000;
BA_DEF_DEF_ "BusType" "CAN";
BA_DEF_DEF_ "FieldType" "";
BA_DEF_DEF_ "GenMsgCycleTime" 0;
BA_DEF_DEF_ "GenSigStartValue" 0;

BA_ "GenMsgSendType" BO_ 1 0;
BA_ "GenMsgSendType" BO_ 100 1;
BA_ "GenMsgCycleTime" BO_ 100 1000;
BA_ "GenMsgSendType" BO_ 101 1;
BA_ "GenMsgCycleTime" BO_ 101 100;
BA_ "GenMsgSendType" BO_ 200 1;
BA_ "GenMsgCycleTime" BO_ 200 100;
BA_ "GenMsgSendType" BO_ 400 1;
BA_ "GenMsgCycleTime" BO_ 400 100;
BA_ "GenMsgSendType" BO_ 500 2;
BA_ "FieldType" SG_ 100 Command "Command";
BA_ "FieldType" SG_ 500 TestEnum "TestEnum";
BA_ "GenSigStartValue" SG_ 500 TestEnum 2;

VAL_ 100 Command 2 "Reboot" 1 "Sync" 0 "None" ;
VAL_ 500 TestEnum 2 "Two" 1 "One" ;
VAL_ 500 TestScaledEnum 3 "Six" 2 "Four" 1 "Two" 0 "Zero" ;
VAL_ 500 TestBoolEnum 1 "One" 0 "Zero" ;
VAL_ 1530 DisconnectStateRearRight 0 "Undefined" 1 "Locked" 2 "Unlocked" 3 "Locking" 4 "Unlocking" 5 "Faulted" ;
VAL_ 1530 DisconnectStateRearLeft 0 "Undefined" 1 "Locked" 2 "Unlocked" 3 "Locking" 4 "Unlocking" 5 "Faulted" ;

`)
	c, _ := generate.Compile("test.dbc", dbc)
	db := *c.Database
	return db
}

func TestDecodeEvaporatorVariables(t *testing.T) {
	db := getTestDatabase()
	message, _ := db.Message(uint32(1927))

	canDataHexString := "008232204e027600ca4b0007d296"

	payload, err := can.PayloadFromHex(canDataHexString)
	if err != nil {
		t.Errorf("err = %v; want nil", err)
	}

	expected := []signal{
		{
			name:        "EvaporatorAirOutTempTarget",
			value:       6.0,
			description: "",
			unit:        "degC",
		},
		{
			name:        "ChillerCoolantOutTempTarget",
			value:       -10.0,
			description: "",
			unit:        "degC",
		},
		{
			name:        "FFTargetSuctionPressure",
			value:       206.75,
			description: "",
			unit:        "kPa",
		},
		{
			name:        "FBTargetSuctionPressure",
			value:       15.75,
			description: "",
			unit:        "kPa",
		},
		{
			name:        "FFTargetCompressorSpeed",
			value:       100,
			description: "",
			unit:        "RPM",
		},
		{
			name:        "FBTargetCompressorSpeed",
			value:       1000,
			description: "",
			unit:        "RPM",
		},
		{
			name:        "CompressorSpeedActual",
			value:       1000,
			description: "",
			unit:        "RPM",
		},
		{
			name:        "EvaporatorAirTempIn",
			value:       10.0,
			description: "",
			unit:        "degC",
		},
	}

	expectedMap := make(map[string]signal)
	for _, s := range expected {
		expectedMap[s.name] = s
	}

	for _, signal := range message.Signals {
		value := signal.UnmarshalPhysicalPayload(&payload)
		valueDesc, _ := signal.UnmarshalValueDescriptionPayload(&payload)
		unit := signal.Unit
		name := signal.Name

		if value != expectedMap[name].value {
			t.Errorf("signal[%s] value = %f ; expected: %f", name, value, expectedMap[name].value)
		}

		if valueDesc != expectedMap[name].description {
			t.Errorf("signal[%s] value = %s ; expected: %s", name, valueDesc, expectedMap[name].description)
		}

		if unit != expectedMap[name].unit {
			t.Errorf("signal[%s] value = %s ; expected: %s", name, unit, expectedMap[name].unit)
		}
	}

	for _, signal := range message.Decode(&payload) {
		value := signal.Value
		valueDesc := signal.Description
		name := signal.Signal.Name

		if value != expectedMap[name].value {
			t.Errorf("signal[%s] value = %f ; expected: %f", name, value, expectedMap[name].value)
		}

		if valueDesc != expectedMap[name].description {
			t.Errorf("signal[%s] value = %s ; expected: %s", name, valueDesc, expectedMap[name].description)
		}
	}
}

func TestDecodeDisconnectState(t *testing.T) {
	byteStringHex := "8000000420061880000005200600"

	p, err := can.PayloadFromHex(byteStringHex)
	if err != nil {
		fmt.Println(err)
	}

	expected := []signal{
		{

			name:        "TargetSpeedRearLeft",
			value:       0.0,
			description: "",
			unit:        "rad/s",
		},
		{

			name:        "DisconnectStateRearLeftTarget",
			value:       0,
			description: "",
			unit:        "",
		},
		{

			name:        "CurrentRearLeft",
			value:       4,
			description: "",
			unit:        "",
		},
		{

			name:        "DisconnectStateRearLeft",
			value:       2,
			description: "Unlocked",
			unit:        "",
		},
		{

			name:        "LockCountRearLeft",
			value:       1560,
			description: "",
			unit:        "",
		},
		{

			name:        "TargetSpeedRearRight",
			value:       0,
			description: "",
			unit:        "rad/s",
		},
		{

			name:        "DisconnectStateRearRightTarget",
			value:       0,
			description: "",
			unit:        "",
		},
		{

			name:        "CurrentRearRight",
			value:       5,
			description: "",
			unit:        "",
		},
		{

			name:        "DisconnectStateRearRight",
			value:       2,
			description: "Unlocked",
			unit:        "",
		},
		{

			name:        "LockCountRearRight",
			value:       1536,
			description: "",
			unit:        "",
		},
	}

	expectedMap := make(map[string]signal)
	for _, s := range expected {
		expectedMap[s.name] = s
	}

	db := getTestDatabase()
	message, _ := db.Message(uint32(1530))
	for _, signal := range message.Signals {
		value := signal.UnmarshalPhysicalPayload(&p)
		valueDesc, _ := signal.UnmarshalValueDescriptionPayload(&p)
		unit := signal.Unit
		name := signal.Name

		if value != expectedMap[name].value {
			t.Errorf("signal[%s] value = %f ; expected: %f", name, value, expectedMap[name].value)
		}

		if valueDesc != expectedMap[name].description {
			t.Errorf("signal[%s] value = %s ; expected: %s", name, valueDesc, expectedMap[name].description)
		}

		if unit != expectedMap[name].unit {
			t.Errorf("signal[%s] value = %s ; expected: %s", name, unit, expectedMap[name].unit)
		}
	}
}

func TestDecodeSensorSonarsData(t *testing.T) {
	data := can.Data{0x01, 0x01, 0x01, 0x02, 0x01, 0x00}
	payload := can.Payload{Data: data[:]}

	db := getTestDatabase()
	message, _ := db.Message(uint32(500))
	fmt.Println(message.Length)

	for _, signal := range message.Signals {
		value := signal.UnmarshalPhysicalPayload(&payload)
		valueDesc, _ := signal.UnmarshalValueDescriptionPayload(&payload)

		valueFromData := signal.UnmarshalPhysical(data)
		descFromData, _ := signal.UnmarshalValueDescription(data)
		name := signal.Name

		if value != valueFromData {
			t.Errorf("signal[%s] value = %f ; expected: %f", name, value, valueFromData)
		}

		if valueDesc != descFromData {
			t.Errorf("signal[%s] value = %s ; expected: %s", name, valueDesc, descFromData)
		}
	}
}

func TestMessageDecodeSensorSonarsData(t *testing.T) {
	data := can.Data{0x01, 0x01, 0x01, 0x02, 0x01, 0x00}
	payload := can.Payload{Data: data[:]}

	db := getTestDatabase()
	message, _ := db.Message(uint32(500))
	fmt.Println(message.Length)

	decodedSignals := message.Decode(&payload)
	for _, signal := range decodedSignals {
		value := signal.Value
		valueDesc := signal.Description
		name := signal.Signal.Name

		valueFromData := signal.Signal.UnmarshalPhysical(data)
		descFromData, _ := signal.Signal.UnmarshalValueDescription(data)

		if value != valueFromData {
			t.Errorf("signal[%s] value = %f ; expected: %f", name, value, valueFromData)
		}

		if valueDesc != descFromData {
			t.Errorf("signal[%s] value = %s ; expected: %s", name, valueDesc, descFromData)
		}
	}
}

func BenchmarkDecodeData(b *testing.B) {
	db := getTestDatabase()
	message, _ := db.Message(uint32(500))
	decodeSignal := func() {
		data := can.Data{0x01, 0x01, 0x01, 0x02, 0x01, 0x00}
		for _, signal := range message.Signals {
			_ = signal.UnmarshalPhysical(data)
			_, _ = signal.UnmarshalValueDescription(data)
		}
	}
	for i := 0; i < b.N; i++ {
		decodeSignal()
	}
}

func BenchmarkDecodePayload(b *testing.B) {
	// {0x01, 0x01, 0x01, 0x02, 0x01, 0x00}

	db := getTestDatabase()
	message, _ := db.Message(uint32(500))
	decodeSignal := func() {
		data := can.Payload{Data: []byte{0x01, 0x01, 0x01, 0x02, 0x01, 0x00}}
		for _, signal := range message.Signals {
			_ = signal.UnmarshalPhysicalPayload(&data)
			_, _ = signal.UnmarshalValueDescriptionPayload(&data)
		}
	}
	for i := 0; i < b.N; i++ {
		decodeSignal()
	}
}
