package mscfb

import (
	"bytes"
	"io"
	"log"
	"os"
	"sync"
	"testing"
)

var (
	novPapPlan = "test/novpapplan.doc"
	testDoc    = "test/test.doc"
	testXls    = "test/test.xls"
	testPpt    = "test/test.ppt"
	testMsg    = "test/test.msg"
	//   Root
	//    |
	//  Alpha -- Bravo -- -- -- -- -- Charlie
	//             |                     |
	//  Delta --  Echo -- Foxtrot      Golf
	//             |                     |
	//  Hotel -- Indigo                Jello
	//             |
	//           Kilo
	// Expected paths:
	// Root, Alpha, Bravo, Bravo/Echo, Bravo/Delta, Bravo/Echo/Indigo, Bravo/Echo/Hotel,
	// Bravo/Echo/Indigo/Kilo, Bravo/Foxtrot, Charlie, Charlie/Golf, Charlie/Golf/Jello
	testEntries = []*File{
		{Name: "Root Node",
			directoryEntryFields: &directoryEntryFields{leftSibID: noStream, rightSibID: noStream, childID: 1},
		},
		{Name: "Alpha",
			directoryEntryFields: &directoryEntryFields{leftSibID: noStream, rightSibID: 2, childID: noStream},
		},
		{Name: "Bravo",
			directoryEntryFields: &directoryEntryFields{leftSibID: noStream, rightSibID: 3, childID: 5},
		},
		{Name: "Charlie",
			directoryEntryFields: &directoryEntryFields{leftSibID: noStream, rightSibID: noStream, childID: 7},
		},
		{Name: "Delta",
			directoryEntryFields: &directoryEntryFields{leftSibID: noStream, rightSibID: noStream, childID: noStream},
		},
		{Name: "Echo",
			directoryEntryFields: &directoryEntryFields{leftSibID: 4, rightSibID: 6, childID: 9},
		},
		{Name: "Foxtrot",
			directoryEntryFields: &directoryEntryFields{leftSibID: noStream, rightSibID: noStream, childID: noStream},
		},
		{Name: "Golf",
			directoryEntryFields: &directoryEntryFields{leftSibID: noStream, rightSibID: noStream, childID: 10},
		},
		{Name: "Hotel",
			directoryEntryFields: &directoryEntryFields{leftSibID: noStream, rightSibID: noStream, childID: noStream},
		},
		{Name: "Indigo",
			directoryEntryFields: &directoryEntryFields{leftSibID: 8, rightSibID: noStream, childID: 11},
		},
		{Name: "Jello",
			directoryEntryFields: &directoryEntryFields{leftSibID: noStream, rightSibID: noStream, childID: noStream},
		},
		{Name: "Kilo",
			directoryEntryFields: &directoryEntryFields{leftSibID: noStream, rightSibID: noStream, childID: noStream},
		},
	}
)

func testFile(t *testing.T, path string) {
	file, _ := os.Open(path)
	defer file.Close()
	doc, err := New(file)
	if err != nil {
		t.Fatalf("Error opening file; Returns error: %v", err)
	}
	if len(doc.File) < 3 {
		t.Fatalf("Expecting several directory entries, only got %d", len(doc.File))
	}
	buf := make([]byte, 512)
	for entry, _ := doc.Next(); entry != nil; entry, _ = doc.Next() {
		_, err := doc.Read(buf)
		if err != nil && err != io.EOF {
			t.Errorf("Error reading entry name, %v", entry.Name)
		}
		if len(entry.Name) < 1 {
			t.Errorf("Error reading entry name")
		}
	}
}

func TestTraverse(t *testing.T) {
	r := new(Reader)
	r.direntries = testEntries
	if r.traverse() != nil {
		t.Error("Error traversing")
	}
	expect := []int{0, 1, 2, 4, 5, 8, 9, 11, 6, 3, 7, 10}
	if len(r.File) != len(expect) {
		t.Fatalf("Error traversing: expecting %d entries, got %d", len(expect), len(r.File))
	}
	for i, v := range r.File {
		if v != testEntries[expect[i]] {
			t.Errorf("Error traversing: expecting %d at index %d; got %v", expect[i], i, v)
		}
	}
	// expect Bravo/Echo/Indigo
	if len(r.File[5].Path) != 2 {
		t.Fatalf("Error traversing: expecting a path length of %d, got %d", 2, len(r.File[4].Path))
	}
	if r.File[5].Path[0] != "Bravo" {
		t.Fatalf("Error traversing: expected path Bravo/Echo, got %s", r.File[4].Path[0])
	}
	if r.File[5].Path[1] != "Echo" {
		t.Fatalf("Error traversing: expected path Bravo/Echo, got %s", r.File[4].Path[1])
	}

	// test last path
	if len(r.File[len(r.File)-1].Path) != 2 {
		t.Fatalf("Error traversing: expecting a path length of %d, got %d", 2, len(r.File[len(r.File)-1].Path))
	}
	if r.File[len(r.File)-1].Path[0] != "Charlie" {
		t.Errorf("Error traversing: expecting Charlie got %s", r.File[len(r.File)-1].Path[0])
	}
	if r.File[len(r.File)-1].Path[1] != "Golf" {
		t.Errorf("Error traversing: expecting Golf got %s", r.File[len(r.File)-1].Path[1])
	}
}

func TestNovPapPlan(t *testing.T) {
	testFile(t, novPapPlan)
}

func TestWord(t *testing.T) {
	testFile(t, testDoc)
}

func TestMsg(t *testing.T) {
	testFile(t, testMsg)
}

func TestPpt(t *testing.T) {
	testFile(t, testPpt)
}

func TestXls(t *testing.T) {
	testFile(t, testXls)
}

func TestConcurrentAccess(t *testing.T) {
	file, _ := os.Open(testXls)
	defer file.Close()
	doc, err := New(file)
	if err != nil {
		log.Fatal(err)
	}
	var wg sync.WaitGroup
	wg.Add(len(doc.File))
	for _, f := range doc.File {
		go func(ff *File) {
			defer wg.Done()
			_, err := io.Copy(io.Discard, ff)
			if err != nil {
				log.Println(err)
			}
		}(f)
	}
	wg.Wait()
}

func TestSeek(t *testing.T) {
	file, _ := os.Open(testXls)
	defer file.Close()
	doc, _ := New(file)
	// the third entry in the XLS file is 2719 bytes
	f := doc.File[3]
	if f.Size != 2719 {
		t.Fatalf("Expecting the third entry of the XLS file to be 2719 bytes long; it is %d", f.Size)
	}
	buf := make([]byte, 2719)
	i, err := f.Read(buf)
	if i != 2719 || err != nil {
		t.Fatalf("Expecting 2719 length and no error; got %d and %v", i, err)
	}
	s, err := f.Seek(50, 1)
	if s != 2719 || err == nil {
		t.Fatalf("%v, %d", err, s)
	}
	s, err = f.Seek(1500, 0)
	if s != 1500 || err != nil {
		t.Fatalf("Seek error: %v, %d", err, s)
	}
	nbuf := make([]byte, 475)
	i, err = f.Read(nbuf)
	if i != 475 || err != nil {
		t.Fatalf("Expecting 475 length and no error; got %d and %v", i, err)
	}
	if !bytes.Equal(buf[1500:1975], nbuf) {
		t.Fatalf("Slices not equal: %s, %s", string(buf[1500:1975]), string(nbuf))
	}
	s, err = f.Seek(5, 1)
	if s != 1980 || err != nil {
		t.Fatalf("Seek error: %v, %d", err, s)
	}
	i, err = f.Read(nbuf[:5])
	if i != 5 || err != nil {
		t.Fatalf("Expecting 5 length, and no error; got %d and %v", i, err)
	}
	if !bytes.Equal(buf[1980:1985], nbuf[:5]) {
		t.Fatalf("Slices not equal: %s, %s", string(buf[1980:1985]), string(nbuf[:5]))
	}
	s, err = f.Seek(30, 2)
	if s != 2689 || err != nil {
		t.Fatalf("Seek error: %v, %d", err, s)
	}
	i, err = f.Read(nbuf[:30])
	if i != 30 || err != nil {
		t.Fatalf("Expecting 30 length, and no error; got %d and %v", i, err)
	}
	if !bytes.Equal(buf[2689:], nbuf[:30]) {
		t.Fatalf("Slices not equal: %d, %s, %s", len(buf[2688:]), string(buf[2688:]), string(nbuf[:30]))
	}
}

func TestWrite(t *testing.T) {
	file, err := os.OpenFile(testXls, os.O_RDWR, 0666)
	if err != nil {
		t.Fatalf("error opening file for read/write %v", err)
	}
	defer file.Close()
	doc, err := New(file)
	if err != nil {
		t.Fatalf("Error opening file; Returns error: %v", err)
	}
	// the third entry in the XLS file is 2719 bytes
	f := doc.File[3]
	s, err := f.Seek(30, 0)
	if s != 30 || err != nil {
		t.Fatalf("Seek error: %v, %d", err, s)
	}
	orig := make([]byte, 4)
	i, err := f.Read(orig)
	if i != 4 || err != nil {
		t.Fatalf("Expecting read length 4, and no error, got %d %v", i, err)
	}
	s, err = f.Seek(30, 0)
	if s != 30 || err != nil {
		t.Fatalf("Seek error: %v, %d", err, s)
	}
	i, err = f.Write([]byte("test"))
	if i != 4 || err != nil {
		t.Errorf("error writing, got %d %v", i, err)
	}
	s, err = f.Seek(30, 0)
	if s != 30 || err != nil {
		t.Fatalf("Seek error: %v, %d", err, s)
	}
	res := make([]byte, 4)
	i, err = f.Read(res)
	if i != 4 || err != nil {
		t.Errorf("error reading, got %d %v", i, err)
	}
	if string(res) != "test" {
		t.Errorf("expecting test, got %s", string(res))
	}
	s, err = f.Seek(30, 0)
	if s != 30 || err != nil {
		t.Fatalf("Seek error: %v, %d", err, s)
	}
	i, err = f.Write(orig)
	if i != 4 || err != nil {
		t.Errorf("error writing, got %d %v", i, err)
	}
	s, err = f.Seek(30, 0)
	if s != 30 || err != nil {
		t.Fatalf("Seek error: %v, %d", err, s)
	}
	i, err = f.Read(res)
	if i != 4 || err != nil {
		t.Errorf("error reading, got %d %v", i, err)
	}
	if string(res) != string(orig) {
		t.Errorf("bad result, expected %s, got %s", string(orig), string(res))
	}
	i, err = f.WriteAt([]byte("test"), 30)
	if i != 4 || err != nil {
		t.Errorf("error writing, got %d %v", i, err)
	}
	i, err = f.ReadAt(res, 30)
	if i != 4 || err != nil {
		t.Errorf("error reading, got %d %v", i, err)
	}
	if string(res) != "test" {
		t.Errorf("expecting test, got %s", string(res))
	}
	i, err = f.WriteAt(orig, 30)
	if i != 4 || err != nil {
		t.Errorf("error writing, got %d %v", i, err)
	}
	i, err = f.ReadAt(res, 30)
	if i != 4 || err != nil {
		t.Errorf("error reading, got %d %v", i, err)
	}
	if string(res) != string(orig) {
		t.Errorf("bad result, expected %s, got %s", string(orig), string(res))
	}
}

func benchFile(b *testing.B, path string) {
	b.StopTimer()
	buf, _ := os.ReadFile(path)
	entrybuf := make([]byte, 32000)
	b.StartTimer()
	rdr := bytes.NewReader(buf)
	for i := 0; i < b.N; i++ {
		doc, _ := New(rdr)
		for entry, _ := doc.Next(); entry != nil; entry, _ = doc.Next() {
			doc.Read(entrybuf)
		}
	}
}

func BenchmarkNovPapPlan(b *testing.B) {
	benchFile(b, novPapPlan)
}

func BenchmarkWord(b *testing.B) {
	benchFile(b, testDoc)
}

func BenchmarkMsg(b *testing.B) {
	benchFile(b, testMsg)
}

func BenchmarkPpt(b *testing.B) {
	benchFile(b, testPpt)
}

func BenchmarkXls(b *testing.B) {
	benchFile(b, testXls)
}

/*
22/12
	BenchmarkNovPapPlan	   50000	     31676 ns/op
	BenchmarkWord	   20000	     65693 ns/op
	BenchmarkMsg	   10000	    198380 ns/op
	BenchmarkPpt	   50000	     30156 ns/op
	BenchmarkXls	  100000	     20327 ns/op
*/
