package goblin

import (
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"
)

type Reporter interface {
	BeginDescribe(string)
	EndDescribe()
	Begin()
	End()
	Failure(*Failure)
	ItTook(time.Duration)
	ItFailed(string)
	ItPassed(string)
	ItIsPending(string)
	ItIsExcluded(string)
}

type TextFancier interface {
	Red(text string) string
	Gray(text string) string
	Cyan(text string) string
	Green(text string) string
	Yellow(text string) string
	WithCheck(text string) string
}

type DetailedReporter struct {
	level, failed, passed, pending, excluded int
	failures                                 []*Failure
	executionTime, totalExecutionTime        time.Duration
	executionTimeMu                          sync.RWMutex
	fancy                                    TextFancier
}

func (r *DetailedReporter) SetTextFancier(f TextFancier) {
	r.fancy = f
}

type TerminalFancier struct {
}

func (self *TerminalFancier) Red(text string) string {
	return "\033[31m" + text + "\033[0m"
}

func (self *TerminalFancier) Gray(text string) string {
	return "\033[90m" + text + "\033[0m"
}

func (self *TerminalFancier) Cyan(text string) string {
	return "\033[36m" + text + "\033[0m"
}

func (self *TerminalFancier) Green(text string) string {
	return "\033[32m" + text + "\033[0m"
}

func (self *TerminalFancier) Yellow(text string) string {
	return "\033[33m" + text + "\033[0m"
}

func (self *TerminalFancier) WithCheck(text string) string {
	return "\033[32m\u2713\033[0m " + text
}

func (r *DetailedReporter) getSpace() string {
	return strings.Repeat(" ", (r.level+1)*2)
}

func (r *DetailedReporter) Failure(failure *Failure) {
	r.failures = append(r.failures, failure)
}

func (r *DetailedReporter) print(text string) {
	fmt.Printf("%v%v\n", r.getSpace(), text)
}

func (r *DetailedReporter) printWithCheck(text string) {
	fmt.Printf("%v%v\n", r.getSpace(), r.fancy.WithCheck(text))
}

func (r *DetailedReporter) BeginDescribe(name string) {
	fmt.Println("")
	r.print(name)
	r.level++
}

func (r *DetailedReporter) EndDescribe() {
	r.level--
}

func (r *DetailedReporter) ItTook(duration time.Duration) {
	r.executionTimeMu.Lock()
	defer r.executionTimeMu.Unlock()
	r.executionTime = duration
	r.totalExecutionTime += duration
}

func (r *DetailedReporter) ItFailed(name string) {
	r.failed++
	r.print(r.fancy.Red(strconv.Itoa(r.failed) + ") " + name))
}

func (r *DetailedReporter) ItPassed(name string) {
	r.passed++
	r.printWithCheck(r.fancy.Gray(name))
}

func (r *DetailedReporter) ItIsPending(name string) {
	r.pending++
	r.print(r.fancy.Cyan("- " + name))
}

func (r *DetailedReporter) ItIsExcluded(name string) {
	r.excluded++
	r.print(r.fancy.Yellow("- " + name))
}

func (r *DetailedReporter) Begin() {
}

func (r *DetailedReporter) End() {
	comp := fmt.Sprintf("%d tests complete", r.passed)

	r.executionTimeMu.RLock()
	t := fmt.Sprintf("(%d ms)", r.totalExecutionTime/time.Millisecond)
	r.executionTimeMu.RUnlock()

	//fmt.Printf("\n\n \033[32m%d tests complete\033[0m \033[90m(%d ms)\033[0m\n", r.passed, r.totalExecutionTime/time.Millisecond)
	fmt.Printf("\n\n %v %v\n", r.fancy.Green(comp), r.fancy.Gray(t))

	if r.pending > 0 {
		pend := fmt.Sprintf("%d test(s) pending", r.pending)
		fmt.Printf(" %v\n\n", r.fancy.Cyan(pend))
	}

	if r.excluded > 0 {
		excl := fmt.Sprintf("%d test(s) excluded", r.excluded)
		fmt.Printf(" %v\n\n", r.fancy.Yellow(excl))
	}

	if len(r.failures) > 0 {
		fmt.Printf("%s \n\n", r.fancy.Red(fmt.Sprintf(" %d tests failed:", len(r.failures))))

	}

	for i, failure := range r.failures {
		fmt.Printf("  %d) %s:\n\n", i+1, failure.TestName)
		fmt.Printf("    %s\n", r.fancy.Red(failure.Message))
		for _, stackItem := range failure.Stack {
			fmt.Printf("    %s\n", r.fancy.Gray(stackItem))
		}
	}
}
