package door

import (
	"bytes"
	"fmt"
	"log"
	"time"
)

/*
WOPR

[      GAME      ]
[  TIME ELAPSED  ]
[     XX HRS     ]
[ XX MIN  XX SEC ]
[■               ]  ->
[      GAME      ]
[ TIME REMAINING ]
[     XX HRS     ]
[ XX MIN  XX SEC ]
[               ■]  <-

Border SINGLE
Ok, Width = 16
1000 / 16 = 62.5 ms = 62500 us

Width 16  (at pos 1, update time/sec increment)
Black on Cyan
*/
type WOPR struct {
	ElapsedPanel   Panel
	Elapsed        time.Time
	ElapsedD       time.Duration
	RemainingPanel Panel
	Remaining      time.Time
	RemainingD     time.Duration
	Color          string
	Index          int
	Ticker         *time.Ticker
	StopIt         chan bool
	output         *bytes.Buffer
}

const DEBUG_WOPR bool = true

func (w *WOPR) Clear() []byte {
	var output bytes.Buffer
	output.Write(w.ElapsedPanel.Clear())
	output.Write(w.RemainingPanel.Clear())
	return output.Bytes()
}

// Initialize, Set X, Y on Panels, Animate()

// Initialize, and create panels
func (w *WOPR) Init(elapsed time.Time, remaining time.Time, color string) {
	if color == "" {
		color = ColorText("BLACK ON CYAN")
	}
	w.output = &bytes.Buffer{}
	w.Elapsed = elapsed
	w.Remaining = remaining
	w.Index = 0
	w.ElapsedPanel = Panel{Width: 16,
		BorderColor: color,
		Style:       SINGLE}

	w.ElapsedPanel.Lines = append(w.ElapsedPanel.Lines, NewLine("      GAME      "))
	w.ElapsedPanel.Lines = append(w.ElapsedPanel.Lines, NewLine("  TIME ELAPSED  "))

	var ehour *Line = &Line{}
	ehour.UpdateF = func(u *bytes.Buffer) {
		var hours int = int(w.ElapsedD.Hours())
		u.Reset()
		/*
			// This does not change anything in the benchmark.
			u.WriteString("     ")
			u.WriteByte(byte(int('0') + (hours%100)/10))
			u.WriteByte(byte(int('0') + (hours%100)%10))
			u.WriteString(" HRS     ")
		*/
		fmt.Fprintf(u, "     %02d HRS     ", hours%100)
	}
	ehour.Update()
	// ehour.Text.Write(ehour.update)
	w.ElapsedPanel.Lines = append(w.ElapsedPanel.Lines, ehour)

	var eminsec *Line = &Line{}
	eminsec.UpdateF = func(u *bytes.Buffer) {
		var mins int = int(w.ElapsedD.Minutes()) % 60
		var secs int = int(w.ElapsedD.Seconds()) % 60
		u.Reset()
		fmt.Fprintf(u, " %02d MIN  %02d SEC ", mins, secs)
	}
	eminsec.Update() // Text.Write(eminsec.UpdateF())
	w.ElapsedPanel.Lines = append(w.ElapsedPanel.Lines, eminsec)

	var eanimate *Line = &Line{}
	eanimate.UpdateF = func(u *bytes.Buffer) {
		u.Reset()
		// Left to Right
		if Unicode {
			for i := 0; i < 16; i++ {
				if i == w.Index {
					u.WriteRune('\u25a0')
				} else {
					u.WriteByte(' ')
				}
			}
			/*
				var buffer []rune = []rune(strings.Repeat(" ", 16))
				buffer[w.Index] = '\u25a0'
				u.WriteString(string(buffer))
			*/
		} else {
			for i := 0; i < 16; i++ {
				if i == w.Index {
					u.WriteRune('\xfe')
				} else {
					u.WriteByte(' ')
				}
			}
			/*
				var buffer []byte = bytes.Repeat([]byte(" "), 16)
				buffer[w.Index] = '\xfe'
				u.WriteString(string(buffer))
			*/
		}
	}
	eanimate.Update() // Text.Write(eanimate.UpdateF())
	w.ElapsedPanel.Lines = append(w.ElapsedPanel.Lines, eanimate)

	w.RemainingPanel = Panel{Width: 16,
		BorderColor: color,
		Style:       SINGLE}
	w.RemainingPanel.Lines = append(w.RemainingPanel.Lines, NewLine("      GAME      "))
	w.RemainingPanel.Lines = append(w.RemainingPanel.Lines, NewLine(" TIME REMAINING "))

	var rhour *Line = &Line{}
	rhour.UpdateF = func(u *bytes.Buffer) {
		var hours int = int(w.RemainingD.Hours())
		u.Reset()
		/*
			u.WriteString("     ")
			u.WriteByte(byte(int('0') + (hours%100)/10))
			u.WriteByte(byte(int('0') + (hours%100)%10))
			u.WriteString(" HRS     ")
		*/
		fmt.Fprintf(u, "     %02d HRS     ", hours%100)
	}
	rhour.Update() // Text.Write(rhour.UpdateF())
	w.RemainingPanel.Lines = append(w.RemainingPanel.Lines, rhour)

	var rminsec *Line = &Line{}
	rminsec.UpdateF = func(u *bytes.Buffer) {
		var mins int = int(w.RemainingD.Minutes()) % 60
		var secs int = int(w.RemainingD.Seconds()) % 60
		u.Reset()
		fmt.Fprintf(u, " %02d MIN  %02d SEC ", mins, secs)
	}
	rminsec.Update() // Text.Write(rminsec.UpdateF())
	w.RemainingPanel.Lines = append(w.RemainingPanel.Lines, rminsec)

	var ranimate *Line = &Line{}

	ranimate.UpdateF = func(u *bytes.Buffer) {
		u.Reset()
		// Left to Right
		if Unicode {
			// Changing to for loop from buffers did 0.
			for i := 0; i < 16; i++ {
				if i == 15-w.Index {
					u.WriteRune('\u25a0')
				} else {
					u.WriteByte(' ')
				}
			}
			/*
				var buffer []rune = []rune(strings.Repeat(" ", 16))
				buffer[15-w.Index] = '\u25a0'
				u.WriteString(string(buffer))
			*/
		} else {
			for i := 0; i < 16; i++ {
				if i == 15-w.Index {
					u.WriteByte('\xfe')
				} else {
					u.WriteByte(' ')
				}
			}

			// var buffer []byte = bytes.Repeat([]byte{' '}, 16)
			// No change here.
			/*
				var buffer [16]byte = [16]byte{' ', ' ', ' ', ' ',
					' ', ' ', ' ', ' ',
					' ', ' ', ' ', ' ',
					' ', ' ', ' ', ' '}
			*/
			// bytes.Repeat([]byte(" "), 16)
			/*
				buffer[15-w.Index] = '\xfe'
				u.Write(buffer[:])
				// u.WriteString(string(buffer))
			*/
		}
	}
	ranimate.Update() // Text.Write(ranimate.UpdateF())
	w.RemainingPanel.Lines = append(w.RemainingPanel.Lines, ranimate)
}

func (w *WOPR) Inc() {
	w.Index++
	if w.Index == 16 {
		w.Index = 0
		w.ElapsedPanel.Update()
		w.RemainingPanel.Update()

		w.RemainingD = time.Duration(w.RemainingD.Seconds()-1) * time.Second
		w.ElapsedD = time.Duration(w.ElapsedD.Seconds()+1) * time.Second
	}
}

var woprBytesUsed int

func (w *WOPR) Output() []byte {
	w.output.Reset()
	w.output.WriteString(SavePos)
	w.output.Write(w.ElapsedPanel.Output())
	w.output.Write(w.RemainingPanel.Output())
	w.output.WriteString(RestorePos)

	if DEBUG_WOPR {
		if w.output.Cap() > woprBytesUsed {
			woprBytesUsed = w.output.Cap()
			log.Printf("WOPR: now %d\n", woprBytesUsed)
		}
	}
	return w.output.Bytes()
}

func (w *WOPR) Animate(d *Door) {
	// til := time.Now().UnixNano() % int64(time.Second)

	// w.Index = int((til / int64(time.Microsecond)) / 62500)
	// either put the sync sleep in the go routine, or sleep and sync Index.

	// time.Sleep(time.Duration(int64(time.Second) - til)) // time.Now().UnixMilli()%1000) * time.Millisecond)

	// time.Second / 16
	// w.Ticker = time.NewTicker(time.Microsecond * time.Duration(62500))

	// w.Index = 0
	// Convert time.Time to time.Duration
	// This gives us consistency when resuming.
	w.ElapsedD = time.Since(w.Elapsed)
	w.RemainingD = time.Until(w.Remaining)
	w.StopIt = make(chan bool)

	go func(d *Door) {
		// til := time.Now().UnixNano() % int64(time.Second)

		sec16 := int64(time.Second) / 16
		// tilms := til % sec16
		w.Index = 0 // int(til / sec16)
		// log.Printf("til: %d, sec: %d, w.Index: %d\n", til, sec16, w.Index)

		// either put the sync sleep in the go routine, or sleep and sync Index.

		// time.Sleep(time.Second - time.Duration(til)) // sec16 - (til % sec16))) //int64(time.Second) - til)) // time.Now().UnixMilli()%1000) * time.Millisecond)

		// time.Second / 16
		w.Ticker = time.NewTicker(time.Duration(sec16))

		// var output bytes.Buffer

		for {
			select {
			case <-w.StopIt:
				return

			case <-w.Ticker.C:
				w.ElapsedPanel.Update()
				w.RemainingPanel.Update()
				//var output []byte = w.Output()
				if !d.Writer.IsClosed() {
					// d.Write(output)
					d.Write(w.Output())
				} else {
					w.Ticker.Stop()
					return
				}

				w.Inc()
			}
		}
	}(d)
}

func (w *WOPR) Stop() {
	w.Ticker.Stop()
	w.StopIt <- true
}