package mk2driver

import (
	"bytes"
	"io"
	"testing"

	"github.com/stretchr/testify/assert"
)

var knownWrites = []byte{
	0x04, 0xff, 0x41, 0x01, 0x00, 0xbb,
	0x05, 0xff, 0x57, 0x36, 0x00, 0x00, 0x6f,
	0x05, 0xff, 0x57, 0x36, 0x01, 0x00, 0x6e,
	0x05, 0xff, 0x57, 0x36, 0x02, 0x00, 0x6d,
	0x05, 0xff, 0x57, 0x36, 0x03, 0x00, 0x6c,
	0x05, 0xff, 0x57, 0x36, 0x04, 0x00, 0x6b,
	0x05, 0xff, 0x57, 0x36, 0x05, 0x00, 0x6a,
	0x05, 0xff, 0x57, 0x36, 0x06, 0x00, 0x69,
	0x05, 0xff, 0x57, 0x36, 0x07, 0x00, 0x68,
	0x05, 0xff, 0x57, 0x36, 0x08, 0x00, 0x67,
	0x05, 0xff, 0x57, 0x36, 0x09, 0x00, 0x66,
	0x05, 0xff, 0x57, 0x36, 0x0a, 0x00, 0x65,
	0x05, 0xff, 0x57, 0x36, 0x0b, 0x00, 0x64,
	0x05, 0xff, 0x57, 0x36, 0x0c, 0x00, 0x63,
	0x05, 0xff, 0x57, 0x36, 0x0d, 0x00, 0x62,
	0x03, 0xff, 0x46, 0x00, 0xb8,
	0x03, 0xff, 0x46, 0x01, 0xb7,
	0x02, 0xff, 0x4c, 0xb3,
	0x05, 0xff, 0x57, 0x30, 0x0d, 0x00, 0x68,
}

var writeBuffer = bytes.NewBuffer(nil)

const (
	testDelta = 0.00000001
)

type testIo struct {
	io.Reader
	io.Writer
}

func NewIOStub(readBuffer []byte) io.ReadWriter {
	return &testIo{
		Reader: bytes.NewBuffer(readBuffer),
		Writer: writeBuffer,
	}
}

// Test a know sequence as reference as extracted from Mk2
func TestSync(t *testing.T) {
	tests := []struct {
		name            string
		knownReadBuffer []byte
		knownWrites     []byte
		result          Mk2Info
	}{
		{
			name: "basic",
			knownReadBuffer: []byte{
				//Len  Cmd
				0x04, 0xff, 0x41, 0x01, 0x00, 0xbb,
				0x07, 0xff, 0x56, 0x96, 0x3e, 0x11, 0x00, 0x00, 0xbf,
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a,
				0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1,
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a,
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a,
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a,
				0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1,
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a,
				0x08, 0xff, 0x57, 0x8e, 0x57, 0x78, 0x8f, 0x00, 0x01, 0xb5,
				0x08, 0xff, 0x57, 0x8e, 0x2f, 0x7c, 0x8f, 0x00, 0x00, 0xda,
				0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1,
				0x08, 0xff, 0x57, 0x8e, 0x04, 0x00, 0x8f, 0x00, 0x80, 0x01,
				0x08, 0xff, 0x57, 0x8e, 0x01, 0x00, 0x8f, 0x00, 0x80, 0x04,
				0x08, 0xff, 0x57, 0x8e, 0x02, 0x00, 0x8f, 0x00, 0x80, 0x03,
				0x08, 0xff, 0x57, 0x8e, 0x38, 0x7f, 0x8f, 0x00, 0x00, 0xce,
				0x07, 0xff, 0x56, 0x96, 0x3e, 0x11, 0x00, 0x00, 0xbf,
				0x0f, 0x20, 0xf3, 0x00, 0xc8, 0x02, 0x0c, 0xa1, 0x05, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x88, 0xb2,
				0x0f, 0x20, 0x01, 0x01, 0xca, 0x09, 0x08, 0xaa, 0x58, 0xab, 0x00, 0xaa, 0x58, 0x9a, 0x00, 0xc3, 0xe8,
				0x06, 0xff, 0x4c, 0x03, 0x00, 0x00, 0x00, 0xac,
				0x05, 0xff, 0x57, 0x85, 0xc8, 0x00, 0x58,
			},
			knownWrites: []byte{
				0x04, 0xff, 0x41, 0x01, 0x00, 0xbb,
				0x05, 0xff, 0x57, 0x36, 0x00, 0x00, 0x6f,
				0x05, 0xff, 0x57, 0x36, 0x01, 0x00, 0x6e,
				0x05, 0xff, 0x57, 0x36, 0x02, 0x00, 0x6d,
				0x05, 0xff, 0x57, 0x36, 0x03, 0x00, 0x6c,
				0x05, 0xff, 0x57, 0x36, 0x04, 0x00, 0x6b,
				0x05, 0xff, 0x57, 0x36, 0x05, 0x00, 0x6a,
				0x05, 0xff, 0x57, 0x36, 0x06, 0x00, 0x69,
				0x05, 0xff, 0x57, 0x36, 0x07, 0x00, 0x68,
				0x05, 0xff, 0x57, 0x36, 0x08, 0x00, 0x67,
				0x05, 0xff, 0x57, 0x36, 0x09, 0x00, 0x66,
				0x05, 0xff, 0x57, 0x36, 0x0a, 0x00, 0x65,
				0x05, 0xff, 0x57, 0x36, 0x0b, 0x00, 0x64,
				0x05, 0xff, 0x57, 0x36, 0x0c, 0x00, 0x63,
				0x05, 0xff, 0x57, 0x36, 0x0d, 0x00, 0x62,
				0x03, 0xff, 0x46, 0x00, 0xb8,
				0x03, 0xff, 0x46, 0x01, 0xb7,
				0x02, 0xff, 0x4c, 0xb3,
				0x05, 0xff, 0x57, 0x30, 0x0d, 0x00, 0x68,
			},
			result: Mk2Info{
				Version:      uint32(2736),
				BatVoltage:   14.41,
				BatCurrent:   -0.4,
				InVoltage:    226.98,
				InCurrent:    1.71,
				InFrequency:  50.10256410256411,
				OutVoltage:   226.980,
				OutCurrent:   1.54,
				OutFrequency: 50.025510204081634,
				ChargeState:  1,
				LEDs: map[Led]LEDstate{
					LedMain:        LedOn,
					LedAbsorption:  LedOn,
					LedBulk:        LedOff,
					LedFloat:       LedOff,
					LedInverter:    LedOff,
					LedOverload:    LedOff,
					LedLowBattery:  LedOff,
					LedTemperature: LedOff,
				},
			},
		},
		{
			name: "multiplus24/3000",
			knownReadBuffer: []byte{
				//Len  Cmd
				0x04, 0xff, 0x41, 0x01, 0x00, 0xbb,
				0x07, 0xff, 0x56, 0x98, 0x3e, 0x11, 0x00, 0x00, 0xbd, // version
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 0
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 1
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 2
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 3
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 4
				0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x0, 0x0, 0xa1, // scale 5
				0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x0, 0x0, 0x6a, // scale 6
				0x08, 0xff, 0x57, 0x8e, 0x57, 0x78, 0x8f, 0x0, 0x1, 0xb5, // scale 7
				0x08, 0xff, 0x57, 0x8e, 0x2f, 0x7c, 0x8f, 0x0, 0x0, 0xda, // scale 8
				0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x0, 0x0, 0xa1, //scale 9
				0x08, 0xff, 0x57, 0x8e, 0x4, 0x0, 0x8f, 0x0, 0x80, 0x1, // scale 10
				0x08, 0xff, 0x57, 0x8e, 0x1, 0x0, 0x8f, 0x0, 0x80, 0x4, // scale 11
				0x08, 0xff, 0x57, 0x8e, 0x6, 0x0, 0x8f, 0x0, 0x80, 0xff, // scale 12
				0x08, 0xff, 0x57, 0x8e, 0x38, 0x7f, 0x8f, 0x0, 0x0, 0xce, // scale 13
				0x07, 0xff, 0x56, 0x98, 0x3e, 0x11, 0x0, 0x0, 0xbd, // version
				0x0f, 0x20, 0xb6, 0x89, 0x6d, 0xb7, 0xc, 0x4e, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, 0x82, // dc info
				0x0f, 0x20, 0x1, 0x1, 0x6d, 0xb7, 0x8, 0x77, 0x5b, 0x21, 0x0, 0x77, 0x5b, 0xfe, 0xff, 0xc3, 0x1e, // ac info
				0x08, 0xff, 0x4c, 0x9, 0x0, 0x0, 0x0, 0x3, 0x0, 0xa1,
				0x05, 0xff, 0x57, 0x85, 0xc8, 0x0, 0x58,
			},
			knownWrites: []byte{},
			result: Mk2Info{
				Version:      0xac0,
				BatVoltage:   26.38,
				BatCurrent:   0,
				InVoltage:    234.15,
				InCurrent:    0.33,
				InFrequency:  50.1025641025641,
				OutVoltage:   234.15,
				OutCurrent:   -0.02,
				OutFrequency: 50.025510204081634,
				ChargeState:  1,
				LEDs: map[Led]LEDstate{
					LedMain:        LedOn,
					LedAbsorption:  LedOff,
					LedBulk:        LedOff,
					LedFloat:       LedOn,
					LedInverter:    LedOff,
					LedOverload:    LedOff,
					LedLowBattery:  LedOff,
					LedTemperature: LedOff,
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			testIO := NewIOStub(tt.knownReadBuffer)
			mk2, err := NewMk2Connection(testIO)
			assert.NoError(t, err, "Could not open MK2")

			event := <-mk2.C()
			mk2.Close()

			if len(tt.knownWrites) > 0 {
				assert.Equal(t, 0, bytes.Compare(writeBuffer.Bytes(), knownWrites), "Expected writes did not match received writes")
			}
			assert.True(t, event.Valid, "data not valid")
			assert.Equal(t, tt.result.Version, event.Version, "Invalid version decoded")
			assert.Equal(t, 0, len(event.Errors), "Reported errors not empty")
			assert.Equal(t, tt.result.LEDs, event.LEDs, "Reported LEDs incorrect")

			assert.InDelta(t, tt.result.BatVoltage, event.BatVoltage, testDelta, "BatVoltage conversion failed")
			assert.InDelta(t, tt.result.BatCurrent, event.BatCurrent, testDelta, "BatCurrent conversion failed")
			assert.InDelta(t, tt.result.InVoltage, event.InVoltage, testDelta, "InVoltage conversion failed")
			assert.InDelta(t, tt.result.InCurrent, event.InCurrent, testDelta, "InCurrent conversion failed")
			assert.InDelta(t, tt.result.InFrequency, event.InFrequency, testDelta, "InFrequency conversion failed")
			assert.InDelta(t, tt.result.OutVoltage, event.OutVoltage, testDelta, "OutVoltage conversion failed")
			assert.InDelta(t, tt.result.OutCurrent, event.OutCurrent, testDelta, "OutCurrent conversion failed")
			assert.InDelta(t, tt.result.OutFrequency, event.OutFrequency, testDelta, "OutFrequency conversion failed")
			assert.InDelta(t, tt.result.ChargeState, event.ChargeState, testDelta, "ChargeState conversion failed")
		})
	}
}

func Test_mk2Ser_scaleDecode(t *testing.T) {
	tests := []struct {
		name            string
		frame           []byte
		expectedScaling scaling
	}{
		{
			name:  "Valid scale",
			frame: []byte{0x8e, 0x9c, 0x7f, 0x8f, 0x01, 0x00, 0x6a},
			expectedScaling: scaling{
				scale:     0.01,
				offset:    1,
				supported: true,
			},
		},
		{
			name:  "Unsupported frame",
			frame: []byte{0x00},
			expectedScaling: scaling{
				supported: false,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			m := &mk2Ser{
				scales: make([]scaling, 0, ramVarMaxOffset),
				p:      NewIOStub([]byte{}),
			}
			m.scaleDecode(tt.frame)
			assert.Equal(t, 1, len(m.scales))
			assert.Equal(t, 1, m.scaleCount)
			assert.Equal(t, tt.expectedScaling.supported, m.scales[0].supported)
			assert.Equal(t, tt.expectedScaling.signed, m.scales[0].signed)
			if tt.expectedScaling.supported {
				assert.InDelta(t, tt.expectedScaling.offset, m.scales[0].offset, testDelta)
				assert.InDelta(t, tt.expectedScaling.scale, m.scales[0].scale, testDelta)
			}
		})
	}
}

func Test_mk2Ser_calcFreq(t *testing.T) {
	tests := []struct {
		name       string
		scales     []scaling
		data       byte
		scaleIndex int
		want       float64
	}{
		{
			name: "Calculate working low",
			scales: []scaling{
				{supported: false},
			},
			data:       0x01,
			scaleIndex: 0,
			want:       10,
		},
		{
			name: "Calculate working high",
			scales: []scaling{
				{
					supported: true,
					offset:    0,
					scale:     0.01,
				},
			},
			data:       0xFE,
			scaleIndex: 0,
			want:       3.937007874015748,
		},
		{
			name: "Calculate clip high",
			scales: []scaling{
				{supported: false},
			},
			data:       0xff,
			scaleIndex: 0,
			want:       0,
		},
		{
			name: "Calculate clip low",
			scales: []scaling{
				{supported: false},
			},
			data:       0x00,
			scaleIndex: 0,
			want:       0,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			m := &mk2Ser{
				scales: tt.scales,
			}
			got := m.calcFreq(tt.data, tt.scaleIndex)
			assert.InDelta(t, tt.want, got, testDelta)
		})
	}
}
