package main

import "core:fmt"
import "core:math/rand"
import os "core:os/os2"
import "core:strconv"
import "core:time"

import "./console"

// TODO: Refactor combat (only use monster array for base values)
// TODO: Colors
// TODO: TUI
// TODO: Refactor message history
// TODO: Add randomised varied messages
// TODO(console): need major cleanup, better color and drawing support (virtual bitmap), functional non-blocking keyboard input, etc
// TODO(console): look over signal management
// TODO: Print final statistics on stop/quit
// TODO: Save progress

/*
// Source - https://stackoverflow.com/a
// Posted by Lucas S.
// Retrieved 2026-01-14, License - CC BY-SA 3.0

int khbit() const
{
    struct timeval tv;
    fd_set fds;
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds);
    select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
    return FD_ISSET(STDIN_FILENO, &fds);
}

void nonblock(int state) const
{
    struct termios ttystate;
    tcgetattr(STDIN_FILENO, &ttystate);

    if ( state == 1)
    {
        ttystate.c_lflag &= (~ICANON & ~ECHO); //Not display character
        ttystate.c_cc[VMIN] = 1;
    }
    else if (state == 0)
    {
        ttystate.c_lflag |= ICANON;
    }
    tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
}

bool keyState(int key) const //Use ASCII table
{
    bool pressed;
    int i = khbit(); //Alow to read from terminal
    if (i != 0)
    {
        char c = fgetc(stdin);
        if (c == (char) key)
        {
            pressed = true;
        }
        else
        {
            pressed = false;
        }
    }

    return pressed;
}

int main()
{
    nonblock(1);
    int i = 0;
    while (!i)
    {
        if (cmd.keyState(32)) //32 in ASCII table correspond to Space Bar
        {
            i = 1;
        }
    }
    nonblock(0);

    return 0;
}
*/

Segment_Kind :: enum {
	Empty,
	Monster,
	Treasure,
}

Segment_Data :: union {
	Empty,
	Monster,
	Treasure,
}

Segment :: struct {
	data: Segment_Data,
}

Glyph :: struct {
	glyph: rune,
	// color
	// options
}

Empty :: struct {
	using _: Glyph,
}

Hero :: struct {
	using _:      Glyph,
	state:        Hero_State,
	level:        int,
	hp:           int,
	max_hp:       int,
	base_atk:     int,
	base_def:     int,
	xp:           int,
	xp_to_level:  int,
	weapon_tier:  int,
	armor_tier:   int,
	weapon_bonus: int,
	armor_bonus:  int,
	gold:         int,
}

Hero_State :: enum {
	Walking,
	Combat,
	Dead,
}

Monster :: struct {
	using _:           Glyph,
	name:              string,
	hp:                int,
	atk:               int,
	def:               int,
	xp_reward:         int,
	weight:            int,
	floor_requirement: int,
}

Treasure :: struct {
	using _: Glyph,
	kind:    Treasure_Kind,
	tier:    int,
	value:   int,
}

Treasure_Kind :: enum {
	Heal,
	Gold,
	Weapon,
	Armor,
}

MONSTERS := []Monster {
	{glyph = 'g', name = "Goblin", hp = 10, atk = 3, def = 0, xp_reward = 5, weight = 30, floor_requirement = 1},
	{glyph = 's', name = "Slime", hp = 1, atk = 1, def = 0, xp_reward = 1, weight = 40, floor_requirement = 0},
	{glyph = 'o', name = "Orc", hp = 20, atk = 6, def = 2, xp_reward = 12, weight = 5, floor_requirement = 7},
	{glyph = 'M', name = "Minotaur", hp = 40, atk = 10, def = 4, xp_reward = 35, weight = 1, floor_requirement = 9},
}

TREASURE := []Treasure {
	{glyph = '♡', kind = .Heal},
	{glyph = '$', kind = .Gold},
	{glyph = 'W', kind = .Weapon},
	{glyph = 'A', kind = .Armor},
}

generate_floor :: proc(segments: []Segment, current_floor: int, floor_weights: [Segment_Kind]int) {
	floor_total_weights: int
	for w in floor_weights {
		floor_total_weights += w
	}

	monster_total_weights: int
	for m in MONSTERS {
		if current_floor >= m.floor_requirement {
			monster_total_weights += m.weight
		}
	}

	for &segment in segments {
		segment_kind: Segment_Kind
		roll := rand.int_max(floor_total_weights)
		running := 0
		for w, i in floor_weights {
			running += w
			if roll < running {
				segment_kind = i
				break
			}
		}

		switch segment_kind {
		case .Empty:
			segment.data = Empty {
				glyph = '-',
			}
		case .Monster:
			monster: Monster

			m_roll := rand.int_max(monster_total_weights)
			m_running := 0
			for m in MONSTERS {
				if current_floor < m.floor_requirement {
					continue
				}
				m_running += m.weight
				if m_roll < m_running {
					monster = m
					break
				}
			}

			monster.hp += current_floor * 2
			monster.atk += current_floor
			if current_floor % 2 == 0 {
				monster.def += current_floor
			}

			segment.data = monster
		case .Treasure:
			treasure := TREASURE[rand.int_max(len(TREASURE))]
			if roll >= 95 {
				#partial switch treasure.kind {
				case .Gold:
					treasure.value += current_floor * 2
				case .Weapon:
					treasure.tier = current_floor / (2 + (rand.int_max(2) - 1))
					treasure.value = treasure.tier * 3
				case .Armor:
					treasure.tier = current_floor / (2 + (rand.int_max(2) - 1))
					treasure.value = treasure.tier * 2
				}
			} else {
				#partial switch treasure.kind {
				case .Gold:
					treasure.value += current_floor
				case .Weapon:
					treasure.tier = current_floor / (3 + (rand.int_max(2) - 1))
					treasure.value = treasure.tier * 2
				case .Armor:
					treasure.tier = current_floor / (3 + (rand.int_max(2) - 1))
					treasure.value = treasure.tier
				}
			}
			segment.data = treasure
		}
	}
}

main :: proc() {
	ticks_per_second := 5
	if len(os.args) > 1 {
		for arg, i in os.args {
			if arg == "-ticks" {
				if i + 1 >= len(os.args) {
					continue
				}
				ticks := strconv.parse_int(os.args[i + 1]) or_else 5
				ticks_per_second = ticks if ticks > 0 else ticks_per_second
			}
		}
	}

	con := console.create()
	defer console.destroy(&con)

	current_floor := 0

	floor_weights := [Segment_Kind]int {
		.Empty    = 60,
		.Monster  = 25,
		.Treasure = 10,
	}

	floor_segments := [30]Segment{}
	generate_floor(floor_segments[:], current_floor, floor_weights)

	hero := Hero {
		glyph        = '@',
		state        = .Walking,
		level        = 1,
		hp           = 20,
		max_hp       = 20,
		base_atk     = 3,
		base_def     = 1,
		xp           = 0,
		xp_to_level  = 10,
		weapon_tier  = 0,
		armor_tier   = 0,
		weapon_bonus = 0,
		armor_bonus  = 0,
		gold         = 0,
	}
	deaths := 0
	hero_position := 0
	hero_attacking := true

	message_history: [128]string
	message_history_slot: int

	ticks_ms := time.Duration(1000 / ticks_per_second) * time.Millisecond
	last_tick := time.now()
	running := true
	for running {
		now := time.now()
		elapsed := time.diff(last_tick, now)
		if elapsed > ticks_ms {
			// LOGIC
			switch hero.state {
			case .Walking:
				hero_position += 1
				if hero.hp < hero.max_hp {
					hero.hp += 1
				}

				if hero_position >= len(floor_segments) {
					current_floor += 1
					hero_position = 0
					message_history[message_history_slot] = "Found a stairwell, going deeper..."
					message_history_slot = (message_history_slot + 1) % len(message_history)
					generate_floor(floor_segments[:], current_floor, floor_weights)
				}

				switch v in floor_segments[hero_position].data {
				case Empty:
				case Monster:
					hero.state = .Combat
					hero_attacking = true
					message_history[message_history_slot] = "A monster!"
					message_history_slot = (message_history_slot + 1) % len(message_history)
				case Treasure:
					treasure := &floor_segments[hero_position].data.(Treasure)
					switch treasure.kind {
					case .Heal:
						hero.hp += hero.max_hp / 3
						if hero.hp > hero.max_hp {
							hero.hp = hero.max_hp
						}
						message_history[message_history_slot] = "Found a healing potion. Tasty!"
						message_history_slot = (message_history_slot + 1) % len(message_history)
					case .Gold:
						hero.gold += treasure.value
						message_history[message_history_slot] = "Picked up gold"
						message_history_slot = (message_history_slot + 1) % len(message_history)
					case .Weapon:
						if treasure.tier > hero.weapon_tier {
							hero.weapon_tier = treasure.tier
							hero.weapon_bonus = treasure.value
							message_history[message_history_slot] = "Found a better weapon"
							message_history_slot = (message_history_slot + 1) % len(message_history)
						} else {
							message_history[message_history_slot] = "Found a worse weapon"
							message_history_slot = (message_history_slot + 1) % len(message_history)
						}
					case .Armor:
						if treasure.tier > hero.armor_tier {
							hero.armor_tier = treasure.tier
							hero.armor_bonus = treasure.value
							message_history[message_history_slot] = "Found a better armor"
							message_history_slot = (message_history_slot + 1) % len(message_history)
						} else {
							message_history[message_history_slot] = "Found a worse armor"
							message_history_slot = (message_history_slot + 1) % len(message_history)
						}
					}

					floor_segments[hero_position].data = Empty {
						glyph = '-',
					}
				}
			case .Combat:
				monster := &floor_segments[hero_position].data.(Monster)
				if hero_attacking {
					message_history[message_history_slot] = "Swing"
					message_history_slot = (message_history_slot + 1) % len(message_history)

					dmg := (hero.base_atk + hero.weapon_bonus) - monster.def
					monster.hp -= dmg if dmg > 1 else 1
					if monster.hp <= 0 {
						monster.glyph = 'x'
						hero.xp += monster.xp_reward
						hero.state = .Walking
						message_history[message_history_slot] = "I'm victorious!"
						message_history_slot = (message_history_slot + 1) % len(message_history)
						break
					}
				} else {
					message_history[message_history_slot] = "Ouch!"
					message_history_slot = (message_history_slot + 1) % len(message_history)

					dmg := monster.atk - (hero.base_def + hero.armor_bonus)
					hero.hp -= dmg if dmg > 1 else 1
					if hero.hp <= 0 {
						message_history[message_history_slot] = "DEATH"
						message_history_slot = (message_history_slot + 1) % len(message_history)
						hero.state = .Dead
						break
					}
				}
				hero_attacking = !hero_attacking
			case .Dead:
				// fmt.printf("\x1B[5B\x1b[2KYOU DIED!\n")
				// running = false
				message_history[message_history_slot] = "Respawning..."
				message_history_slot = (message_history_slot + 1) % len(message_history)
				deaths += 1
				current_floor = 0
				hero_position = 0
				hero.hp = hero.max_hp
				hero.gold = 0
				hero.weapon_tier = 0
				hero.armor_tier = 0
				hero.weapon_bonus = 0
				hero.armor_bonus = 0
				hero.xp = 0
				hero.state = .Walking
				generate_floor(floor_segments[:], current_floor, floor_weights)
				continue
			}

			// NOTE: loop to add multiple levels if enough xp was gained
			if hero.xp >= hero.xp_to_level {
				for hero.xp >= hero.xp_to_level {
					hero.xp -= hero.xp_to_level
					hero.xp_to_level = hero.level * 10
					hero.level += 1

					hero.max_hp += 5
					hero.hp = hero.max_hp
					hero.base_atk += 1
					if hero.level % 2 == 0 {
						hero.base_def += 1
					}
				}
				message_history[message_history_slot] = "☆ Level up! ☆"
				message_history_slot = (message_history_slot + 1) % len(message_history)
			}

			// RENDER (this should probably be stringbuilt instead of multiple printfs)
			fmt.printf("\x1b[2KFloor: %d (deaths: %d)\n", current_floor + 1, deaths)
			fmt.printf("\x1b[2KLevel: %d", hero.level)
			fmt.printf(" [")
			progress := int((f32(hero.xp) / f32(hero.xp_to_level)) * 10)
			for i in 0 ..< 10 {
				if i > progress {
					fmt.printf("-")
				} else if i < progress {
					fmt.printf("=")
				} else {
					fmt.printf(">")
				}
			}
			fmt.printf("]\n")
			fmt.printf("\x1b[2KHP: %d/%d, Gold: %d\n", hero.hp, hero.max_hp, hero.gold)
			fmt.printf(
				"\x1b[2KW: %d(+%d), A: %d(+%d)\n",
				hero.weapon_tier,
				hero.weapon_bonus,
				hero.armor_tier,
				hero.armor_bonus,
			)
			fmt.printf(
				"\x1b[2KDMG: %d, ARM: %d\n",
				hero.base_atk + hero.weapon_bonus,
				hero.base_def + hero.armor_bonus,
			)
			fmt.printf("\x1b[2K")
			for segment, i in floor_segments {
				if i == hero_position {
					fmt.printf("%c", hero.glyph)
				} else {
					switch v in segment.data {
					case Empty:
						fmt.printf("%c", segment.data.(Empty).glyph)
					case Monster:
						fmt.printf("%c", segment.data.(Monster).glyph)
					case Treasure:
						fmt.printf("%c", segment.data.(Treasure).glyph)
					}
				}
			}
			fmt.printf("\x1B[5A\x1b[0G")
			fmt.printf(
				"\x1b[40C\x1b[0m\x1b[38;5;27m╭─⟦ᚠ⟧──────────────────────────────────⟦ᚱ⟧─╮\n",
			)
			current_message_history_slot := (message_history_slot + len(message_history) - 10) % len(message_history)
			for current_message_history_slot != message_history_slot {
				fmt.printf(
					"\x1b[40C\x1b[38;5;24m│\x1b[0m %40s \x1b[38;5;24m│\n",
					message_history[current_message_history_slot],
				)
				current_message_history_slot = (current_message_history_slot + 1) % len(message_history)
			}
			fmt.printf(
				"\x1b[40C\x1b[38;5;21m╰─⟦ᚦ⟧──────────────────────────────────⟦ᚨ⟧─╯\n\x1b[0m",
			)
			fmt.printf("\x1B[12A")

			last_tick = now
		}

		running = !console.should_quit()
		/*if console.wait_for_input(con) != .none {
			fmt.printf("KEY\n")
		}*/

		time.sleep(time.Millisecond)
	}
	fmt.printf("\x1b[2KEND\n")
}