package main

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

	"github.com/seehuhn/mt19937"
)

var Config map[string]string

func press_a_key(d *door.Door) rune {
	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 rune
	var ex door.Extended
	var err error
	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, ex, err = d.WaitKey(time.Duration(sleep_ms) * time.Millisecond) // 0, int64(sleep_ms)*1000)

	for err == door.ErrTimeout && ex == door.NOP { // 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, ex, err = d.WaitKey(time.Duration(sleep_ms) * time.Millisecond) // 0, int64(sleep_ms)*1000)
	}

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

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 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)
}

/*
door::renderFunction statusValue(door::ANSIColor status,
                                 door::ANSIColor value) {
  door::renderFunction rf = [status,
                             value](const std::string &txt) -> door::Render {
    door::Render r(txt);
    size_t pos = txt.find(':');
    if (pos == std::string::npos) {
      for (char const &c : txt) {
        if (std::isdigit(c))
          r.append(value);
        else
          r.append(status);
      }
    } else {
      pos++;
      r.append(status, pos);
      r.append(value, txt.length() - pos);
    }
    return r;
  };
  return rf;
}

*/

func RenderStatusValue(status string, value string) func(string) string {
	renderF := func(text string) string {

		pos := strings.Index(text, ":")
		if pos != -1 {
			return status + text[:pos] + value + text[pos:]
		} else {
			var r door.Render = door.Render{Line: text}

			for _, letter := range text {
				if unicode.IsDigit(letter) {
					r.Append(value, 1)
				} else {
					r.Append(status, 1)
				}
			}
			return r.Result
		}
	}
	return renderF
}

// Display the SysOp settings in the Config/yaml file
func DisplaySettings(d *door.Door) {
	d.Write(door.Clrscr + door.Reset)
	W := 35
	panel := door.Panel{Width: W,
		X:           5,
		Y:           5,
		Style:       door.DOUBLE,
		BorderColor: door.ColorText("BOLD CYAN ON BLUE"),
	}
	l := door.Line{Text: fmt.Sprintf("%*s", W, "Game Settings - SysOp Configurable"),
		DefaultColor: door.ColorText("BOLD GREEN ON BLUE")}
	panel.Lines = append(panel.Lines, l)
	renderF := RenderStatusValue(door.ColorText("BOLD YELLOW ON BLUE"), door.ColorText("BOLD GREEN ON BLUE"))
	for key, value := range Config {
		if key[0] == '_' {
			continue
		}
		key = strings.Replace(key, "_", " ", -1)
		l = door.Line{Text: fmt.Sprintf("%20s : %*s", key, -(W - 23), value), RenderF: renderF}
		panel.Lines = append(panel.Lines, l)
	}
	d.Write(panel.Output() + door.Reset + door.CRNL)
	press_a_key(d)
}

func Configure(d *door.Door, db *DBData) {
	menu := ConfigMenu()
	// deckcolor := "DeckColor"
	// save_deckcolor := false
	var choice int

	for choice >= 0 {
		d.Write(door.Clrscr)
		choice = menu.Choose(d)

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

		switch option {
		case 'D':
			// Deck color
			current_color := db.GetSetting("DeckColor", "All")
			colormenu := DeckColorMenu(current_color)
			d.Write(door.Clrscr)
			c := colormenu.Choose(d)
			if c > 0 {
				db.SetSetting("DeckColor", DeckColors[c-1])
			}
			d.Write(door.CRNL)
			press_a_key(d)
		case 'V':
			// View settings
			DisplaySettings(d)
		case 'Q':
			choice = -1
		}
	}
}

func Help() door.Panel {
	W := 60
	center_x := (door.Width - W) / 2
	center_y := (door.Height - 16) / 2
	help := door.Panel{X: center_x,
		Y:           center_y,
		Width:       W,
		Style:       door.DOUBLE,
		BorderColor: door.ColorText("BOLD YELLOW ON BLUE")}
	help.Lines = append(help.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "Help"),
		DefaultColor: door.ColorText("BOLD CYAN ON BLUE")})
	help.Lines = append(help.Lines, help.Spacer())
	copyright := SPACEACE + " v" + SPACEACE_VERSION
	help.Lines = append(help.Lines,
		door.Line{Text: fmt.Sprintf("%*s", -W, copyright),
			DefaultColor: door.ColorText("BOLD WHITE ON BLUE")})
	copyright = SPACEACE_COPYRIGHT
	if door.Unicode {
		copyright = strings.Replace(copyright, "(C)", "\u00a9", -1)
	}
	help.Lines = append(help.Lines,
		door.Line{Text: fmt.Sprintf("%*s", -W, copyright),
			DefaultColor: door.ColorText("BOLD WHITE ON BLUE")})
	for _, text := range []string{"",
		"Use Left/Right arrow keys, or 4/6 keys to move marker.",
		"The marker wraps around the sides of the screen.", "",
		"Select card to play with Space or 5.",
		"A card can play if it is higher or lower in rank by 1.",
		"",
		"Enter draws another card.",
		"",
		"Example: A Jack could play either a Ten or a Queen.",
		"Example: A King could play either an Ace or a Queen.",
		"",
		"The more cards in your streak, the more points earn.",
	} {
		help.Lines = append(help.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, text)})
	}
	return help
}

func About() door.Panel {
	W := 60
	center_x := (door.Width - W) / 2
	center_y := (door.Height - 16) / 2
	about := door.Panel{X: center_x,
		Y:           center_y,
		Width:       W,
		Style:       door.SINGLE_DOUBLE,
		BorderColor: door.ColorText("BOLD YELLOW ON BLUE"),
	}
	about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "About This Door"),
		DefaultColor: door.ColorText("BOLD CYAN ON BLUE")})
	about.Lines = append(about.Lines, about.Spacer())
	copyright := SPACEACE + " v" + SPACEACE_VERSION
	about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, copyright)})
	copyright = SPACEACE_COPYRIGHT
	if door.Unicode {
		copyright = strings.Replace(copyright, "(C)", "\u00a9", -1)
	}
	about.Lines = append(about.Lines,
		door.Line{Text: fmt.Sprintf("%*s", -W, copyright),
			DefaultColor: door.ColorText("BOLD WHITE ON BLUE")})
	for _, text := range []string{"",
		"This door was written by Bugz.",
		"",
		"It is written in Go, understands CP437 and unicode, adapts",
		"to screen sizes, uses door32.sys, supports TheDraw Fonts,",
		"and runs on Linux and Windows."} {
		about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, text)})
	}
	return about
}

func MakeScoresRender(dateColor string, datelen int, nickColor string, nicklen int, scoreColor string) func(string) string {
	renderF := func(text string) string {
		var r door.Render = door.Render{Line: text}
		r.Append(dateColor, datelen)
		r.Append(nickColor, nicklen)
		r.Append(scoreColor, len(text)-(datelen+nicklen))
		return r.Result
	}
	return renderF
}

func DisplayScores(Door *door.Door, db *DBData, config *map[string]string) {
	// Scores::score()

	// make_top_scores_panel()
	W := 38
	var top_scores_panel door.Panel = door.Panel{Width: W,
		Style:       door.DOUBLE,
		BorderColor: door.ColorText("CYAN ON BLUE"),
		Title:       "[ The TOP Monthly Scores: ]",
	}

	const nick_len int = 17 // Max Nick Length

	var date_monthly string
	var ok bool
	date_monthly, ok = (*config)["date_monthly"]

	if !ok {
		date_monthly = "January"
	}
	var longest_month int = len(FormatDate(1631280600, date_monthly))

	monthly := db.GetMonthlyScores(15)
	if len(monthly) == 0 {
		top_scores_panel.Lines = append(top_scores_panel.Lines,
			door.Line{Text: fmt.Sprintf("%*s", -W, "No, Not Yet!"),
				DefaultColor: door.ColorText("BRI YELLOW ON BLUE"),
			})
	} else {
		ScoresRender := MakeScoresRender(door.ColorText("BOLD WHITE ON BLUE"),
			longest_month,
			door.ColorText("CYAN ON BLUE"),
			nick_len,
			door.ColorText("BOLD CYAN ON BLUE"))
		YourScoresRender := MakeScoresRender(
			door.ColorText("BOLD WHITE ON BLUE"),
			longest_month,
			door.ColorText("BOLD GREEN ON BLUE"),
			nick_len,
			door.ColorText("BOLD YELLOW ON BLUE"))

		for idx := range monthly {
			// What?
			timeDate := ToTime(monthly[idx].Date).Unix()
			var result string = fmt.Sprintf("%*s", -longest_month,
				FormatDate(timeDate, date_monthly))
			result += " " + fmt.Sprintf("%*s", -(nick_len-1), monthly[idx].User)
			result += fmt.Sprintf(" %d", monthly[idx].Score)
			result += strings.Repeat(" ", W-len(result))
			if monthly[idx].User == Door.Config.Real_name || monthly[idx].User == Door.Config.Handle {
				top_scores_panel.Lines = append(top_scores_panel.Lines,
					door.Line{Text: result, RenderF: YourScoresRender})
			} else {
				top_scores_panel.Lines = append(top_scores_panel.Lines,
					door.Line{Text: result, RenderF: ScoresRender})
			}
		}

	}
	// end make_top_scores_panel

	// make_top_this_month_panel()
	W = 38
	var title string = fmt.Sprintf("[ The TOP Scores for %s: ]",
		FormatDate(time.Now().Unix(), date_monthly))
	var top_month_panel door.Panel = door.Panel{Width: W,
		Style:       door.DOUBLE,
		BorderColor: door.ColorText(""),
		Title:       fmt.Sprintf("%*s", -W, title),
	}
	scores := db.GetScores(15)
	if len(scores) == 0 {
		top_month_panel.Lines = append(top_month_panel.Lines,
			door.Line{Text: fmt.Sprintf("%*s", -W, "No, Not Yet!"),
				DefaultColor: door.ColorText("BRI YELLOW ON BLUE"),
			})
	} else {

	}
	// Scores.display_scores()

}

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())

	d := door.Door{}
	d.Init("space-ace")
	defer d.Close()

	db := DBData{}
	db.Open("space-database.db")
	defer db.Close()
	// Use Real_name or Handle?  Or read config?
	db.User = d.Config.Real_name

	const config_filename = "space-ace.yaml"
	var err error
	if FileExists(config_filename) {
		Config, err = LoadConfig(config_filename)
		if err != nil {
			log.Printf("LoadConfig( %s ): %s\n", config_filename, err)
			Config = make(map[string]string)
		}
	} else {
		Config = make(map[string]string)
	}

	var update_config bool = false

	// Guessing at this point as to what settings I actually want.
	config_defaults := map[string]string{"hands_per_day": "3",
		"date_format":     "January 2",
		"date_score":      "01/02/2006",
		"makeup_per_day":  "5",
		"play_days_ahead": "2",
		"date_monthly":    "January 2006"}

	// _seed
	_, ok := Config["_seed"]
	if !ok {
		// Initialize the seed
		Config["_seed"] = fmt.Sprintf("%d,%d,%d", rng.Int31(), rng.Int31(), rng.Int31())
		update_config = true
	}

	for key, value := range config_defaults {
		if SetConfigDefault(&Config, key, value) {
			update_config = true
		}
	}

	if update_config {
		SaveConfig(config_filename, Config)
	}

	starfield := StarField{}
	starfield.Regenerate(rng)

	starfield.Display(&d)
	// d.Write(door.Goto(1, 1))
	cx := (door.Width - 72) / 2
	if cx < 0 {
		cx = 1
	}

	for idx, line := range ANSISpace() {
		d.Write(door.Goto(cx, idx+3) + line)
	}
	d.Write(door.Reset + door.CRNL)

	// Get the last call value (if they have called before)
	last_call, _ := strconv.ParseInt(db.GetSetting("LastCall", "0"), 10, 64)
	now := time.Now()
	db.SetSetting("LastCall", fmt.Sprintf("%d", now.Unix()))

	// Check for maint run -- get FirstOfMonthDate, and see if
	// records are older then it is.  (If so, yes -- run maint)!

	db.ExpireScores(0)

	if last_call != 0 {
		d.Write("Welcome Back!" + door.CRNL)
		delta := now.Sub(time.Unix(last_call, 0))
		hours := delta.Hours()
		if hours > 24 {
			d.Write(fmt.Sprintf("It's been %0.1f days since you last played."+door.CRNL, hours/24))
		} else {
			if hours > 1 {
				d.Write(fmt.Sprintf("It's been %0.1f hours since you last played."+door.CRNL, hours))
			} else {
				minutes := delta.Minutes()
				d.Write(fmt.Sprintf("It's been %0.1f minutes since you last played."+door.CRNL, minutes))
			}
		}
	}

	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)
		starfield.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 'P':
			// Play cards
			pc := PlayCards{Door: &d,
				DB:     &db,
				Config: &Config,
				RNG:    rng,
			}
			pc.Init()
			pc.Play()

		case 'S':
			// View Scores
			DisplayScores(&d, &db, &Config)
			press_a_key(&d)
		case 'C':
			// Configure
			Configure(&d, &db)
		case 'H':
			// Help
			h := Help()
			d.Write(door.Clrscr + h.Output() + door.Reset + door.CRNL)
			press_a_key(&d)
		case 'A':
			// About
			a := About()
			d.Write(door.Clrscr + a.Output() + door.Reset + door.CRNL)
			press_a_key(&d)
		case 'Q':
			choice = -1
		}

	}

	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")
}