package main

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/stretchr/testify/assert"
	"io"
	"strings"
	"testing"
)

var (
	g_eol = []byte("\n")
	g_eow = []byte(",")
	g_arg = CMArgs{g_eol, g_eow, []byte("'"), ""}
)

type checkLine struct {
	src []byte
	pk  []byte
	sub []byte
}

// test Line;
func TestLine_SplitPrimaryKey(t *testing.T) {
	ckls := []checkLine{
		{
			[]byte("a,b,c,d"),
			[]byte("a"),
			[]byte("b,c,d"),
		}, {
			[]byte("a ,b,c,d"),
			[]byte("a "),
			[]byte("b,c,d"),
		}, {
			[]byte(" a , b, c,d "),
			[]byte(" a "),
			[]byte(" b, c,d "),
		},
	}
	for _, ckl := range ckls {
		var l Line
		l.write(ckl.src)
		pk, sub, err := l.splitPK(g_eow)
		assert.Nil(t, err)
		assert.Equal(t, ckl.pk, pk)
		assert.Equal(t, ckl.sub, sub)
	}
}

type checkFiles struct {
	eol []byte
	src []byte
	cks [][]byte
}

type ioRC struct {
	*bytes.Reader
}

func (i *ioRC) Close() error {
	return nil
}

func newRC(b []byte) io.ReadCloser {
	var i ioRC
	i.Reader = bytes.NewReader(b)
	return &i
}

// test file;
func TestCMFile_readline(t *testing.T) {
	cks := [][]byte{
		[]byte("a,b,c"),
		[]byte("1,2,3"),
		[]byte("11,22,33"),
		[]byte(" a, , c "),
		[]byte(" 1,2,3 "),
		[]byte("\n1,2,3"),
	}
	ckfs := []checkFiles{
		{[]byte("\n"), []byte("a,b,c\r1,2,3\r\r11,22,33\r\r\r"), cks[:3]},
		{[]byte("\n"), []byte("a,b,c\n1,2,3\n\n11,22,33\n\n"), cks[:3]},
		{[]byte("\r\n"), []byte("a,b,c\r\n1,2,3\r\n\r\n11,22,33\r\n\r\n\r\n"), cks[:3]},
		// space
		{[]byte("\n"), []byte(" a, , c \n 1,2,3 "), cks[3:]},
		// other eol
		{[]byte("|"), []byte("a,b,c|1,2,3|11,22,33||"), cks[:3]},
		{[]byte("|@|"), []byte("a,b,c|@|1,2,3|@|"), cks[:2]},
		{[]byte("\n\n"), []byte("a,b,c\n\n\n1,2,3\n\n"), [][]byte{cks[0], cks[5]}},
	}
	for idx, ckf := range ckfs {
		arg := g_arg
		arg.eol = ckf.eol
		rc := newRC(ckf.src)
		cmf := CMFile{
			name: fmt.Sprintf("test_%d", idx),
			fp:   rc,
			rd:   bufio.NewReader(rc),
			arg:  &arg,
		}
		for _, ck := range ckf.cks {
			l, err := cmf.readLine()
			if err != nil {
				if err != io.EOF {
					t.Fatal(err)
				} else {
					continue
				}
			}
			assert.Equal(t, ck, l.bytes())
		}
	}
}

type ioWC struct {
	*bytes.Buffer
}

func (i *ioWC) Close() error {
	return nil
}

type checkCM struct {
	inputs [][]byte
	outs   []byte
	err    string
	arg    *CMArgs
}

func TestCSVMerger(t *testing.T) {
	ckcms := []checkCM{
		{ // normal check.
			inputs: [][]byte{
				[]byte("pk,n1,n2,n3\npv1,nv11,nv12,nv13\npv2,nv21,nv22,nv23\npv3,nv31,nv32,nv33\npv4,nv41,nv42,nv43"),
				[]byte("pk,n4,n5,n6\npv1,nv14,nv15,nv16\npv2,nv24,nv25,nv26\npv3,nv34,nv35,nv36\npv4,nv44,nv45,nv46"),
			},
			outs: []byte("pk,n1,n2,n3,n4,n5,n6\npv1,nv11,nv12,nv13,nv14,nv15,nv16\npv2,nv21,nv22,nv23,nv24,nv25,nv26\npv3,nv31,nv32,nv33,nv34,nv35,nv36\npv4,nv41,nv42,nv43,nv44,nv45,nv46"),
			err:  "",
		}, { // not equal.
			inputs: [][]byte{
				[]byte("pk,n1,n2,n3\npv1,nv11,nv12,nv13\npv2,nv21,nv22,nv23\npv3,nv31,nv32,nv33\npv4,nv41,nv42,nv43"),
				[]byte("pk,n4,n5,n6\npv1,nv14,nv15,nv16\npv1,nv24,nv25,nv26\npv3,nv34,nv35,nv36\npv4,nv44,nv45,nv46"),
			},
			outs: nil,
			err:  "pk was not equal",
		}, { // normal check.
			inputs: [][]byte{
				[]byte("pk,n1,n2,n3\n'pv1','nv11'"),
				[]byte("pk,n4,n5,n6\n'pv1','nv14','nv15','nv16'"),
			},
			outs: []byte("pk,n1,n2,n3,n4,n5,n6\n'pv1','nv11','nv14','nv15','nv16'"),
			err:  "",
		}, { // eow is '|@|'
			inputs: [][]byte{
				[]byte("pk|@|n1|@|n2|@|n3\n'pv1'|@|'nv11'"),
				[]byte("pk|@|n4|@|n5|@|n6\n'pv1'|@|'nv14'|@|'nv15'|@|'nv16'"),
			},
			outs: []byte("pk|@|n1|@|n2|@|n3|@|n4|@|n5|@|n6\n'pv1'|@|'nv11'|@|'nv14'|@|'nv15'|@|'nv16'"),
			err:  "",
			arg: &CMArgs{
				eol:  g_eol,
				eow:  []byte("|@|"),
				wrap: g_arg.wrap,
			},
		}, { // eow is '|@|'
			inputs: [][]byte{
				[]byte("1|@|a1|@|q1\n2|@|a2|@|q2\n3|@|a3|@|q3\n"),
				[]byte("1|@|z1|@|x1\n2|@|z2|@|x2\n3|@|z3|@|x3\n"),
			},
			outs: []byte("1|@|a1|@|q1|@|z1|@|x1\n2|@|a2|@|q2|@|z2|@|x2\n3|@|a3|@|q3|@|z3|@|x3"),
			err:  "",
			arg: &CMArgs{
				eol:  g_eol,
				eow:  []byte("|@|"),
				wrap: g_arg.wrap,
			},
		},
	}

	for idx, ckcm := range ckcms {
		checkMerge(t, &ckcm, idx)
	}
}

func checkMerge(t *testing.T, ckcm *checkCM, idx int) {
	var cm CSVMerge
	cm.arg = g_arg
	if ckcm.arg != nil {
		cm.arg = *ckcm.arg
	}
	// output
	var buf bytes.Buffer
	cm.output = &ioWC{&buf}
	// input
	for idx, in := range ckcm.inputs {
		rc := newRC(in)
		cmf := CMFile{
			name: fmt.Sprintf("test_%d", idx),
			fp:   rc,
			rd:   bufio.NewReader(rc),
			arg:  &cm.arg,
		}
		cm.fps = append(cm.fps, &cmf)
	}
	// merge;
	err := cm.Merge()
	// check;

	if len(ckcm.err) > 0 {
		if !strings.HasPrefix(err.Error(), ckcm.err) {
			t.Errorf("%d : %s;expect=%s", idx, err.Error(), ckcm.err)
		}
	} else {
		assert.Nil(t, err)
		outdata := buf.Bytes()
		assert.Equal(t, string(ckcm.outs), string(outdata))
		assert.Equal(t, ckcm.outs, outdata)
	}
}
