package cnmfmt

import (
	"bytes"
	"io"
	"strings"
	"testing"

	"contnet.org/lib/cnm-go"
)

var parseTests = map[string]Text{
	"\\nfoo\nbar\\": Text{[]Span{
		Span{Format{}, "\nfoo bar\\"},
	}},
	"**foo": Text{[]Span{
		Span{Format{Bold: true}, "foo"},
	}},
	"//foo": Text{[]Span{
		Span{Format{Italic: true}, "foo"},
	}},
	"__foo": Text{[]Span{
		Span{Format{Underline: true}, "foo"},
	}},
	"``foo": Text{[]Span{
		Span{Format{Monospace: true}, "foo"},
	}},
	"foo*bar": Text{[]Span{
		Span{Format{}, "foo*bar"},
	}},
	"foo*": Text{[]Span{
		Span{Format{}, "foo*"},
	}},
	"foo**": Text{[]Span{
		Span{Format{}, "foo"},
	}},
	"foo***": Text{[]Span{
		Span{Format{}, "foo"},
		Span{Format{Bold: true}, "*"},
	}},
	"foo****": Text{[]Span{
		Span{Format{}, "foo"},
	}},
	"*foo": Text{[]Span{
		Span{Format{}, "*foo"},
	}},
	"****foo": Text{[]Span{
		Span{Format{}, "foo"},
	}},
	"******foo": Text{[]Span{
		Span{Format{Bold: true}, "foo"},
	}},
	"foo ** bar": Text{[]Span{
		Span{Format{}, "foo "},
		Span{Format{Bold: true}, " bar"},
	}},
	"foo** bar": Text{[]Span{
		Span{Format{}, "foo"},
		Span{Format{Bold: true}, " bar"},
	}},
	"foo **bar": Text{[]Span{
		Span{Format{}, "foo "},
		Span{Format{Bold: true}, "bar"},
	}},
	"foo ** bar ** baz": Text{[]Span{
		Span{Format{}, "foo "},
		Span{Format{Bold: true}, " bar "},
		Span{Format{}, " baz"},
	}},
	"foo ** bar** baz": Text{[]Span{
		Span{Format{}, "foo "},
		Span{Format{Bold: true}, " bar"},
		Span{Format{}, " baz"},
	}},
	"**__**foo": Text{[]Span{
		Span{Format{Underline: true}, "foo"},
	}},
	"***": Text{[]Span{
		Span{Format{Bold: true}, "*"},
	}},
	"*\\**": Text{[]Span{
		Span{Format{}, "***"},
	}},
	"\\*": Text{[]Span{
		Span{Format{}, "*"},
	}},
	"\\*\\*": Text{[]Span{
		Span{Format{}, "**"},
	}},
	"\\**": Text{[]Span{
		Span{Format{}, "**"},
	}},
	"*\\*": Text{[]Span{
		Span{Format{}, "**"},
	}},
	"\\": Text{[]Span{
		Span{Format{}, "\\"},
	}},
	"\\\\": Text{[]Span{
		Span{Format{}, "\\"},
	}},
	" ** // `` ": Text{[]Span{
		Span{Format{Bold: true}, " "},
		Span{Format{Bold: true, Italic: true}, " "},
	}},
	"**": Text{[]Span{}},
	"**``__//foo": Text{[]Span{
		Span{Format{Bold: true, Monospace: true, Underline: true, Italic: true}, "foo"},
	}},
	"**foo//bar**baz": Text{[]Span{
		Span{Format{Bold: true}, "foo"},
		Span{Format{Bold: true, Italic: true}, "bar"},
		Span{Format{Italic: true}, "baz"},
	}},
	"@@foo": Text{[]Span{
		Span{Format{Link: "foo"}, ""},
	}},
	"@@foo@@": Text{[]Span{
		Span{Format{Link: "foo"}, ""},
	}},
	"@@foo bar@@": Text{[]Span{
		Span{Format{Link: "foo"}, "bar"},
	}},
	"@@  foo": Text{[]Span{
		Span{Format{Link: "foo"}, ""},
	}},
	"@@foo  ": Text{[]Span{
		Span{Format{Link: "foo"}, ""},
	}},
	"@@foo\\": Text{[]Span{
		Span{Format{Link: "foo\\"}, ""},
	}},
	"@@foo \\": Text{[]Span{
		Span{Format{Link: "foo"}, "\\"},
	}},
	"@@foo \\\\": Text{[]Span{
		Span{Format{Link: "foo"}, "\\"},
	}},
	"@@foo@": Text{[]Span{
		Span{Format{Link: "foo@"}, ""},
	}},
	"@@foo\\@@": Text{[]Span{
		Span{Format{Link: "foo@@"}, ""},
	}},
	"@@f\\\\o\\o\\n @": Text{[]Span{
		Span{Format{Link: "f\\o\\o\n"}, "@"},
	}},
	"@@http://example.com foo **bar @@baz**": Text{[]Span{
		Span{Format{Link: "http://example.com"}, "foo "},
		Span{Format{Bold: true, Link: "http://example.com"}, "bar "},
		Span{Format{Bold: true}, "baz"},
	}},
	"//@@http://example.com foo //bar @@": Text{[]Span{
		Span{Format{Italic: true, Link: "http://example.com"}, "foo "},
		Span{Format{Link: "http://example.com"}, "bar "},
	}},
	"__\\  asd \\ zxc\\ ": Text{[]Span{
		Span{Format{Underline: true, Monospace: false}, "  asd  zxc "},
	}},
	"@@/ test/@@": Text{[]Span{
		Span{Format{Link: "/"}, "test/"},
	}},
	"@@/ /test@@": Text{[]Span{
		Span{Format{Link: "/"}, "/test"},
	}},
	"/": Text{[]Span{
		Span{Format{}, "/"},
	}},
	"test/**": Text{[]Span{
		Span{Format{}, "test/"},
	}},
	"//test/": Text{[]Span{
		Span{Format{Italic: true}, "test/"},
	}},
	"/**test": Text{[]Span{
		Span{Format{}, "/"},
		Span{Format{Bold: true}, "test"},
	}},
}

func TestParseParagraph(t *testing.T) {
	for k, v := range parseTests {
		t.Run(k, func(t *testing.T) {
			txt := ParseParagraph(k)
			if !textEqual(txt, v) {
				t.Errorf("ParseParagraph(%q):\nexpected: %#v\n     got: %#v", k, v, txt)
			}
		})
	}
}

func TestParse(t *testing.T) {
	for k, v := range parseTests {
		t.Run(k, func(t *testing.T) {
			txts := Parse(k)
			if len(txts) != 1 || !textEqual(txts[0], v) {
				t.Errorf("Parse(%q):\nexpected: %#v\n     got: %#v", k, []Text{v}, txts)
			}
		})
	}
}

func textEqual(a, b Text) bool {
	if len(a.Spans) != len(b.Spans) {
		return false
	}
	for i := range a.Spans {
		if a.Spans[i] != b.Spans[i] {
			return false
		}
	}
	return true
}

var escapeTests = map[string]string{
	"\n\r\t\v\x00":     "\\n\\r\\t\v\\x00",
	"@@!!##__//__``**": "\\@\\@!!##\\_\\_\\/\\/\\_\\_\\`\\`\\*\\*",
	`foo\@\@bar`:       `foo\\\@\\\@bar`,
}

func TestEscape(t *testing.T) {
	for k, v := range escapeTests {
		t.Run(k, func(t *testing.T) {
			if e := Escape(k); e != v {
				t.Errorf("Escape(%q): expected %q, got %q", k, v, e)
			}
		})
	}
}

var parseTextTests = map[string]TextFmtContents{
	"foo  ** bar\nbaz\n\n\nquux ** ": TextFmtContents{[]Text{
		Text{[]Span{
			Span{Format{}, "foo "},
			Span{Format{Bold: true}, " bar baz"},
		}},
		Text{[]Span{
			Span{Format{}, "quux "},
		}},
	}},

	"\n": TextFmtContents{},

	"foo": TextFmtContents{[]Text{
		Text{[]Span{
			Span{Format{}, "foo"},
		}},
	}},

	"\n\n": TextFmtContents{},

	"foo\n\t\t\t\t\nbar": TextFmtContents{[]Text{
		Text{[]Span{Span{Format{}, "foo"}}},
		Text{[]Span{Span{Format{}, "bar"}}},
	}},

	"foo\n\t\t   \f\r\t\nbar": TextFmtContents{[]Text{
		Text{[]Span{Span{Format{}, "foo"}}},
		Text{[]Span{Span{Format{}, "bar"}}},
	}},

	`foo**bar\*\*baz\*\*quux**qweasd`: TextFmtContents{[]Text{Text{[]Span{
		Span{Format{}, "foo"},
		Span{Format{Bold: true}, "bar**baz**quux"},
		Span{Format{}, "qweasd"},
	}}}},
}

func TestParseTextFmt(t *testing.T) {
	for k, v := range parseTextTests {
		t.Run(k, func(t *testing.T) {
			parser := cnm.NewParser(strings.NewReader(k))
			err := parser.Next()
			if err != nil && err != io.EOF {
				t.Fatalf("error parsing %q: %v", k, err)
			}
			content, err := parseTextFmt(parser, cnm.TopLevel)
			if err != nil && err != io.EOF {
				t.Fatalf("error parsing %q: %v", k, err)
			}
			tf, ok := content.(TextFmtContents)
			if !ok {
				t.Fatalf("%q: expected type %T, got %T", k, v, content)
			}
			if !paragraphsEqual(v.Paragraphs, tf.Paragraphs) {
				t.Fatalf("%q:\nexpected: %#v\n     got: %#v", k, v, tf)
			}
			txts := Parse(k)
			if !paragraphsEqual(txts, v.Paragraphs) {
				t.Fatalf("%q:\nexpected: %#v\n     got: %#v", k, v.Paragraphs, txts)
			}
		})
	}
}

func paragraphsEqual(a, b []Text) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if !textEqual(a[i], b[i]) {
			return false
		}
	}
	return true
}

var writeTests = map[string]TextFmtContents{
	"": TextFmtContents{},

	"foo\n": TextFmtContents{[]Text{
		Text{[]Span{
			Span{Format{}, "foo"},
		}},
	}},

	"**foo\n": TextFmtContents{[]Text{
		Text{[]Span{
			Span{Format{Bold: true}, "foo"},
		}},
	}},

	"foo **bar baz\n\nquux\n": TextFmtContents{[]Text{
		Text{[]Span{
			Span{Format{}, "foo "},
			Span{Format{Bold: true}, "bar baz"},
		}},
		Text{[]Span{
			Span{Format{}, "quux"},
		}},
	}},

	"foo**bar``baz**quux\n\n" +
		"\\ __qwe\\ __//\\  asd \\ //``zxc``**\\ \n\n" +
		"//@@http://example.com exa//mple@@ @@href text@@// test\n": TextFmtContents{[]Text{
		Text{[]Span{
			Span{Format{}, "foo"},
			Span{Format{Bold: true}, "bar"},
			Span{Format{Bold: true, Monospace: true}, "baz"},
			Span{Format{Monospace: true}, "quux"},
		}},
		Text{[]Span{
			Span{Format{}, " "},
			Span{Format{Underline: true}, "qwe "},
			Span{Format{Italic: true}, "  asd  "},
			Span{Format{Monospace: true}, "zxc"},
			Span{Format{Bold: true}, " "},
		}},
		Text{[]Span{
			Span{Format{Italic: true, Link: "http://example.com"}, "exa"},
			Span{Format{Link: "http://example.com"}, "mple"},
			Span{Format{}, " "},
			Span{Format{Link: "href"}, "text"},
			Span{Format{Italic: true}, " test"},
		}},
	}},

	"foo**bar\\*\\*baz\\*\\*quux**qweasd\n": TextFmtContents{[]Text{Text{[]Span{
		Span{Format{}, "foo"},
		Span{Format{Bold: true}, "bar**baz**quux"},
		Span{Format{}, "qweasd"},
	}}}},
}

func TestWriteTextFmt(t *testing.T) {
	for k, v := range writeTests {
		t.Run(k, func(t *testing.T) {
			var buf bytes.Buffer
			err := v.WriteIndent(&buf, 0)
			if err != nil {
				t.Fatalf("WriteIndent error: %v", err)
			}
			w := buf.String()
			t.Log("expected:\n" + k)
			t.Log("     got:\n" + w)
			if k != w {
				t.Fatalf("WriteIndent: output did not match expected document:\nexpected: %q\n     got: %q", k, w)
			}
		})
	}
}

func TestWriteParseTextFmt(t *testing.T) {
	for k, v := range writeTests {
		t.Run(k, func(t *testing.T) {
			var buf bytes.Buffer
			err := v.WriteIndent(&buf, 0)
			if err != nil {
				t.Fatalf("WriteIndent error: %v", err)
			}
			w := buf.String()

			if w == "" {
				w = "\n"
			}
			parser := cnm.NewParser(strings.NewReader(w))
			err = parser.Next()
			if err != nil && err != io.EOF {
				t.Fatalf("error parsing %q: %v", w, err)
			}
			content, err := parseTextFmt(parser, cnm.TopLevel)
			if err != nil && err != io.EOF {
				t.Fatalf("error parsing %q: %v", w, err)
			}
			tf, ok := content.(TextFmtContents)
			if !ok {
				t.Fatalf("%q: expected type %T, got %T", w, v, content)
			}
			if !paragraphsEqual(v.Paragraphs, tf.Paragraphs) {
				t.Fatalf("%q:\nexpected: %#v\n     got: %#v", k, v, tf)
			}
		})
	}
}

func TestParseWriteTextFmt(t *testing.T) {
	for k, v := range writeTests {
		t.Run(k, func(t *testing.T) {
			s := k
			if s == "" {
				s = "\n"
			}
			parser := cnm.NewParser(strings.NewReader(s))
			err := parser.Next()
			if err != nil && err != io.EOF {
				t.Fatalf("error parsing %q: %v", k, err)
			}

			content, err := parseTextFmt(parser, cnm.TopLevel)
			if err != nil && err != io.EOF {
				t.Fatalf("error parsing %q: %v", k, err)
			}
			tf, ok := content.(TextFmtContents)
			if !ok {
				t.Fatalf("%q: expected type %T, got %T", k, v, content)
			}
			if !paragraphsEqual(tf.Paragraphs, v.Paragraphs) {
				t.Fatalf("%q: expected %#v, got %#v", k, v, tf)
			}

			var buf bytes.Buffer
			err = tf.WriteIndent(&buf, 0)
			if err != nil {
				t.Fatalf("WriteIndent error: %v", err)
			}

			w := buf.String()
			/*if w == "\n" {
				k = ""
			}*/

			if k != w {
				t.Fatalf("%q:\nexpected: %#v\n     got: %#v", k, k, w)
			}
		})
	}
}