#!/bin/python3
import re, os, subprocess
import shutil, atexit
import sys, termios, tty
from threading import Thread
from time import sleep

# TODO: Command line args --no-title / -nt, --no-colour / -no, --help / -h and --version / -v

txt = """
.s5SSSs.                                              .s5SSSs.                                    
      SS. .s        .s5SSSs.  .s5SSSSs. .s5SSSs.            SS. .s    s.  .s5SSSs.  s.  .s5SSSs.  
sS    S%S                 SS.       SSS       SS.     sS    `:;       SS.       SS. SS.       SS. 
SS    S%S sS        sS    S%S     sSSS  sS    `:;     SS        sS    S%S sS    S%S S%S sS    S%S 
SS .sSSS  SS        SSSs. S%S    sSS"   SSSs.         `:;;;;.   SS    S%S SS    S%S S%S SS    S%S 
SS    S%S SS        SS    S%S   sSS     SS                  ;;. SS    S%S SS    S%S S%S SS    S%S 
SS    `:; SS        SS    `:;  sSS      SS                  `:; SS    `:; SS    `:; `:; SS    `:; 
SS    ;,. SS    ;,. SS    ;,. sSS       SS    ;,.     .,;   ;,. SS    ;,. SS    ;,. ;,. SS    ;,. 
`:;;;;;:' `:;;;;;:' :;    ;:' `:;;;;;:' `:;;;;;:'     `:;;;;;:' `:;;;;;:' ;;;;;;;:' ;:' `:;;;;;:' 
""" # Font used: amcslash
txt = txt+'\033[0m'

txt = re.sub('([;,]+)', '\033[32m\\1', txt)
txt = re.sub('([5S]+)', '\033[38;2;210;85;80m\\1', txt)
txt = re.sub('([s.]+)', '\033[38;2;255;120;120m\\1', txt)
txt = re.sub(r'(%+)', '\033[38;2;225;60;50m\\1', txt)
txt = re.sub('([`:"\']+)', '\033[92m\\1', txt)

print(txt)
print(' '*8+'\033[91mMade by @Tsunami014\033[0m')
print('\n')

def bracketulate(txt):
    return txt+'\033[0m' #f'\033[93m[{txt}\033[93m]\033[0m'
BracketulateAmount = 1 # The amount of chars used up in a bracketulate

basetxts = {
    'info': '\033[38;5;69m~',
    'error': '\033[38;5;160m-',
    'good': '\033[38;5;28m+',
    'warn': '\033[38;5;202;52;1m!',
    'mid': '\033[38;2;193;156;0m=',
    'bad': '\033[38;5;9m*'
}

txts = {
    i: bracketulate(basetxts[i])+' '+basetxts[i][:-1] for i in basetxts
    #i: basetxts[i]+'\033[0m ' for i in basetxts
}

def highlight(txt):
    return '\033[38;5;172m' + txt + '\033[39m'

def bold(txt):
    return '\033[1m' + txt + '\033[22m'

def header(txt):
    txt = f' {txt} '
    lines = "─"*len(txt)
    return f'\033[35m╭{lines}╮\n│\033[95;15;1;3m{txt}\033[35m│\n╰{lines}╯\033[0m'

class Spinner:
    spins = ['|', '/', '-', '\\']

    @classmethod
    def spinning_cursor(cls):
        while 1:
            for cursor in cls.spins:
                yield bracketulate('\033[38;5;166m'+cursor)

    def __init__(self, txt, delay=0.1):
        self.txt = txt
        self.delay = delay
        self.running = False
        self.requestStop = False
        self.t = None

    def spinner_task(self):
        self.running = True
        spinner_generator = self.spinning_cursor()
        print()
        while not self.requestStop:
            print('\033[F' + self.txt + next(spinner_generator))
            sleep(self.delay)
        self.running = False

    def __enter__(self):
        self.requestStop = False
        self.started = False
        self.t = Thread(target=self.spinner_task, daemon=True).start()
        return self

    def __exit__(self, exception, value, tb):
        self.requestStop = True
        while self.running:
            pass

# Save the original terminal attributes globally so we can restore them later
orig_termios = None

def enable_input_mode():
    global orig_termios
    fd = sys.stdin.fileno()
    orig_termios = termios.tcgetattr(fd)
    tty.setcbreak(fd) # or tty.setraw(fd) if you prefer raw mode

def disable_input_mode():
    global orig_termios
    if orig_termios:
        termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, orig_termios)
        orig_termios = None

atexit.register(disable_input_mode)

def getch():
    if orig_termios is None:
        enable_input_mode()
    try:
        return sys.stdin.read(1)
    finally:
        disable_input_mode()

def options(title, opts, default=0):
    prevchrs = 0
    opt = default

    def print_opts():
        print(f'\033[K{bracketulate('\033[38;5;166m?')} \033[92;11m{title}: \033[90m(Use arrow keys) \033[94m{opts[opt]}\033[0m')
        for i in range(len(opts)):
            t = opts[i]
            spaces = ' '*(BracketulateAmount+1)
            if i == opt:
                print(f'\033[94;1;3m>{spaces[:-1]}{t}\033[0m')
            else:
                print(f'{spaces}{t}\033[0m')
    print_opts()

    def update_opts():
        nonlocal prevchr
        prevchr = 0
        print(f'\033[{len(opts)+2}B', end='')
        print('\033[F'*(len(opts)+2))
        print_opts()
        print('\033[F'*(len(opts)-opt)+'\033[2C', end='')
    
    update_opts()
    
    while True:
        char = getch()
        if char == '\x1b':
            prevchr = 1
        elif char == '[' and prevchr == 1:
            prevchr = 2
        elif char == 'A' and prevchr == 2 and opt > 0:
            opt -= 1
            update_opts()
        elif char == 'B' and prevchr == 2 and opt < len(opts)-1:
            opt += 1
            update_opts()
        elif char == '5' and prevchr == 2:
            opt = 0
            update_opts()
        elif char == '6' and prevchr == 2:
            opt = len(opts)-1
            update_opts()
        elif char == '\n':
            break
        else:
            prevchr = 0
    print(f'\033[F\033[{len(opts)+2}B'+'\033[F\033[K'*len(opts), end='')
    disable_input_mode()
    return opt

def ask(title, keyOpts, newline=False, printAtEnd=False):
    print(f'{bracketulate('\033[38;5;166m?')} \033[92m{title}{"\n" if newline else " "}\033[94m> ', end='', flush=True)
    while True:
        k = getch()
        if k in keyOpts:
            disable_input_mode()
            if printAtEnd:
                print(k+'\033[0m')
            return keyOpts.index(k)

def askInp(title, newline=False):
    disable_input_mode()
    return input(f'{bracketulate('\033[38;5;166m?')} \033[92m{title}{"\n" if newline else " "}\033[94m> ')

l = ['Make a new project here 📁', 'Make a new project in a subdirectory 🗃️', 'Open docs 📚️', '\033[35mQuit 👋']
chosen = options('What would you like to do?', l)

def emptyFolder(folder): # From https://stackoverflow.com/questions/185936/how-to-delete-the-contents-of-a-folder
    for filename in os.listdir(folder):
        file_path = os.path.join(folder, filename)
        try:
            if os.path.isfile(file_path) or os.path.islink(file_path):
                os.unlink(file_path)
            elif os.path.isdir(file_path):
                shutil.rmtree(file_path)
        except Exception as e:
            print('Failed to delete %s. Reason: %s' % (file_path, e))

def makeNewProject(where):
    print(txts['info']+'Making project in '+where+'...')
    usingGit = False
    if subprocess.run(['git', '--version'], stdout=subprocess.DEVNULL).returncode == 0:
        if ask(f'Do you want to initialise a git repo? \033[90m[y/n]', ['y', 'Y', 'n', 'N']) in (0, 1):
            print('Yes\033[0m')
            subprocess.run(['git', 'init', where], stdout=subprocess.DEVNULL)
            usingGit = True
        else:
            print('No\033[0m')
    else:
        print(txts['info']+'Git not found, so cannot make a repo!')
    with open(os.path.join(where, 'README.md'), 'w+') as f:
        f.write('# '+(askInp('What is the name of the project? \033[90m(My Project)') or 'My Project'))
    with open(os.path.join(where, (askInp('What is the name of the main file? \033[90m(main.py)') or 'main.py')), 'w+') as f:
        # TODO: Various default files
        f.write("""from BlazeSudio.Game import Game
from BlazeSudio import collisions
import BlazeSudio.Game.statics as Ss

G = Game()

@G.DefaultSceneLoader
class MainGameScene(Ss.BaseScene):
    pass

G.load_scene()
G.play(debug=True)
""")
    with open(os.path.join(where, 'requirements.txt'), 'w+') as f:
        f.write('Blaze-Sudio\n')
    if usingGit:
        subprocess.run(['git', 'add', '-A'], cwd=where, stdout=subprocess.DEVNULL)
    if subprocess.run(['code', '--version'], stdout=subprocess.DEVNULL).returncode == 0:
        if ask(f'Do you want to launch VSCode? \033[90m[y/n]', ['y', 'Y', 'n', 'N']) in (0, 1):
            print('Yes\033[0m')
            subprocess.run(['code', where], stdout=subprocess.DEVNULL)
        else:
            print('No\033[0m')
    else:
        print(txts['info']+'VSCode not found, so cannot open!')
    # TODO: Ask if add empty ldtk file
    # TODO: Ask if use direnv and/or if should add a venv (if have direnv)

# TODO: Templates
if chosen == 0:
    print(f'\n{header("NEW PROJECT")}')
    if os.listdir(os.getcwd()) != []:
        resp = options(f'Path {os.getcwd()} has things in it! What do you want to do?', [f'{bold("Override")} it & {highlight("delete everything in it")} 🔥', '\033[35mQuit 👋'])
        if resp == 0:
            if ask(highlight(f'Are you sure you want to {bold("override")} the folder {os.getcwd()} and delete everything in it?')+' \033[90m[y/n]', ['y', 'Y', 'n', 'N']) in (0, 1):
                print('Yes\033[0m')
                dr = os.getcwd()
                print(txts['warn']+'Deleting all files in '+dr+'...')
                emptyFolder(dr)
                makeNewProject(dr)
            else:
                print('No\033[0m')
                print(txts['good']+'OK, quitting!')
                complete = True
        else:
            print(txts['good']+'OK, quitting!')
            complete = True
    else:
        makeNewProject(os.getcwd())
elif chosen == 1:
    print(f'\n{header("NEW PROJECT")}')
    complete = False
    while not complete:
        where = os.path.join(os.getcwd(), askInp('What would you like to name the folder? \033[90m(Type nothing to exit)').replace('/', '_'))
        if where == os.getcwd():
            print(txts['good']+'OK, exiting!')
            complete = True
            continue
        if os.path.exists(where):
            if os.listdir(where) != []:
                resp = options(f'Path %s exists and has things in it! What do you want to do?'%where, [f'{bold("Override")} it & {highlight("delete everything in it")} 🔥', 'Choose again 🤔', '\033[35mQuit 👋'])
                if resp == 0:
                    if ask(highlight(f'Are you sure you want to {bold("override")} the folder {where} and delete everything in it"?')+' \033[90m[y/n]', ['y', 'Y', 'n', 'N']) in (0, 1):
                        print('Yes\033[0m')
                        print(txts['warn']+'Deleting folder '+where+'...')
                        emptyFolder(where)
                        makeNewProject(where)
                        complete = True
                    else:
                        print('No\033[0m')
                elif resp == 1:
                    pass
                else:
                    print(txts['good']+'OK, quitting!')
                    complete = True
            else:
                print(txts['info']+'Directory %s exists, but is empty. Using that.'%where)
                makeNewProject(where)
                complete = True
        else:
            os.mkdir(where)
            makeNewProject(where)
            complete = True
elif chosen == 2:
    print(f'\n{header("DOCS")}')
    if ask(f'Would you like to open the {bold("offline")} 🖥️  docs {highlight("(1)")} or the {bold("online")} 🌐 docs {highlight("(2)")}?', ['1', '2']) == 0:
        print('Offline\033[0m')
        print(txts['good']+'OK! Opening offline docs...')
        print(txts['bad']+'NOTIMPLEMENTED')
    else:
        print('Online\033[0m')
        print(txts['good']+'OK! Opening online docs...')
        import webbrowser
        webbrowser.open('https://tsunami014.gitbook.io/blaze-sudios')
else: # quit
    print(txts['good']+'OK, quitting!')

print('\033[0m', end='')