package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"red-green/door"
	"runtime/debug"
	"runtime/pprof"
	"time"
	// "net/http"
	// _ "net/http/pprof"
)

const ENABLE_PROFILER bool = false

// "runtime/pprof"

// Max X value we can use before hitting MemStats panel.
var MaxX int = 0

func press_keys(d *door.Door) {
	d.WriteA(door.Reset, door.CRNL, "Press some keys... <ENTER> to exit.")
	var r rune
	var ex door.Extended
	var err error

	for (r != 0x0d) && (err == nil) {
		r, ex, err = d.WaitKey(door.Inactivity)
		if ex == door.MOUSE {
			m, ok := d.GetMouse()
			if ok {
				// var m door.MouseInfo = door.Mouse
				d.WriteA(fmt.Sprintf("M %d (%d,%d) ", m.Button, m.X, m.Y))
			}
		} else {
			if ex == door.NOP {
				d.WriteA(fmt.Sprintf("%d (%x) ", r, r))
			} else {
				d.WriteA(fmt.Sprintf("<%s> ", ex.String()))
			}
		}
	}
	d.WriteA(door.Reset, door.CRNL)
}

func press_a_key(d *door.Door) error {
	var err error
	var ex door.Extended

	d.WriteA(door.Reset, door.CRNL, "Press a key, or LEFT mouse click to continue...")
	for {
		_, ex, err = d.WaitKey(door.Inactivity)
		if ex == door.MOUSE {
			m, ok := d.GetMouse()
			if ok {
				if m.Button == 1 {
					break
				}
			}
		} else {
			break
		}
	}
	d.WriteA(door.CRNL)
	return err
}

func main() {
	var message string

	if ENABLE_PROFILER {
		fp1, err1 := os.Create("cpu.pro")
		if err1 != nil {
			log.Fatal(err1)
		}
		pprof.StartCPUProfile(fp1)
		defer pprof.StopCPUProfile()

		defer func() {
			fp2, err2 := os.Create("memory.pro")
			if err2 != nil {
				log.Fatal(err2)
			}
			pprof.WriteHeapProfile(fp2)

		}()
	}

	/*
		go func() {
			http.ListenAndServe(":6060", nil)
		}()
	*/

	var d door.Door = door.Door{}

	d.Init("testdoor")

	defer func() {
		if err := recover(); err != nil {
			// This displays stack trace stderr
			debug.PrintStack()
			fmt.Println("ERROR:", err)
			log.Println("FAILURE:", err)
			// Display error to user
			d.WriteA(fmt.Sprintf(door.Reset+door.CRNL+"Exception: %v"+door.CRNL, err))
		}
	}()

	defer d.Close()

	// Updaters work best when the screen doesn't scroll, so start
	// us off at the very top.

	d.WriteA(door.Clrscr)

	// Start spinrite effects
	var ticker *time.Ticker = time.NewTicker(time.Millisecond * time.Duration(100)) // 100

	var spin door.SpinRiteMsg = door.SpinRiteMsgInit(15, 5,
		door.ColorText("RED ON GREEN"),
		[]string{"RED", "GREEN", "SOFTWARE"})

	/*
		var spin2 door.SpinRite = door.SpinRiteInit(13, 5,
			door.ColorText("BRI CYA ON BLUE"))
	*/
	// Add in the GoRoutine Status panel

	MaxX = door.Width - 20
	var goPanel door.Panel = door.Panel{X: door.Width - 19,
		Y:           3,
		Width:       16,
		Style:       door.DOUBLE,
		Title:       "] GoRuntime [",
		BorderColor: door.ColorText("BOLD YELL"),
	}

	const DisplayGoRoutines bool = true

	if DisplayGoRoutines {
		goLineUpdater := func(u *bytes.Buffer) {
			u.Reset()
			u.Write(GoRoutinesStatus())
			// log.Println(status)
		}

		var goLine door.Line = door.Line{
			Text:         &bytes.Buffer{},
			UpdateF:      goLineUpdater,
			Width:        goPanel.Width,
			DefaultColor: door.ColorText("CYAN"),
		}
		// goLine.Update() // Force Update
		goPanel.Lines = append(goPanel.Lines, goLine)
	}

	// Display Legend on GoRuntime panel
	const DisplayGoRoutineLegend bool = false
	// Display Memory
	const DisplayMemory bool = true
	var MemoryStats map[string]*bytes.Buffer = make(map[string]*bytes.Buffer)

	if DisplayGoRoutineLegend {
		// Line for legend.
		goPanel.Lines = append(goPanel.Lines, door.Line{Width: goPanel.Width})
	}

	if DisplayMemory {
		memoryUpdater := func(u *bytes.Buffer) {
			if u == nil {
				u = &bytes.Buffer{}
			}
			u.Reset()
			Memory(&MemoryStats)
			fmt.Fprintf(u, "%-8s%8s", "Sys", MemoryStats["Sys"].Bytes())
		}
		// memoryUpdater(&bytes.Buffer{})

		goPanel.Lines = append(goPanel.Lines, door.Line{UpdateF: memoryUpdater,
			Width:        goPanel.Width,
			DefaultColor: door.ColorText("BLU")})
		heapUpdater := func(u *bytes.Buffer) {
			if u == nil {
				u = &bytes.Buffer{}
			}
			u.Reset()
			fmt.Fprintf(u, "%-8s%8s", "HeapSys", MemoryStats["Heap"].Bytes())
		}
		goPanel.Lines = append(goPanel.Lines, door.Line{UpdateF: heapUpdater,
			Width:        goPanel.Width,
			DefaultColor: door.ColorText("BLU")})

		stackUpdater := func(u *bytes.Buffer) {
			u.Reset()
			fmt.Fprintf(u, "%-8s%8s", "StackSys", MemoryStats["StackSys"].Bytes())
		}
		goPanel.Lines = append(goPanel.Lines, door.Line{UpdateF: stackUpdater,
			Width:        goPanel.Width,
			DefaultColor: door.ColorText("BLU")})
		gcUpdater := func(u *bytes.Buffer) {
			u.Reset()
			fmt.Fprintf(u, "%-8s%8s", "NumGC", MemoryStats["NumGC"].Bytes())
		}
		goPanel.Lines = append(goPanel.Lines, door.Line{UpdateF: gcUpdater,
			Width:        goPanel.Width,
			DefaultColor: door.ColorText("BLU")})
	}

	var lIndex int = 0
	var legendCount int = 0
	const legendUpdate = 20

	go func() {
		defer func() {
			r := recover()
			if r != nil {
				log.Printf("** FAIL: %s, %#v\n", r, r)
			}
		}()

		// var output string
		var legend []string = []string{
			"R=run r=Runnable",
			"S=Select s=Syscall",
			"Chan <read >write",
			"Z=Sleep P=Preempt",
			"I=Idle D=Dead",
			"W=Wait C=Copystk",
		}
		if DisplayGoRoutineLegend {
			goPanel.Lines[1].Text = bytes.NewBuffer([]byte(legend[0]))
		}

		for range ticker.C {
			// output = door.SavePos + door.Goto(door.Width-16, 1) + spin.Output() +
			//	door.Goto(door.Width-15, 3) + spin2.Output() + door.RestorePos

			if DisplayGoRoutineLegend {
				legendCount++
				if legendCount >= legendUpdate {
					legendCount = 0
					lIndex++
					if lIndex == len(legend) {
						lIndex = 0
					}
					goPanel.Lines[1].Text = bytes.NewBuffer([]byte(legend[lIndex]))
				}
			}

			if goPanel.X == 0 {
				goPanel.X = door.Width - 40
			}

			goPanel.Update()

			//output = door.Goto(door.Width-16, 1) + spin.Output() + goPanel.Output()
			/*
				    +
					door.Goto(door.Width-15, 3) + spin2.Output()
			*/

			if !d.Disconnect() {
				// d.Update(output)
				d.WriteA(door.SavePos, door.Goto(door.Width-16, 1), spin.Output(), goPanel.Output(), door.RestorePos)
			} else {
				log.Println("Ticker.Stop: Disconnected!")
				ticker.Stop()
				return
			}
		}
		log.Println("range Ticker.C stopped.")
	}()

	var wopr door.WOPR
	wopr.Init(d.StartTime, d.TimeOut, "")
	wopr.ElapsedPanel.X = door.Width - 19
	wopr.ElapsedPanel.Y = door.Height - 15
	wopr.RemainingPanel.X = door.Width - 19
	wopr.RemainingPanel.Y = door.Height - 8

	wopr.Animate(&d)

	// bold := door.Color(1, 37, 40)
	var bolder string = door.ColorText("BOLD YEL ON BLUE")
	d.WriteA("Welcome to ", bolder, "go door go TestDoor.", door.Reset, door.CRNL, "...", door.CRNL)

	d.EnableMouse(door.Normal)

	press_a_key(&d)
	d.WriteA(door.CRNL)

	var b []string
	if door.CP437 {
		b = door.AlertBox("Alert: go \xfb is in use!", 1)
	} else {
		b = door.AlertBox("Alert: go \u221a is in use!", 1)
	}

	warningColor := door.ColorText("BRI WHI ON GREEN")
	for _, line := range b {
		// Prevent color bleeding.
		d.WriteA(warningColor, line, door.Reset, door.CRNL)
	}
	d.WriteA(door.Reset, door.CRNL)

	var left time.Duration = d.TimeLeft()

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

	press_a_key(&d)

	var mainmenu door.Menu = MainMenu()
	var choice int

	for choice >= 0 {
		d.WriteA(door.Clrscr, door.HideCursor)
		choice = mainmenu.Choose(&d)
		d.WriteA(door.ShowCursor)

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

		wopr.Stop()
		// Clear WOPR panels.
		d.WriteA(door.Reset, wopr.Clear())

		r, b := mainmenu.Panel.RightBottomPos()
		d.WriteA(door.Goto(r, b))
		// 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 'F':
			font_demo(&d)
			press_a_key(&d)
		case 'I':
			d.WriteA(door.Reset, door.CRNL, door.CRNL)
			input_demo(&d)
			press_a_key(&d)
		case 'M':
			// Why is this so far down on the screen? (Scrolls)
			d.WriteA(door.Reset, door.CRNL, "TO DO:  Provide menu of options to select from...", door.CRNL)
			press_a_key(&d)
		case 'P':
			progress_bars(&d)
			press_a_key(&d)
		case 'S':
			panel_demo(&d)
			press_a_key(&d)
		case 'T':
			about_test_door(&d)
			press_a_key(&d)
		case 'W':
			width_demo(&d)
		case 'Q':
			// This is also far down on the screen ...
			choice = -1
		case 'C': // Disabled
			var a, z int
			z = 0
			a = 10 / z
			z = a
			_ = a
			_ = z
		}

		wopr.Animate(&d)
	}

	// d.Write("\x1b[?1000l") // disable mouse
	// d.Write("\x1b[?1002l")
	d.DisableMouse()
	d.WriteA(door.Reset, door.CRNL)

	if d.Config.BBSID != "" {
		message = fmt.Sprintf("Returning to the %s BBS..."+door.CRNL, d.Config.BBSID)
	} else {
		message = "Returning to the BBS..." + door.CRNL
	}

	d.WriteA(message)

	d.WaitKey(time.Second)
	left = d.TimeLeft()

	ticker.Stop()

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

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

	fmt.Println("Ending testdoor.go")
}