package testutil

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"runtime/debug"
	"strconv"
	"strings"

	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/util"
)

// TestingT is a subset of the functionality provided by testing.T.
type TestingT interface {
	Logf(string, ...interface{})
	Skipf(string, ...interface{})
	Errorf(string, ...interface{})
	FailNow()
}

type MarkdownTestCase struct {
	No       int
	Markdown string
	Expected string
}

const attributeSeparator = "//- - - - - - - - -//"
const caseSeparator = "//= = = = = = = = = = = = = = = = = = = = = = = =//"

func DoTestCaseFile(m goldmark.Markdown, filename string, t TestingT) {
	fp, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	defer fp.Close()

	scanner := bufio.NewScanner(fp)
	c := MarkdownTestCase{
		No:       -1,
		Markdown: "",
		Expected: "",
	}
	cases := []MarkdownTestCase{}
	line := 0
	for scanner.Scan() {
		line++
		if util.IsBlank([]byte(scanner.Text())) {
			continue
		}
		c.No, err = strconv.Atoi(scanner.Text())
		if err != nil {
			panic(fmt.Sprintf("%s: invalid case No at line %d", filename, line))
		}
		if !scanner.Scan() {
			panic(fmt.Sprintf("%s: invalid case at line %d", filename, line))
		}
		line++
		if scanner.Text() != attributeSeparator {
			panic(fmt.Sprintf("%s: invalid separator '%s' at line %d", filename, scanner.Text(), line))
		}
		buf := []string{}
		for scanner.Scan() {
			line++
			text := scanner.Text()
			if text == attributeSeparator {
				break
			}
			buf = append(buf, text)
		}
		c.Markdown = strings.Join(buf, "\n")
		buf = []string{}
		for scanner.Scan() {
			line++
			text := scanner.Text()
			if text == caseSeparator {
				break
			}
			buf = append(buf, text)
		}
		c.Expected = strings.Join(buf, "\n")
		cases = append(cases, c)
	}
	DoTestCases(m, cases, t)
}

func DoTestCases(m goldmark.Markdown, cases []MarkdownTestCase, t TestingT) {
	for _, testCase := range cases {
		DoTestCase(m, testCase, t)
	}
}

func DoTestCase(m goldmark.Markdown, testCase MarkdownTestCase, t TestingT) {
	var ok bool
	var out bytes.Buffer
	defer func() {
		if err := recover(); err != nil {
			format := `============= case %d ================
Markdown:
-----------
%s

Expected:
----------
%s

Actual
---------
%v
%s
`
			t.Errorf(format, testCase.No, testCase.Markdown, testCase.Expected, err, debug.Stack())
		} else if !ok {
			format := `============= case %d ================
Markdown:
-----------
%s

Expected:
----------
%s

Actual
---------
%s
`
			t.Errorf(format, testCase.No, testCase.Markdown, testCase.Expected, out.Bytes())
		}
	}()

	if err := m.Convert([]byte(testCase.Markdown), &out); err != nil {
		panic(err)
	}
	ok = bytes.Equal(bytes.TrimSpace(out.Bytes()), bytes.TrimSpace([]byte(testCase.Expected)))
}
