package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/gdamore/tcell"
	"github.com/mattn/go-isatty"
)

var scr tcell.Screen
var csvMode bool
var rawMode bool

type result struct {
	wpm      int
	cpm      int
	accuracy float64
}

var results []result

func readConfig() map[string]string {
	cfg := map[string]string{}

	home, _ := os.LookupEnv("HOME")
	path := filepath.Join(home, ".ttrc")

	if b, err := ioutil.ReadFile(path); err == nil {
		for _, ln := range bytes.Split(b, []byte("\n")) {
			a := strings.SplitN(string(ln), ":", 2)
			if len(a) == 2 {
				cfg[a[0]] = strings.Trim(a[1], " ")
			}
		}
	}

	return cfg
}

func exit() {
	scr.Fini()

	if csvMode {
		for _, r := range results {
			fmt.Printf("%d,%d,%.2f\n", r.wpm, r.cpm, r.accuracy)
		}
	}

	os.Exit(0)
}

func showReport(scr tcell.Screen, cpm, wpm int, accuracy float64) {
	report := fmt.Sprintf("WPM: %d\nCPM: %d\nAccuracy: %.2f%%\n", wpm, cpm, accuracy)

	scr.Clear()
	drawCellsAtCenter(scr, stringToCells(report), -1)
	scr.HideCursor()
	scr.Show()

	for {
		if key, ok := scr.PollEvent().(*tcell.EventKey); ok && key.Key() == tcell.KeyEscape {
			return
		} else if ok && key.Key() == tcell.KeyCtrlC {
			exit()
		}
	}
}

func main() {
	var n int
	var contentFn func() []string
	var oneShotMode bool
	var wrapSz int
	var err error

	flag.IntVar(&n, "n", 50, "The number of random words which constitute the test.")
	flag.IntVar(&wrapSz, "w", 80, "Wraps the input text at the given number of columns (ignored if -raw is present)")

	flag.BoolVar(&csvMode, "csv", false, "Print the test results to stdout in the form <wpm>,<cpm>,<accuracy>.")
	flag.BoolVar(&rawMode, "raw", false, "Don't reflow text or show one paragraph at a time.")
	flag.BoolVar(&oneShotMode, "o", false, "Automatically exit after a single run.")

	flag.Usage = func() {
		fmt.Println(`Usage: tt [options]

  By default tt creates a test consisting of 50 random words. Arbitrary text
  can also be piped directly into the program to create a custom test. Each
  paragraph of the input is treated as a segment of the test.
  
  E.G
  
  shuf -n 40 /etc/dictionaries-common/words|tt
  
  Note that linebreaks are determined exclusively by the input if -raw is specified.
  
Keybindings:
  <esc> Restarts the test
  <C-c> Terminates tt
  <C-backspace> Deletes the previous word
  
Options:`)

		flag.PrintDefaults()
	}
	flag.Parse()

	if !isatty.IsTerminal(os.Stdin.Fd()) {
		b, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			panic(err)
		}

		if rawMode {
			contentFn = func() []string { return []string{string(b)} }
		} else {
			s := strings.Replace(string(b), "\r", "", -1)
			s = regexp.MustCompile("\n\n+").ReplaceAllString(s, "\n\n")
			content := strings.Split(strings.Trim(s, "\n"), "\n\n")

			for i, _ := range content {
				content[i] = strings.Replace(wordWrap(strings.Trim(content[i], " "), wrapSz), "\n", " \n", -1)
			}

			contentFn = func() []string { return content }
		}
	} else {
		contentFn = func() []string { return []string{randomText(n, wrapSz)} }
	}

	scr, err = tcell.NewScreen()
	if err != nil {
		panic(err)
	}

	if err := scr.Init(); err != nil {
		panic(err)
	}

	fgcol := newTcellColor("#8C8C8C")
	bgcol := newTcellColor("#282828")

	hicol2 := newTcellColor("#805b13")
	hicol3 := newTcellColor("#b4801b")
	hicol := newTcellColor("#ffffff")
	errcol := newTcellColor("#a10705")

	cfg := readConfig()
	if c, ok := cfg["bgcol"]; ok {
		bgcol = newTcellColor(c)
	}
	if c, ok := cfg["fgcol"]; ok {
		fgcol = newTcellColor(c)
	}
	if c, ok := cfg["hicol"]; ok {
		hicol = newTcellColor(c)
	}
	if c, ok := cfg["hicol2"]; ok {
		hicol2 = newTcellColor(c)
	}
	if c, ok := cfg["hicol3"]; ok {
		hicol3 = newTcellColor(c)
	}
	if c, ok := cfg["errcol"]; ok {
		errcol = newTcellColor(c)
	}

	typer := NewTyper(scr, fgcol, bgcol, hicol, hicol2, hicol3, errcol)

	for {
		scr.Clear()
		nerrs, ncorrect, t, exitKey := typer.Start(contentFn())

		switch exitKey {
		case 0:
			cpm := int(float64(ncorrect) / (float64(t) / 60E9))
			wpm := cpm / 5
			accuracy := float64(ncorrect) / float64(nerrs+ncorrect) * 100

			results = append(results, result{wpm, cpm, accuracy})
			if oneShotMode {
				exit()
			}
			showReport(scr, cpm, wpm, accuracy)
		case tcell.KeyCtrlC:
			exit()
		}
	}
}