#!/usr/bin/env python3
__version__ = "1.0.0"
# YOU MUST INCLUDE THE SLASH AT THE END
WEBSITE = "https://gfl.amaryllisworks.pw/"
#WEBSITE = "http://127.0.0.1:8000/"

# imports
from typing import BinaryIO, List, Any, Dict, Set, Tuple, TypedDict, Callable, ContextManager
from typing_extensions import Literal, Union
from collections.abc import Generator
from contextlib import contextmanager, suppress

#import urllib.request
import re
import argparse
import sys
import requests
import json
import io
import os
from pathlib import Path
import inquirer
from http.server import HTTPServer, SimpleHTTPRequestHandler
from concurrent.futures import ThreadPoolExecutor

# Helper functions from gfLib...

class bcolors:
	HEADER = '\033[95m'
	OKBLUE = '\033[94m'
	OKGREEN = '\033[92m'
	WARNING = '\033[93m'
	FAIL = '\033[91m'
	ENDC = '\033[0m'
	BOLD = '\033[1m'
	UNDERLINE = '\033[4m'
    
def printWarn(text):
	print(bcolors.WARNING + text + bcolors.ENDC)
	
def printError(text):
	print(bcolors.FAIL + text + bcolors.ENDC)
	
def printOK(text):
	print(bcolors.OKGREEN + text + bcolors.ENDC)

# Source - https://stackoverflow.com/a/77962065
# Posted by andyhasit
# Retrieved 2026-03-17, License - CC BY-SA 4.0
def insert_into_dict(existing:dict, pos:int, key:Any, value:Any):
	keys = list(existing.keys())
	keys.insert(pos, key)
	return {k: existing.get(k, value) for k in keys}



import importlib.util
if importlib.util.find_spec("inquirer") is None:
	print(bcolors.FAIL + "Hey moron, you didn't install the dependencies.")
	print("Run pip install -r requirements.txt and try again."+bcolors.ENDC)
	sys.exit(1)

def host_http_server(port:int=8000):
	os.chdir("data")
	server = HTTPServer(("0.0.0.0", port), SimpleHTTPRequestHandler)
	print(f"Navigate to http://localhost:{port} in your web browser to access the interpreter.")
	server.serve_forever()

def download_file(url, destination):
	resp:requests.Response  = requests.get(url, allow_redirects=True)
	resp.raise_for_status()
	
	dest_path = Path(destination).parent
	os.makedirs(dest_path, exist_ok=True)
	with open(destination,'wb') as outFile:
		outFile.write(resp.content)
		

# Main program goes here
if __name__ == "__main__":
	from rich import print
	from rich.layout import Layout, LayoutRender
	from rich.progress import track, Progress
	from rich.live import Live
	from rich.text import Text
	from rich.console import Console
	from rich.panel import Panel
	#from downloader import download
	from time import sleep

	parser = argparse.ArgumentParser(description="Download GFL interpreter website")
	#parser.add_argument("host",action='store_true',help="Host the web server. If used in conjunction with --update, will host after updating.")
	#parser.add_argument("update",action='store_true',help="Update the site files.")
	parser.add_argument("--port",type=int,help="Port to use for hosting, from 1024 to 65535.", default=8000)
	parser.add_argument(
		"mode",
		nargs="?",
		choices=["run", "update"],
		#default=None,
		help="Whether to run the interpreter or update it. If not specified it will ask."
	)

	args = parser.parse_args()

	if not args.mode:
		if os.path.exists(os.path.join("data","index.html")):
			choice = inquirer.list_input(
				"What would you like to do?",
				choices=["Update the interpreter's files","Use the interpreter"]
			)
			if choice is None:
				sys.exit(-1)
			elif choice == "Use the interpreter":
				args.mode = "run"
			else:
				args.mode = "update"
		else: #If no file exists, then we can't run it. So default to update
			args.mode = "update"

	if args.mode == "run":
		host_http_server(args.port)
	else:
		os.makedirs("data", exist_ok=True)

		#Always download these files
		files = [
			"index.html",
			"stylesheet.css",
			"script.js",
			"client-side-translator.js",
			"portraitInformation.json",
			"chapterDatabase.json",
		]
		for f in files:
			download_file(WEBSITE+f,os.path.join("data",f))
			print("Downloaded "+f)

		with open(os.path.join("data","index.html"), 'r', encoding='utf-8') as f:
			data = f.read()
			data = data.replace("https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css","materialize.min.css")
			data = data.replace("https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js","materialize.min.js")

		with open(os.path.join("data","index.html"), 'w', encoding='utf-8') as f_out:
			f_out.write(data)

		m_css = os.path.join("data","materialize.min.css")
		if not os.path.exists(m_css):
			download_file("https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css",m_css)
			print("Downloaded "+m_css)
		m_js = os.path.join("data","materialize.min.js")
		if not os.path.exists(m_js):
			download_file("https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js", m_js)
			print("Downloaded "+m_js)

		with open(os.path.join("data","chapterDatabase.json"), encoding="utf-8") as f:
			DATABASE = json.load(f)
			#MUSIC:Dict[str,str] = DATABASE['music']
			MUSIC:Set[str] = set([f+".ogg" for f in DATABASE['music'].values()])
			SFX:Set[str] = set([f+".ogg" for f in DATABASE['se'].values()])
			AVGTEXTURE:Set[str] = set([f+".png" for f in DATABASE['bg']])
			VIDEOS = [ '2021Winter.mp4', '2023SpringEd_2.mp4', '202404ActivityPV.mp4', '202406ActivityPV.mp4', '202407ActivityPV.mp4', '202408ActivityPV.mp4', '202410ActivityPV.mp4', 'AllSeeingEye01.mp4', 'AllSeeingEye02.mp4', 'AllSeeingEye03.mp4', 'AllSeeingEye04.mp4', 'DoomsdayClock01.mp4', 'DoomsdayClock02.mp4', 'DoomsdayClock03.mp4', 'DoomsdayClock04.mp4', 'DoomsdayClock05.mp4', 'DoomsdayClock06.mp4', 'DoomsdayClock07.mp4' ]

			with open(os.path.join("data","portraitInformation.json"), encoding="utf-8") as ff:
				tmp:Dict[str,List[str]] = json.load(ff)
				AVGPIC:List[str] = []
				for v in tmp.values():
					for f in v:
						if f: #Skip empty entries
							AVGPIC.append(f+".png")
					#AVGPIC.extend([f+".png" for f in v])
					#AVGPIC.extend(v)

		"""The root folder and the files within them. The downloader will join the key+value together."""
		FOLDERS = {
			"assets":[ 'favicon-16x16.png', 'favicon-32x32.png', 'favicon.ico', 'maskBox2.png', 'maskBox.png', 'snow1.png', 'snow2.png', 'snow3.png' ],
			'audio':MUSIC,
			"avgtexture":AVGTEXTURE,
			"pic":AVGPIC,
			"sfx":SFX,
			'video':VIDEOS,
			#This gets added later in the for loop below
			#"avgtxt":[]
		}
		#print(promptResult['Language'])
		#sys.exit(0)

		# for count in track(FOLDERS):
		# 	#print(count)
		# 	for letter in track("ABCDEF", transient=True):
		# 		print(f"Stage {count}{letter}")
		# 		sleep(0.1)

		# 	sleep(0.1)

		layout = Layout()
		layout.split_column(
			Layout(name="upper"),
			Layout(name="lower"),
			Layout(name="total"),
		)
		#print(layout)

		#logger = Text()
		log = []

		#There is no way to reorder or insert tasks in tasks_progress so we have to preload them and then generate the tasks[] array
		tasks_preload:List[Tuple[str,int]] = [
			("Download main site files...",len(FOLDERS['assets'])),
			("Download music...", len(MUSIC)),
			("Download backgrounds...", len(AVGTEXTURE)),
			("Download character images...", len(AVGPIC)),
			("Download sound effects...", len(SFX)),
			("Download videos...", len(VIDEOS))
		]
		# tasks = [
		# 	tasks_progress.add_task("Download main site files...", total=len(FOLDERS['assets'])),
		# 	tasks_progress.add_task("Download music...", total=len(MUSIC)),
		# 	tasks_progress.add_task("Download backgrounds...", total=len(AVGTEXTURE)),
		# 	tasks_progress.add_task("Download character images...", total=len(AVGPIC)),
		# 	tasks_progress.add_task("Download sound effects...", total=len(SFX)),
		# 	tasks_progress.add_task("Download videos...", total=len(VIDEOS)),
		# ]


		promptResult = inquirer.prompt([
			inquirer.Checkbox(
				"Language",
				message="What languages to download? (Select at least one. Use space to select)",
				choices=[
					("English","en"),
					("Chinese","ch"),
					("Japanese","jp"),
					("Korean","kr"),
					("Russian (Fan Translation)","ru")
				],
				default=['en','ch']
			)
		])
		if promptResult is None or promptResult['Language'] is None:
			sys.exit(1)
		for avgtxt_lang in promptResult['Language']:
			os.makedirs(os.path.join("data","avgtxt",avgtxt_lang), exist_ok=True)

			all_files = []
			js = DATABASE['story']
			for t in js: #main, side, etc
				for i in range(len(js[t])):
					for j in range(len(js[t][i]['episodes'])):
						ep = js[t][i]['episodes'][j]
						for part in ep['parts']:
							all_files.append(part)
							#FOLDERS['avgtxt'].append('')
			
			#os.path.join here is safe, it will get rewritten to Unix paths for the downloader.
			k = os.path.join("avgtxt",avgtxt_lang)
			#FOLDERS[k] = all_files
			FOLDERS = insert_into_dict(FOLDERS, 1, k, all_files)
			tasks_preload.insert(1, ("Download text for language "+avgtxt_lang, len(all_files)))


		#Generate the tasks list here
		tasks_progress = Progress()
		tasks = [ tasks_progress.add_task(desc, total=total) for desc, total in tasks_preload ]

		layout['lower'].size = len(tasks)+2
		layout['total'].size = 3


		total_progress = Progress()
		total_task = total_progress.add_task(f"Folder 1/{len(tasks)+1}: ", total=len(tasks))

		#total_progress.start()
		#tasks_progress.start()

		# layout = Layout()
		# layout.split_column(
		# 	Layout(name="upper"),
		# 	Layout(tasks_progress, name="lower"),
		# 	Layout(total_progress, name="total"),
		# )
		# layout['lower'].size = 10
		# layout['total'].size = 3

		def multithreaded_download_and_update(task, server_url, download_path):

			upper_region:LayoutRender = next(iter(layout.map.values()))
			height = upper_region.region.height - 2

			visible = "\n".join(log[-height:])

			#log.append(server_url)
			#screen.refresh()
			#log.append("Downloading "+download_path)
			#layout['upper'].update(Panel(visible, title="Console Log"))
			#screen.refresh()

			if os.path.exists(download_path):
				log.append("Skipped "+download_path+" because file already exists")
			else:
				try: 
					download_file(server_url, download_path)
					log.append("[green]Downloaded "+download_path+"[/]")
				except:
					log.append("[red]Error downloading "+server_url+"[/]")
					print("[red]Error downloading "+server_url+"[/]")
			#log.append("Downloaded "+download_path)
			tasks_progress.advance(task)


			layout['upper'].update(Panel(visible, title="Console Log"))
			layout['lower'].update(Panel(tasks_progress, title="Current Progress"))
			screen.refresh()


		cur_task = 0
		with Live(layout, auto_refresh=False) as screen:
			layout['total'].update(Panel(total_progress,title="Total Progress"))

			for folder_path, files_in_folder in FOLDERS.items():


				with ThreadPoolExecutor(max_workers=5) as pool:
					for file in files_in_folder:
						if not file:
							tasks_progress.advance(tasks[cur_task])
							continue

						server_url = f"{WEBSITE}{folder_path.replace("\\","/")}/{file}" #Fix Windows paths here
						download_path = os.path.join("data",folder_path, file)
						pool.submit(multithreaded_download_and_update, tasks[cur_task], server_url, download_path)
				cur_task +=1
				total_progress.advance(total_task)
				total_progress.update(task_id=total_task, description=f"Folder {cur_task+1}/{len(tasks)+1}: ")

			#with ThreadPoolExecutor(max_workers=5) as pool:
			#	for song in MUSIC:



				# download_file(server_url, download_path)

				# #tasks_progress.console.print(image)
				# #tasks_progress.update(task_id=task, description="Downloading "+image)

				# #log.append(layout["upper"].size)
				# upper_region:LayoutRender = next(iter(layout.map.values()))
				# height = upper_region.region.height - 2


				# visible = "\n".join(log[-height:])
				# log.append("Downloaded "+download_path)

				# tasks_progress.advance(task1)
				# layout['upper'].update(Panel(visible, title="Console Log"))
				# layout['lower'].update(Panel(tasks_progress, title="Current Progress"))
				# screen.refresh()
				#sleep(0.1)

				#print()
				#sys.exit(0)


			# task1 = progress.add_task("[red]Total Progress...", total=1000)
			# task2 = progress.add_task("[green]Processing...", total=1000)
			# task3 = progress.add_task("[cyan]Cooking...", total=1000)

			# while not progress.finished:
			# 	progress.update(task1, advance=0.5)
			# 	progress.update(task2, advance=0.3)
			# 	progress.update(task3, advance=0.9)
			# 	sleep(0.02)


# questions = [
#     inquirer.Checkbox(
#         "interests",
#         message="What are you interested in?",
#         choices=["Computers", "Books", "Science", "Nature", "Fantasy", "History"],
#         default=["Computers", "Books"],
#     ),
# ]