package predictor

import (
	diff "0dev.org/diff"
	"bytes"
	"fmt"
	"io/ioutil"
	"testing"
)

// Sample input from RFC1978 - PPP Predictor Compression Protocol
var input = []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x0a,
	0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x0a,
	0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x0a,
	0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x0a,
	0x41, 0x42, 0x41, 0x42, 0x41, 0x42, 0x41, 0x0a,
	0x42, 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, 0x0a,
	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x0a}

// Sample output from RFC1978 - PPP Predictor Compression Protocol
var output = []byte{0x60, 0x41, 0x41, 0x41, 0x41, 0x41, 0x0a, 0x60,
	0x41, 0x41, 0x41, 0x41, 0x41, 0x0a, 0x6f, 0x41,
	0x0a, 0x6f, 0x41, 0x0a, 0x41, 0x42, 0x41, 0x42,
	0x41, 0x42, 0x0a, 0x60, 0x42, 0x41, 0x42, 0x41,
	0x42, 0x0a, 0x60, 0x78, 0x78, 0x78, 0x78, 0x78, 0x0a}

func TestCompressorSample(t *testing.T) {
	var (
		buf bytes.Buffer
		err error
	)

	out := Compressor(&buf)
	err = out(input)
	if err != nil {
		t.Error(err)
	}

	err = out(nil)
	if err != nil {
		t.Error(err)
	}

	result := buf.Bytes()
	delta := diff.Diff(diff.D{len(result), len(output), func(i, j int) bool { return result[i] == output[j] }})

	if len(delta.Added) > 0 || len(delta.Removed) > 0 {
		t.Error("Unexpected compressed output", delta)
	}
}

func TestDecompressorSample(t *testing.T) {
	in := Decompressor(bytes.NewReader(output))
	result, err := ioutil.ReadAll(in)
	if err != nil {
		t.Error("Unexpected error while decompressing", err)
	}

	delta := diff.Diff(diff.D{len(result), len(input),
		func(i, j int) bool { return result[i] == input[j] }})

	if len(delta.Added) > 0 || len(delta.Removed) > 0 {
		t.Error("Unexpected decompressed output", delta)
	}
}

var testData = [][]byte{
	[]byte{},
	[]byte{0, 1, 2, 3},
	[]byte{0, 1, 2, 3, 4, 5, 6, 7},
	[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
	[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
}

func TestCycle(t *testing.T) {
	for i := 0; i < len(testData); i++ {
		if err := cycle(testData[i], len(testData[i])); err != nil {
			t.Error(err)
		}
	}
}

func TestStepCycle(t *testing.T) {
	for i := 0; i < len(testData); i++ {
		for j := 1; j < len(testData); j++ {
			if err := cycle(testData[i], j); err != nil {
				t.Error("Error for testData[", i, "], step[", j, "] ", err)
			}
		}
	}
}

func cycle(input []byte, step int) error {
	var (
		buf bytes.Buffer
		err error
	)

	if step > len(input) {
		return nil
	}

	// Create a compressor and write the given data
	compressor := Compressor(&buf)

	var data []byte = input
	var trace []byte = make([]byte, 0)

	for len(data) > 0 {
		if step <= len(data) {

			trace = append(trace, data[:step]...)

			err = compressor(data[:step])
			if err != nil {
				return err
			}
			data = data[step:]
		} else {
			step = len(data)
		}
	}

	// Flush the compressor
	err = compressor(nil)
	if err != nil {
		return err
	}

	// Attempt to decompress the data
	compressed := buf.Bytes()
	decompressed, err := ioutil.ReadAll(Decompressor(bytes.NewReader(compressed)))
	if err != nil {
		return err
	}

	// Diff the result against the initial input
	delta := diff.Diff(diff.D{len(input), len(decompressed),
		func(i, j int) bool { return input[i] == decompressed[j] }})

	// Return a well-formated error if any differences are found
	if len(delta.Added) > 0 || len(delta.Removed) > 0 {
		return fmt.Errorf("Unexpected decompressed output %v\ninput:  %#x\ntrace:  %#x\noutput: %#x\n",
			delta, input, trace, decompressed)
	}

	// All is good :)
	return nil
}
