package main

import (
	_ "embed"
	"fmt"
	"log"
	"math/rand"
	"strings"
	"time"

	"github.com/gdamore/tcell/v3"
	"github.com/gdamore/tcell/v3/color"
)

type coords struct {
    x, y int
}


var fallingQuotes = []string{
	"IT DESCENDS.",
	"The sky provides.",
	"I sense tuna.",
	"Ah yes. The prophecy.",
	"Rain harder.",
	"Something edible approaches.",
}

var catchQuotes = []string{
	"Mine.",
	"Calculated.",
	"Too easy.",
	"As foretold.",
	"Nom.",
	"You may drop another.",
}

var missQuotes = []string{
	"This is sabotage.",
	"The sky betrayed me.",
	"I meant to let that one go.",
	"It lacked seasoning.",
	"Unacceptable.",
}

func randomQuote(pool []string) string {
	return pool[rand.Intn(len(pool))]
}

//go:embed assets/title/title_idle.txt
var title_idle string

//go:embed assets/title/title_blinking.txt
var title_blinking string

func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
	row := y1
	col := x1
	var width int
	for text != "" {
		text, width = s.Put(col, row, text, style)
		col += width
		if col >= x2 {
			row++
			col = x1
		}
		if row > y2 {
			break
		}
		if width == 0 {
			// incomplete grapheme at end of string
			break
		}
	}
}

func drawTitle(s tcell.Screen, x, padding_top, frame int, style tcell.Style, titles [][]string) int{	
	lines := titles[frame]
	frame = (frame + 1) % 2
	// Draw Title
	xmax, ymax := s.Size()
	for l:= range lines{
		drawText(s,x, l+padding_top, xmax, ymax, style, lines[l])
	}
	return len(lines)
}


func DrawMainMenu(s tcell.Screen, defStyle tcell.Style, old_session bool) bool{
	cats := [][]string{
		strings.Split(string(title_idle), "\n"),
		strings.Split(string(title_blinking), "\n")}

	var first_item_menu string
	if (old_session){
		first_item_menu = "Continue"
	} else {
		first_item_menu = "Start New Game"
	}
	
	curr_menu_item := 0
	menu := true
	gap_title_menu := 3
	settings, credits := false, false
	frame := 0
	ticker := time.NewTicker(500 * time.Millisecond)
	defer ticker.Stop()
	
	// Event loop
	for {
		s.Clear()
		title_size := drawTitle(s, 10, 2, frame, defStyle, cats)
		xmax,ymax := s.Size()
		// Draw Menu
		if menu {
			drawText(s, 10, title_size+gap_title_menu, xmax, ymax, defStyle,   "Use arrows or WASD to navigate. Press ENTER to select")
			switch curr_menu_item {
			case 0:
				drawText(s, 10, title_size+gap_title_menu+2, xmax, ymax, defStyle,   "▶ "+first_item_menu)
				drawText(s, 10, title_size+gap_title_menu+3, xmax, ymax, defStyle,   "  Settings")
				drawText(s, 10, title_size+gap_title_menu+4, xmax, ymax, defStyle,   "  Credits")
			case 1:
				drawText(s, 10, title_size+gap_title_menu+2, xmax, ymax, defStyle,   "  "+first_item_menu)
				drawText(s, 10, title_size+gap_title_menu+3, xmax, ymax, defStyle,   "▶ Settings")
				drawText(s, 10, title_size+gap_title_menu+4, xmax, ymax, defStyle,   "  Credits")
			case 2:
				drawText(s, 10, title_size+gap_title_menu+2, xmax, ymax, defStyle,   "  "+first_item_menu)
				drawText(s, 10, title_size+gap_title_menu+3, xmax, ymax, defStyle,   "  Settings")
				drawText(s, 10, title_size+gap_title_menu+4, xmax, ymax, defStyle,   "▶ Credits")
			}
		}
		
		if credits {
			drawText(s, 10, title_size+gap_title_menu, xmax,ymax, defStyle, "Abderrahmane Faiz")
			drawText(s, 10, title_size+gap_title_menu+1, xmax,ymax, defStyle, "\u001B]8;;https://afaiz.dev\u001B\\https://afaiz.dev\u001B]8;;\u001B\\")
			drawText(s, 10, title_size+gap_title_menu+2, xmax,ymax, defStyle, "ASCII Art found on https://emojicombos.com/cat")
			drawText(s, 10, title_size+gap_title_menu+4, xmax,ymax, defStyle, "Press ENTER to go back to the menu")
		}

		if settings {
			drawText(s, 10, title_size+gap_title_menu, xmax,ymax, defStyle, "Nothing to customize for now ^^'")
			drawText(s, 10, title_size+gap_title_menu+4, xmax,ymax, defStyle, "Press ENTER to go back to the menu")
		}
		
		// Update screen
		s.Show()

		// Poll event (this can be in a select statement as well)
		select{
			case <- ticker.C:
			frame = (frame + 1) % 2
			continue
			case ev := <-s.EventQ():
			// Process event
			switch ev := ev.(type) {
			default:
				continue
			case *tcell.EventResize:
				s.Sync()
			case *tcell.EventKey:
				if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
					return false
				} else if (ev.Key() == tcell.KeyUp || ev.Str() == "w" || ev.Str() == "W") && menu {
					if curr_menu_item == 0 {
						curr_menu_item = 2
					} else {
						curr_menu_item = curr_menu_item - 1
					}
				} else if (ev.Key() == tcell.KeyDown || ev.Str() == "s" || ev.Str() == "S") && menu {
					curr_menu_item = (curr_menu_item + 1) % 3
				} else if ev.Key() == tcell.KeyEnter && credits {
					credits = false
					menu = true
				} else if ev.Key() == tcell.KeyEnter && settings {
					settings = false
					menu = true
				}  else if ev.Key() == tcell.KeyEnter && curr_menu_item == 2 {
					menu = false
					credits = true
				}  else if ev.Key() == tcell.KeyEnter && curr_menu_item == 1 {
					menu = false
					settings = true	
				}  else if ev.Key() == tcell.KeyEnter && curr_menu_item == 0 {
					return true
				}
				
			}
		}
	}
}


func DrawCat(s tcell.Screen, x1,y1,x2,y2 int, style tcell.Style, direction rune) {
	mycat := []string{" /\\_/\\","( o.o )", " > ^ <"}
	switch direction{
		case 'l':
		mycat[len(mycat)-1] = " < ^ <"
		case 'r':
		mycat[len(mycat)-1] = " > ^ >"
		default :
		mycat[len(mycat)-1] = " > ^ <"
	}
	
	for index,cat := range mycat{
		drawText(s, x1, y1 + index, x2,y2, style, cat)
	}
}

// x1,y1 ------- x2,y1
// |              |
// |              |
// x1,y2 ------- x2,y2
func GetFoodPositions(x1,y1,x2,y2,level int) []*coords{
	var res []*coords
	nb_food := 5 + level
	for i := range nb_food {
		x := rand.Intn((x2-1)-(x1+1)) + (x1+1)
		y := y1 - i*3 
		food_pos := coords{x: x, y: y}
		res = append(res, &food_pos)
	}
	return res
}

func DrawFood(s tcell.Screen, y1, y2 int, food_positions []*coords, defStyle tcell.Style, decrement bool) {
	xmax, ymax := s.Size()
	for _, food_pos := range food_positions {
		if food_pos.y > y1 && food_pos.y < (y2-1) {
			drawText(s,food_pos.x,food_pos.y,xmax,ymax,defStyle,"@")
		}
		if food_pos.y < (y2-1) && decrement {
			food_pos.y += 1
		}
	}
}



func main() {
	// Initialize screen
	defStyle := tcell.StyleDefault.Background(color.Black).Foreground(color.NewHexColor(0xf3e5ab))
	s, err := tcell.NewScreen()
	if err != nil {
		log.Fatalf("%+v", err)
	}
	if err := s.Init(); err != nil {
		log.Fatalf("%+v", err)
	}
	s.SetStyle(defStyle)
	
	quit := func() {
		maybePanic := recover()
		s.Fini()
		if maybePanic != nil {
			panic(maybePanic)
		}
	}
	defer quit()

	// Main Menu
	start_game := DrawMainMenu(s, defStyle, false)

	if (!start_game){
		return
	}

	score := 0
	level := 1
	direction := 'n'
	x_offset := 0
	new_level := true
	var food_positions []*coords
	speed := 0
	message := randomQuote(fallingQuotes)
	duration := time.Duration(1000/level)
	ticker := time.NewTicker(duration * time.Millisecond)
game_loop:
	go_to_menu := false
	for {
		if (go_to_menu){
			break
		}
		s.Clear()
			
		xmax,ymax := s.Size()

		length_factor := 5
		width_factor := 4
		// x1,y1 ------- x2,y1
		// |              |
		// |              |
		// x1,y2 ------- x2,y2

		x1 := xmax/width_factor
		x2 := 3*xmax/width_factor
		width_map := x2 - x1 + 1

		y1 := ymax/length_factor
		y2 := 4*ymax/length_factor
		length_map := y2 - y1 + 1
		

		// Draw Game Map Borders
		// upper border
		for i := range(width_map){
			drawText(s,x1+i, y1-1, xmax, ymax, defStyle, "=")
		}
		// bottom border
		for i := range(width_map){
			drawText(s,x1+i, y2, xmax, ymax, defStyle, "=")
		}
		// left border
		for i := range(length_map){
			drawText(s,x1, y1+i, xmax, ymax, defStyle, "|")
		}
		// right border
		for i := range(length_map){
			drawText(s,x2, y1+i, xmax, ymax, defStyle, "|")
		}

		position_cat_x := (x2 + x1)/2 + x_offset
		speed = 2 + (level * 0)
		DrawCat(s, position_cat_x, y2 - 3, xmax,ymax,defStyle, direction)
		if (new_level){
			food_positions = GetFoodPositions(x1,y1,x2,y2,level)
			new_level = false
		}
		
		DrawFood(s,y1,y2,food_positions,defStyle, false)

		
		// Draw UI
		drawText(s, x1, y1 - 6, xmax, ymax, defStyle,   "Use arrows or AD to move. Use Up Arrow or W to speed up")
		drawText(s, x1, y1 - 5, xmax, ymax, defStyle,   "Press P to Pause.")
		drawText(s, x1, y1 - 3, xmax, ymax, defStyle,   fmt.Sprintf("Level: %d", level))
		drawText(s, x1, y1 - 2, xmax, ymax, defStyle,   fmt.Sprintf("Score: %d", score))
		drawText(s, x1, y2 + 1, xmax, ymax, defStyle,   fmt.Sprintf("Vanilla: \"%s\"", message))

		s.Show()
		defer ticker.Stop()
		select{
			case <- ticker.C:
			DrawFood(s,y1,y2,food_positions,defStyle,true)
			s.Sync()
			case ev := <- s.EventQ():
			// Process event
			switch ev := ev.(type) {
			case *tcell.EventResize:
				s.Sync()
			case *tcell.EventKey:
				if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
				return
				} else if (ev.Key() == tcell.KeyRight || ev.Str() == "d" || ev.Str() == "D") {
					direction = 'r'
					if (position_cat_x + speed < (x2-7)){
						x_offset += speed
					} else {
						x_offset = x2 - 7 - (x2 + x1)/2
					}
				} else if (ev.Key() == tcell.KeyLeft || ev.Str() == "a" || ev.Str() == "A") {
					direction = 'l'
					if (position_cat_x - speed > (x1+1)){
						x_offset -= speed
					} else {
						x_offset = (x1+1) - (x2 + x1)/2
					}
				} else if (ev.Key() == tcell.KeyUp || ev.Str() == "w" || ev.Str() == "W") {
					direction = 'n'
				} else if (ev.Str() == "p" || ev.Str() == "P"){
				go_to_menu = true
				}
			}
		}

		// collect_food
		for _, food_pos := range food_positions{
			if (food_pos.y == y2 - 2){
				if (position_cat_x <= food_pos.x && food_pos.x <= position_cat_x + 7) {
					message = randomQuote(catchQuotes)
					score++
				} else {
					message = randomQuote(missQuotes)
				}
			}
		}

		new_level = true
		level++
		for _,food_pos := range food_positions{
			if food_pos.y < y2-3{
				level--
				new_level = false
				break
			}
		}
		
	}
	start_game = DrawMainMenu(s, defStyle, true)
	if (start_game){
		goto game_loop
	}
}
