import random
import time
import sys
import termios
from rich.live import Live
from rich.text import Text
from rich.panel import Panel
from rich.console import Console
import argparse

console = Console()

#Setup arguments for argparser
parser = argparse.ArgumentParser(description="A visual cellular automaton simulator")
parser.add_argument("--seed", help='Specify seed', type=int)
parser.add_argument("--prob", help='Specify the probability that each cell is alive (from 1-100)', type=int)
parser.add_argument("--infinite", action='store_true', help='Generate new seed on complete death')
parser.add_argument("--customrules", help="Specify custom game rules (B##/S##)", type=str)
parser.add_argument('--customcolors', help='Specify custom colours for living and dead cells', type=str)
parser.add_argument('--randomcolors', action='store_true', help='Make alive cells generate with random colors')
parser.add_argument('--age', action='store_true', help='Darken colours of cells as they get older')
_saved_settings = None
args=parser.parse_args()


def save_terminal_settings():
    global _saved_settings
    fd = sys.stdin.fileno()
    _saved_settings = termios.tcgetattr(fd)
    return _saved_settings

def restore_terminal_settings():
    global _saved_settings
    if _saved_settings:
        fd = sys.stdin.fileno()
        termios.tcsetattr(fd, termios.TCSADRAIN, _saved_settings)

def disable_echo():
    fd = sys.stdin.fileno()
    new_attr = termios.tcgetattr(fd)
    new_attr[3] = new_attr[3] & ~termios.ECHO 
    termios.tcsetattr(fd, termios.TCSANOW, new_attr)

def init_grid(seed):
    columns = console.size.width - 4
    rows = console.size.height // 2 - 1
    if args.seed and seed != None: 
        random_seed = args.seed
    else:
        random_seed = random.randint(0, 10000000000000)
    if args.prob: 
        living_probability = args.prob
    else:
        living_probability = random.randint(35, 65)
    print (random_seed)
    grid = [[0 for _ in range(columns)] for _ in range(rows)]
    random.seed(random_seed)
    for y in range(1, columns-1):
        for x in range(1, rows-1):
            state=random.randint(1, 100)
            if state<=living_probability:
                grid[x][y] = 1
    birth = [3]
    survive = [2, 3]

    if args.customrules:
        try:
            rules_raw = args.customrules.upper().split("/S")
            birth = [int(n) for n in rules_raw[0][1:]]
            survive = [int(n) for n in rules_raw[1]]
        except Exception:
            print("Invalid custom rule format. Use B##/S## (example: B3/S23)")
            sys.exit(1)
    if args.customcolors:
        try:
            colours_raw=args.customcolors.upper().split('/D')
            alive_colour_str = colours_raw[0][1:]
            dead_colour_str = colours_raw[1]
            alive_colour = [int(alive_colour_str[0:3]), int(alive_colour_str[3:6]), int(alive_colour_str[6:9])]
            dead_colour = [int(dead_colour_str[0:3]), int(dead_colour_str[3:6]), int(dead_colour_str[6:9])]

        except Exception:
            print("Invalid custom rule format. Use ARRRGGGBBB/DRRRGGGBBB")
            sys.exit(1)
    else:
        alive_colour = [0, 84, 81]
        dead_colour = [140, 3, 3]
    return rows, columns, grid, birth, survive, alive_colour, dead_colour


def sum_surrounding(grid, x, y):
    return (
        grid[x+1][y] +
        grid[x-1][y] +
        grid[x][y+1] +
        grid[x][y-1] +
        grid[x+1][y+1] +
        grid[x+1][y-1] +
        grid[x-1][y+1] +
        grid[x-1][y-1]
    )


def update_array(grid, rows, columns, birth, survive):
    new_grid = [row[:] for row in grid]

    for x in range(1, rows-1):
        for y in range(1, columns-1):

            neighbours = sum_surrounding(grid, x, y)

            if grid[x][y] == 0 and neighbours in birth:
                new_grid[x][y] = 1
            elif grid[x][y] != 0 and neighbours in survive:
                new_grid[x][y] += 1
            else:
                new_grid[x][y] = 0

    return new_grid


def render_grid(grid, alive_colours, dead_colours):
    text = Text()

    for row in grid:
        for cell in row:
            if cell and not args.randomcolors:
                if args.age:
                    text.append("██", style=f"rgb({max(alive_colours[0] - cell * 20, 0)},{max(alive_colours[1] - cell * 20, 0)},{max(alive_colours[2] - cell * 20, 0)})")
                else:
                    text.append("██", style=f"rgb({alive_colours[0]},{alive_colours[1]},{alive_colours[2]})")
            elif cell and args.randomcolors:
                text.append("██", style=f"rgb({random.randint(0, 255)},{random.randint(0, 255)},{random.randint(0, 255)})")
            else:
                text.append("██", style=f"rgb({dead_colours[0]},{dead_colours[1]},{dead_colours[2]})")
        text.append("\n")
    return Panel(text, title="Game of Life", border_style="cyan")

def main():
    rows, columns, grid, birth, survive, alive_colours, dead_colours = init_grid(None)

    save_terminal_settings()
    disable_echo()

    try:
        with Live(render_grid(grid, alive_colours, dead_colours), refresh_per_second=20, screen=True) as live:
            while True:
                time.sleep(0.2)
                grid = update_array(grid, rows, columns, birth, survive)
                live.update(render_grid(grid, alive_colours, dead_colours))
                alive = 0
                for row in grid:
                    for cell in row:
                        if cell:
                            alive += 1
                if alive <= 1 and args.infinite:
                    rows, columns, grid, birth, survive, alive_colours, dead_colours = init_grid(True)
                    time.sleep(1)
    except KeyboardInterrupt:
        pass
    finally:
        restore_terminal_settings()

if __name__ == '__main__':
    main()
