// Copyright 2017 Blues Inc.  All rights reserved.
// Use of this source code is governed by licenses granted by the
// copyright holder including that found in the LICENSE file.

package notelib

import (
	"fmt"
	"testing"

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

const verboseSmazzTest = false

type smazzTest struct {
	name         string
	uncompressed []byte
	compressed   []byte
	crc16        uint16
}

var smazzTests = []smazzTest{
	{
		name:         "test-null",
		uncompressed: []byte{},
		compressed:   []byte{},
		crc16:        0x0000,
	},

	{
		name:         "test-grow",
		uncompressed: []byte("AAAAAaBBBBBbCCCCCcDDDDDdEEEEEeFFFFFf"),
		compressed:   []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x61, 0x42, 0x42, 0x42, 0x42, 0x42, 0x62, 0x43, 0x43, 0x43, 0x43, 0x43, 0x63, 0x44, 0x44, 0x44, 0x44, 0x44, 0x64, 0x45, 0x45, 0x45, 0x45, 0x45, 0x65, 0x46, 0x46, 0x46, 0x46, 0x46, 0x66},
		crc16:        0x794F,
	},

	{
		name:         "test1",
		uncompressed: []byte(`{"p":"net.ozzie.ray:test","s":"my device","k":"NOTE-LORA,"v":"1.2.3","o":""}`),
		compressed:   []byte{0x00, 0x00, 0x1B, 0x46, 0x8F, 0x1A, 0x25, 0x25, 0x94, 0x2A, 0xBA, 0x24, 0x36, 0xB6, 0xC5, 0x92, 0x1E, 0x46, 0xE4, 0x26, 0xB8, 0xE6, 0x89, 0x92, 0x16, 0x46, 0xFF, 0x04, 0x4E, 0x4F, 0x54, 0x45, 0x28, 0xFF, 0x04, 0x4C, 0x4F, 0x52, 0x41, 0x5D, 0x46, 0xC2, 0xC3, 0x05, 0x92, 0x1A, 0x46, 0x01},
		crc16:        0x0CB6,
	},

	{
		name:         "test2",
		uncompressed: []byte(`{"minutes":12,"v_min":12.1,"v_max":12.1,"v_chg":12.1,"i_min":12.1,"i_max":12.1,"i_chg":12.1,"p_min":12.1,"p_max":12.1,"p_chg":12.1}`),
		compressed:   []byte{0x00, 0x00, 0x81, 0x20, 0xB6, 0x1E, 0x3A, 0x5D, 0xEC, 0x42, 0x5D, 0xED, 0x42, 0x5D, 0x66, 0x8E, 0x42, 0x50, 0xEC, 0x42, 0x50, 0xED, 0x42, 0x50, 0x66, 0x8E, 0x42, 0x57, 0xEC, 0x42, 0x57, 0xED, 0x42, 0x57, 0x66, 0x8E, 0x42, 0x33},
		crc16:        0xC1A7,
	},

	{
		name:         "test3",
		uncompressed: []byte(`{"status":"0","motion":12,"seconds":14,"time":14,"dop":12.1,"voltage":12.1,"daily_charging_mins":14,"distance":14.1,"bearing":14.1,"velocity":14.1,"temperature":12.1,"humidity":12.1,"pressure":14.1,"total":12,"count":12,"sensor":"12","usv":14.1,"cpm":12.1,"cpm_secs":12,"cpm_count":14,"journey":14,"jcount":12,"button":true,"inside_fence":true,"usb":true,"charging":true}`),
		compressed:   []byte{0x00, 0x00, 0xEE, 0xF2, 0x45, 0x54, 0xD3, 0x9A, 0x3A, 0x5A, 0xCF, 0xAF, 0xEF, 0x3C, 0x5B, 0xD5, 0x10, 0x3C, 0x4B, 0x1A, 0x1B, 0x42, 0x5D, 0xFB, 0xC7, 0xE3, 0x42, 0x4B, 0x0C, 0xDE, 0x24, 0x66, 0xC8, 0xF3, 0x99, 0xEC, 0x1E, 0x3C, 0x4B, 0x14, 0xC5, 0xAC, 0x89, 0x43, 0x49, 0x10, 0xB4, 0x99, 0x43, 0x5D, 0x10, 0x17, 0x1A, 0x0E, 0xBD, 0x24, 0x43, 0x5B, 0xBF, 0xCA, 0xBA, 0xFA, 0xAA, 0x42, 0x4F, 0xF6, 0xDB, 0xBD, 0x24, 0x42, 0x57, 0xAA, 0xF7, 0x20, 0xAA, 0x43, 0x5B, 0xD3, 0xB5, 0x3A, 0x4A, 0x1A, 0xD4, 0x1F, 0x3A, 0x5A, 0xA8, 0x1E, 0xB3, 0x46, 0x03, 0x04, 0x92, 0xF2, 0x21, 0x43, 0x4A, 0xF4, 0x42, 0x4A, 0xF4, 0x76, 0x82, 0x3A, 0x4A, 0xF4, 0x66, 0x1A, 0xD4, 0x1F, 0x3C, 0x51, 0xD0, 0xF5, 0x24, 0x3C, 0x51, 0xB7, 0xD4, 0x1F, 0x3A, 0x49, 0x20, 0x1F, 0xB9, 0x19, 0x38, 0x50, 0xF9, 0xDB, 0x10, 0x69, 0xA8, 0x89, 0x38, 0x5C, 0x1E, 0x0D, 0x38, 0x4A, 0xC8, 0xF3, 0x99, 0x38, 0x33},
		crc16:        0x9650,
	},

	{
		name:         "test4",
		uncompressed: []byte(`{"voltage":12.1,"temperature":14.1,"humidity":14.1,"pressure":14.1,"motion":12,"count":14}`),
		compressed:   []byte{0x00, 0x00, 0xF0, 0xC7, 0xE3, 0x42, 0x5B, 0xBF, 0xCA, 0xBA, 0xFA, 0xAA, 0x43, 0x4F, 0xF6, 0xDB, 0xBD, 0x24, 0x43, 0x57, 0xAA, 0xF7, 0x20, 0xAA, 0x43, 0x54, 0xD3, 0x9A, 0x3A, 0x4A, 0x1A, 0xD4, 0x1F, 0x3C, 0x33},
		crc16:        0xC51C,
	},

	{
		name:         "test5",
		uncompressed: []byte(`{"sensor":"12","pm01_0":14.1,"pm02_5":14.1,"pm10_0":14.1,"pm01_0_rstd":12.1,"pm02_5_rstd":12.1,"pm10_0_rstd":12.1,"c00_30":12,"c00_50":12,"c01_00":12,"c02_50":12,"c05_00":12,"c10_00":12,"pm01_0cf1":14.1,"pm02_5cf1":14.1,"pm10_0cf1":14.1,"csamples":12,"csecs":12,"voltage":12.1,"temperature":14.1,"humidity":14.1,"pressure":14.1,"charging":true,"usb":true,"indoors":true,"motion":12,"cpm":12.1,"cpm_count":14,"usv":12.1}`),
		compressed:   []byte{0x00, 0x00, 0xB1, 0xF9, 0xB3, 0x46, 0x03, 0x04, 0x92, 0xF4, 0x02, 0x03, 0x29, 0x02, 0x43, 0x57, 0x18, 0x02, 0x04, 0x29, 0x07, 0x43, 0x57, 0x18, 0x03, 0x02, 0x29, 0x02, 0x43, 0x57, 0x18, 0x02, 0x03, 0x29, 0x02, 0x29, 0xD2, 0x1F, 0x0F, 0x42, 0x57, 0x18, 0x02, 0x04, 0x29, 0x07, 0x29, 0xD2, 0x1F, 0x0F, 0x42, 0x57, 0x18, 0x03, 0x02, 0x29, 0x02, 0x29, 0xD2, 0x1F, 0x0F, 0x42, 0x4A, 0x02, 0x02, 0x29, 0x05, 0x02, 0x3A, 0x4A, 0x02, 0x02, 0x29, 0x07, 0x02, 0x3A, 0x4A, 0x02, 0x03, 0x29, 0x02, 0x02, 0x3A, 0x4A, 0x02, 0x04, 0x29, 0x07, 0x02, 0x3A, 0x4A, 0x02, 0x07, 0x29, 0x02, 0x02, 0x3A, 0x4A, 0x03, 0x02, 0x29, 0x02, 0x02, 0x3A, 0x57, 0x18, 0x02, 0x03, 0x29, 0x02, 0x0E, 0x11, 0x03, 0x43, 0x57, 0x18, 0x02, 0x04, 0x29, 0x07, 0x0E, 0x11, 0x03, 0x43, 0x57, 0x18, 0x03, 0x02, 0x29, 0x02, 0x0E, 0x11, 0x03, 0x43, 0x4A, 0xBE, 0x18, 0x1B, 0xC1, 0x1E, 0x3A, 0x4A, 0x8B, 0x1E, 0x3A, 0x5D, 0xFB, 0xC7, 0xE3, 0x42, 0x5B, 0xBF, 0xCA, 0xBA, 0xFA, 0xAA, 0x43, 0x4F, 0xF6, 0xDB, 0xBD, 0x24, 0x43, 0x57, 0xAA, 0xF7, 0x20, 0xAA, 0x43, 0x4A, 0xC8, 0xF3, 0x99, 0x38, 0x5C, 0x1E, 0x0D, 0x38, 0x50, 0xB2, 0x1A, 0xB3, 0x1E, 0x38, 0x54, 0xD3, 0x9A, 0x3A, 0x4A, 0xF4, 0x42, 0x4A, 0xF4, 0x66, 0x1A, 0xD4, 0x1F, 0x3C, 0x5C, 0x1E, 0x21, 0x42, 0x33},
		crc16:        0xA926,
	},
	{
		name:         "unicode",
		uncompressed: []byte(`{"サッポロシチュウオウク":"*","_note":"0"}`),
		compressed:   []byte{0x00, 0x00, 0xff, 0x21, 0xe3, 0x82, 0xb5, 0xe3, 0x83, 0x83, 0xe3, 0x83, 0x9d, 0xe3, 0x83, 0xad, 0xe3, 0x82, 0xb7, 0xe3, 0x83, 0x81, 0xe3, 0x83, 0xa5, 0xe3, 0x82, 0xa6, 0xe3, 0x82, 0xaa, 0xe3, 0x82, 0xa6, 0xe3, 0x82, 0xaf, 0x46, 0xfe, 0x2a, 0x92, 0x71, 0xd3, 0x10, 0x45, 0x33},
		crc16:        0xC293,
	},
}

func TestSmazz(t *testing.T) {

	ctx := Smazz(SmazzCodeTemplate)
	for _, test := range smazzTests {
		compressed, err := ctx.Encode(nil, test.uncompressed)
		require.NoError(t, err)
		isUncompressed := ""
		if len(test.uncompressed) == 0 || compressed[0] != 0 {
			isUncompressed = "(would have grown)"
		}
		decompressed, err := ctx.Decode(nil, compressed)
		require.NoError(t, err)
		pct := float64(0)
		if len(test.uncompressed) > 0 {
			pct = float64(len(test.uncompressed)-len(compressed)) * 100.0 / float64(len(test.uncompressed))
		}
		testDecompressed, err := ctx.Decode(nil, test.compressed)
		require.NoError(t, err)
		if verboseSmazzTest {
			fmt.Printf("smazz_test name:%s inp:%d enc:%d dec:%d pct:%f %s\n", test.name, len(test.uncompressed), len(compressed), len(decompressed), pct, isUncompressed)
		}
		require.Equal(t, Crc16(test.uncompressed), test.crc16)
		require.Equal(t, decompressed, test.uncompressed)
		require.Equal(t, len(compressed), len(test.compressed))
		require.Equal(t, decompressed, test.uncompressed)
		require.Equal(t, compressed, test.compressed)
		require.Equal(t, decompressed, testDecompressed)
	}

}

// CRC-16 calculation using IBM polynomial
// x^16 + x^15 + x^2 + 1 (0xA001)
// Initial value: 0
func Crc16(data []byte) uint16 {
	size := len(data)
	var crc uint16
	for i := 0; i < size; i++ {
		crc ^= uint16(data[i])
		for k := 0; k < 8; k++ {
			if (crc & 1) != 0 {
				crc = (crc >> 1) ^ 0xA001
			} else {
				crc = crc >> 1
			}
		}
	}
	return crc
}
