#!/usr/bin/python3

from os.path import exists, join as path_join
from os import system, environ, makedirs
from argparse import ArgumentParser
from sys import stdin
import subprocess

# Paths
PATHS = {
	"src_dir": "src",
	"build_dir": "build",
	"template": "template.c",
	"substitute": "substituted.c",
	"output": "render_bytebeat",
	"fwrite_le_header": "fwrite_le.h",
	"fwrite_le": "fwrite_le.c"
}

# Solve paths
PATHS["template"] = path_join(PATHS["src_dir"], PATHS["template"])
PATHS["substitute"] = path_join(PATHS["build_dir"], PATHS["substitute"])
PATHS["output"] = path_join(PATHS["build_dir"], PATHS["output"])
PATHS["fwrite_le_header"] = PATHS["src_dir"] + "/" + PATHS["fwrite_le_header"]
PATHS["fwrite_le"] = path_join(PATHS["src_dir"], PATHS["fwrite_le"])

# Add `.` directory before all paths for compilation
PATHS["template"] = path_join(".", PATHS["template"])
PATHS["substitute"] = path_join(".", PATHS["substitute"])
PATHS["output"] = path_join(".", PATHS["output"])
PATHS["fwrite_le"] = path_join(".", PATHS["fwrite_le"])

# Default parameters
DEFAULT_PARAMETERS = {
	"CC": "gcc",
	"CFLAGS": "-Ofast -Wall -Wextra -Wpedantic -Wno-unused-variable -Wno-unused-but-set-variable -Wno-dangling-else -Wno-parentheses -std=c99",
	"INPUT_FILE": PATHS["substitute"],
	"OUTPUT_FILE": PATHS["output"]
}

def fetch(name: str):
	if from_env := environ.get(name):
		return from_env
	else:
		return DEFAULT_PARAMETERS[name]

def read_file(path: str) -> str:
	return open(path, "r", encoding="utf-8-sig").read()

def rewrite_file(path: str, content: str):
	return open(path, "w", encoding="utf-8").write(content)

def read_from_file_or_stdin(path: str) -> str:
	if path == "-":
		return "\n".join(stdin)
	elif exists(path):
		return read_file(path)
	else:
		print("The specified file doesn't exist")
		raise SystemExit

def substitute_vars(replacements: dict, text: str) -> str:
	for placeholder, replacement in replacements.items():
		text = text.replace(f"`{placeholder}`", str(replacement))
	return text

CC = fetch("CC")
CFLAGS = fetch("CFLAGS")
INPUT_FILE = fetch("INPUT_FILE")
OUTPUT_FILE = fetch("OUTPUT_FILE")

if __name__ == "__main__":
	parser = ArgumentParser(description=\
		"Substitutes supplied C (non-JavaScript!) bytebeat into the template, "
		"then attempts to compile the instance of the template. Accepts "
		"environmental variables `CC`, `CFLAGS`, `INPUT_FILE`, "
		"`OUTPUT_FILE`.")
	parser.add_argument("file", type=str,
		help="bytebeat formula file (use `-` to read from stdin)")
	parser.add_argument("-r", "--sample-rate", default=8000, type=int,
		help="sample rate (Hz)")
	parser.add_argument("-p", "--final-sample-rate", default=None, type=int,
		help="convert the output to a different sample rate (usually one that "
		"is set in the system, to improve sound quality) during generation "
		"(not just reinterpretation)")
	parser.add_argument("-b", "--bit-depth", default=8, type=int,
		help="bit depth")
	parser.add_argument("-s", "--signed", default=False, action="store_true",
		help="is signed?")
	parser.add_argument("-c", "--channels", default=1, type=int,
		help="amount of channels")
	parser.add_argument("-t", "--seconds", default=None, type=int,
		help="length in seconds (samples = sample rate * seconds) : "
			 "default = 30 seconds")
	parser.add_argument("-l", "--samples", default=None, type=int,
		help="length in samples (adds to `-t`; supports negative numbers) : "
			 "default = +0 samples")
	parser.add_argument("-a", "--no-return", default=False, action="store_true",
		help="do not insert return statement before the code")
	parser.add_argument("-q", "--silent", default=False, action="store_true",
		help="do not output anything during generation")
	parser.add_argument("-v", "--verbose", default=False, action="store_true",
		help="show progress during generation")
	args = parser.parse_args()

	bytebeat_contents = read_from_file_or_stdin(args.file).strip()

	if not bytebeat_contents:
		print("No valid contents")
		raise SystemExit

	# - Compilation
	makedirs(PATHS["build_dir"], exist_ok=True)

	if not args.no_return:  # Insert return statement
		bytebeat_contents = f"return {bytebeat_contents}"

	final_sample_rate_code = ""
	if not args.final_sample_rate is None:
		sample_rate_ratio = args.sample_rate / args.final_sample_rate
		final_sample_rate_code = f"w *= {sample_rate_ratio}L;"
		args.sample_rate = args.final_sample_rate

	samples = 0
	while True:
		no_seconds = args.seconds is None or args.seconds == 0
		no_samples = args.samples is None or args.samples == 0
		seconds_exist = not no_seconds
		samples_exist = not no_samples

		if seconds_exist and args.seconds < 0:
			print("CLI: Count of seconds can't be less than zero.")
			raise SystemExit

		if no_seconds and samples_exist:
			samples = args.samples
		elif seconds_exist and samples_exist:
			samples = args.seconds * args.sample_rate + args.samples
		elif seconds_exist and no_samples:
			samples = args.seconds * args.sample_rate
		elif no_seconds and no_samples:
			args.seconds = 30  # default
			continue
		else:
			print("CLI: Incorrect seconds/samples length format.")
			raise SystemExit
		break

	if samples <= 0:
		print("CLI: Count of samples should be greater than zero.")
		raise SystemExit

	rewrite_file(PATHS["substitute"], substitute_vars({
		"bytebeat_contents": bytebeat_contents,
		"sample_rate": args.sample_rate,
		"final_sample_rate_code": final_sample_rate_code,
		"bit_depth": args.bit_depth,
		"is_signed": "1" if args.signed else "0",
		"channels": args.channels,
		"length": samples,
		"silent_mode": "true" if args.silent else "false",
		"verbose_mode": "true" if args.verbose and not args.silent else "false",
		"fwrite_le": "../" + PATHS["fwrite_le_header"]
	}, read_file(PATHS["template"])))

	# Compile by invoking the shell script
	print("Compiling")

	# Let the system execute aliases by calling os.system
	command = " ".join([CC, CFLAGS, INPUT_FILE, PATHS["fwrite_le"], "-o",
		OUTPUT_FILE])
	print(command, flush=True)
	system(command)