package main

import (
	"database/sql"
	"fmt"
	"math/rand"
	"os"
	"path/filepath"
	"red-green/door"
	"regexp"
	"strconv"
	"strings"
	"time"
	"unicode"

	_ "github.com/mattn/go-sqlite3"
	"github.com/seehuhn/mt19937"
)

func pctUpdate(pct *int64) func() int64 {
	return func() int64 {
		return *pct
	}
}

// Can I add a function to Door?
// NO:  cannot define new methods on non-local type door.Door

/*
func (d *door.Door) PressAKey() {
	d.Write(door.Reset + door.CRNL + "Press a key to continue...")
	d.Key()
	d.Write(door.CRNL)
}
*/

func find_words(text string) [][]int {
	word, _ := regexp.Compile("([A-Za-z]+)")
	return word.FindAllStringIndex(text, -1)
}

func press_a_key(d *door.Door) int {
	green := door.ColorText("BOLD GREEN")
	blue := door.ColorText("BOLD BLUE")
	yellow := door.ColorText("BOLD YELLOW")
	any_caps := green
	any_lower := yellow

	any_color := func(text string) string {
		var output string
		var lastColor string

		for _, letter := range text {
			if unicode.IsUpper(letter) {
				if lastColor != any_caps {
					lastColor = any_caps
					output += any_caps
				}
				output += string(letter)
			} else {
				if lastColor != any_lower {
					lastColor = any_lower
					output += any_lower
				}
				output += string(letter)
			}
		}
		return output
	}

	text := "Press a key to continue..."
	work_text := []rune(text)

	d.Write(door.Reset + any_color(text))

	var r int = -1
	var t int
	sleep_ms := 250
	ms_sleep := 0

	words := find_words(text)
	var word int = 0
	var wpos int = 0
	current_word := text[words[word][0]:words[word][1]]
	t = 0

	r = d.WaitKey(0, int64(sleep_ms)*1000)

	for r == -1 {
		ms_sleep += sleep_ms

		if ms_sleep > 1000 {
			ms_sleep -= 1000
			t++
			if t >= int(door.Inactivity) {
				d.Write(strings.Repeat("\b", len(text)))
				d.Write(any_color(text) + door.CRNL)
				return -1
			}
		}
		wpos++

		if wpos == len(current_word) {
			word++
			wpos = 0
			work_text = []rune(text)

			if word == len(words) {
				word = 0
				if any_caps == green {
					any_caps = blue
				} else {
					any_caps = green
				}
				d.Write(strings.Repeat("\b", len(text)))
				d.Write(any_color(string(work_text)))
				current_word = text[words[word][0]:words[word][1]]
				goto READKEY
			}

			current_word = text[words[word][0]:words[word][1]]
		}

		{
			c := rune(current_word[wpos])

			for !unicode.IsLower(c) {
				wpos++
				if wpos == len(current_word) {
					wpos = 0
					word++
					work_text = []rune(text)
					if word == len(words) {
						word = 0
					}
					current_word = text[words[word][0]:words[word][1]]
				}
				c = rune(current_word[wpos])
			}

			// Ok, we found something that's lower case
			work_text[words[word][0]+wpos] = unicode.ToUpper(c)
		}
		d.Write(strings.Repeat("\b", len(text)))
		d.Write(any_color(string(work_text)))

	READKEY:
		r = d.WaitKey(0, int64(sleep_ms)*1000)
	}

	d.Write(strings.Repeat("\b", len(text)))
	d.Write(any_color(text))
	return r
}

func MainMenu() door.Menu {
	// Make the main menu
	m := door.Menu{Panel: door.Panel{Width: 45,
		X:           5,
		Y:           5,
		Style:       door.DOUBLE,
		Title:       "[ Main Menu: ]",
		TitleOffset: 3,
		BorderColor: door.ColorText("BRI CYAN ON BLA")}}
	m.SelectedR = door.MakeMenuRender(door.ColorText("BOLD CYAN"),
		door.ColorText("BOLD BLUE"),
		door.ColorText("BOLD CYAN"),
		door.ColorText("BOLD BLUE"))
	m.UnselectedR = door.MakeMenuRender(door.ColorText("BOLD YEL ON BLUE"),
		door.ColorText("BOLD WHI ON BLUE"),
		door.ColorText("BOLD YEL ON BLUE"),
		door.ColorText("BOLD CYAN ON BLUE"))

	m.AddSelection("A", "ANSI Display")
	m.AddSelection("D", "Display Information (dropfile, screen)")
	m.AddSelection("I", "Input Prompt Demo")
	m.AddSelection("P", "Progress Bars Demo")
	m.AddSelection("S", "Show Panel")

	m.AddSelection("Q", "Quit")
	return m
}

func display_information(d *door.Door) {
	d.Write(door.Clrscr)

	keyColor := door.ColorText("BRI GREEN")
	sepColor := door.ColorText("BRI YEL")
	valColor := door.ColorText("CYAN")
	nice_format := func(key string, value string) string {
		return fmt.Sprintf("%s%-20s %s: %s%s", keyColor, key, sepColor, valColor, value) + door.CRNL
	}

	d.Write(nice_format("BBS Software", d.Config.BBSID))
	d.Write(nice_format("Real Name", d.Config.Real_name))
	d.Write(nice_format("Handle", d.Config.Handle))
	d.Write(nice_format("User #", strconv.Itoa(d.Config.User_number)))
	d.Write(nice_format("Security Level", strconv.Itoa(d.Config.Security_level)))
	d.Write(nice_format("Node #", strconv.Itoa(d.Config.Node)))
	d.Write(nice_format("Unicode", strconv.FormatBool(door.Unicode)))
	d.Write(nice_format("CP437", strconv.FormatBool(door.CP437)))
	d.Write(nice_format("Screen Size", fmt.Sprintf("%d X %d", door.Width, door.Height)))
}

func display_ansi(d *door.Door) {
	space := SPACE()
	d.Write(door.Clrscr)
	for _, line := range space {
		if door.Unicode {
			d.Write(door.CP437_to_Unicode(line) + door.CRNL)
		} else {
			d.Write(line + door.CRNL)
		}
	}
}

func input_demo(d *door.Door) {
	inputColor := door.ColorText("BRI WHI ON BLUE")
	inputColor2 := door.ColorText("BRI WHI ON GREEN")
	prompt := door.Line{Text: "What is YOUR Name: "}
	prompt.RenderF = door.RenderBlueYellow
	d.Write(prompt.Output() + inputColor)
	name := d.Input(25)
	d.Write(door.Reset + door.CRNL)
	prompt.Text = "What is Your Quest: "
	d.Write(prompt.Output() + inputColor2)
	quest := d.Input(35)
	d.Write(door.Reset + door.CRNL)
	prompt.Text = "What is your Favorite CoLoR: "
	d.Write(prompt.Output() + inputColor2)
	color := d.Input(15)
	d.Write(door.Reset + door.CRNL)
	d.Write(fmt.Sprintf("You're %s on the %s quest, and fond of %s."+door.CRNL, name, quest, color))
}

func progress_bars(d *door.Door) {
	d.Write(door.Clrscr)

	bar := door.BarLine{Line: door.Line{DefaultColor: door.ColorText("BOLD YELLOW")}, Width: 20, Style: door.HALF_STEP}
	bar2 := door.BarLine{Width: 30, Style: door.SOLID, PercentStyle: door.PERCENT_SPACE}

	bar2.ColorRange = []door.BarRange{
		{2500, door.ColorText("RED")},
		{5000, door.ColorText("BROWN")},
		{7500, door.ColorText("BOLD YEL")},
		{9500, door.ColorText("GREEN")},
		{10100, door.ColorText("BRI GRE")}}

	bar3 := door.BarLine{Width: 15, Style: door.GRADIENT, Line: door.Line{DefaultColor: door.ColorText("CYAN")}}

	var percentage int64
	bar.UpdateP = pctUpdate(&percentage)
	bar2.UpdateP = pctUpdate(&percentage)
	bar3.UpdateP = pctUpdate(&percentage)

	update_bars := func() {
		bar.Update()
		bar2.Update()
		bar3.Update()
	}

	d.Write(door.Goto(3, 12) + "Half-Step")
	d.Write(door.Goto(25, 12) + "% with space and Color Range")
	d.Write(door.Goto(57, 12) + "Gradient")

	bar_start := door.Goto(3, 15)

	for f := 0; f <= 100; f++ {
		percentage = int64(f * 100)

		update_bars()
		d.Write(bar_start + bar.Output() + "  " + door.Reset + bar2.Output() + door.Reset + "  " + bar3.Output())

		if d.Disconnected {
			// don't continue to sleep if we're disconnected
			break
		}

		time.Sleep(time.Millisecond * 100)
	}

}

func panel_demo(d *door.Door) {
	width := 55
	fmtStr := "%-55s"
	p := door.Panel{X: 5, Y: 5, Width: width, Style: door.DOUBLE, BorderColor: door.ColorText("CYAN ON BLUE"), Title: "[ Panel Demo ]"}

	lineColor := door.ColorText("BRIGHT WHI ON BLUE")
	// Add lines to the panel
	for _, line := range []string{"The BBS Door Panel Demo", "(C) 2021 Red Green Software, https://red-green.com"} {
		if door.Unicode {
			line = strings.Replace(line, "(C)", "\u00a9", -1)
		}
		l := door.Line{Text: fmt.Sprintf(fmtStr, line), DefaultColor: lineColor}
		p.Lines = append(p.Lines, l)
	}
	p.Lines = append(p.Lines, p.Spacer())
	p.Lines = append(p.Lines, door.Line{Text: fmt.Sprintf(fmtStr, "Welcome to golang!"), DefaultColor: lineColor})

	d.Write(door.Clrscr)
	d.Write(p.Output() + door.CRNL)
}

func createTable(db *sql.DB) {
	db.Exec("CREATE TABLE IF NOT EXISTS settings(username TEXT, setting TEXT, value TEXT, PRIMARY KEY(username, setting));")
	db.Exec("CREATE TABLE IF NOT EXISTS scores ( \"username\" TEXT, \"when\" INTEGER, \"date\" INTEGER, \"hand\" INTEGER, \"won\" INTEGER, \"score\" INTEGER, PRIMARY KEY(\"username\", \"date\", \"hand\"));")
}

func main() {
	var message string

	// Get path to binary, and chdir to it.
	binaryPath, _ := os.Executable()
	binaryPath = filepath.Dir(binaryPath)
	_ = os.Chdir(binaryPath)

	fmt.Println("Starting space-ace")

	rng := rand.New(mt19937.New())
	rng.Seed(time.Now().UnixNano())

	sqliteDatabase, err := sql.Open("sqlite3", "./space-database.db")
	if err != nil {
		fmt.Printf("%#v\n", err)
	}
	defer sqliteDatabase.Close()
	createTable(sqliteDatabase)
	d := door.Door{}

	d.Init("space-ace")

	s := StarField{}
	s.Regenerate()

	s.Display(&d)
	d.Write(door.Goto(1, 1) + door.Reset)
	d.Key()

	bold := door.Color(1, 37, 40)
	bolder := door.ColorText("BLI BOLD YEL ON BLUE")
	d.Write("Welcome to " + bolder + "door32.sys" + door.Reset + door.CRNL + "..." + door.CRNL)
	key := press_a_key(&d)
	d.Write(fmt.Sprintf("Key %s%d / %x%s", bold, key, key, door.Reset) + door.CRNL)

	b := door.AlertBox("Warning: golang is in use!", 1)
	d.Write(door.ColorText("BRI WHI ON GREEN"))
	for _, line := range b {
		d.Write(line + door.CRNL)
	}
	d.Write(door.Reset + door.CRNL)

	left := d.TimeLeft()

	message = fmt.Sprintf("You have %0.2f minutes / %0.2f seconds remaining..."+door.CRNL, left.Minutes(), left.Seconds())
	d.Write(message)

	press_a_key(&d)

	mainmenu := MainMenu()
	var choice int

	for choice >= 0 {
		d.Write(door.Clrscr)
		s.Display(&d)
		choice = mainmenu.Choose(&d)

		if choice < 0 {
			break
		}
		option := mainmenu.GetOption(choice)

		// fmt.Printf("Choice: %d, Option: %c\n", choice, option)

		switch option {
		case 'A':
			display_ansi(&d)
			press_a_key(&d)
		case 'D':
			display_information(&d)
			press_a_key(&d)
		case 'I':
			d.Write(door.Reset + door.CRNL + door.CRNL)
			input_demo(&d)
			press_a_key(&d)
		case 'P':
			progress_bars(&d)
			press_a_key(&d)
		case 'S':
			panel_demo(&d)
			press_a_key(&d)
		case 'Q':
			choice = -1
			break
		}

	}

	d.Write(door.Reset + door.CRNL)

	message = fmt.Sprintf("Returning to %s ..."+door.CRNL, d.Config.BBSID)
	d.Write(message)

	d.WaitKey(1, 0)
	left = d.TimeLeft()

	message = fmt.Sprintf("You had %0.2f minutes / %0.2f seconds remaining!"+door.CRNL, left.Minutes(), left.Seconds())
	d.Write(message)

	left = d.TimeUsed()
	d.Write(fmt.Sprintf("You used %0.2f seconds / %0.2f minutes."+door.CRNL, left.Seconds(), left.Minutes()))

	fmt.Println("Exiting space-ace")
}