#
# configuration.py
# Apply options from config.ini to the existing Configuration headers
#
import re, shutil, configparser
from pathlib import Path

verbose = 0
def blab(str,level=1):
	if verbose >= level: print(f"[config] {str}")

def config_path(cpath):
	return Path("Marlin", cpath)

# Apply a single name = on/off ; name = value ; etc.
# TODO: Limit to the given (optional) configuration
def apply_opt(name, val, conf=None):
	if name == "lcd": name, val = val, "on"

	# Create a regex to match the option and capture parts of the line
	regex = re.compile(rf'^(\s*)(//\s*)?(#define\s+)({name}\b)(\s*)(.*?)(\s*)(//.*)?$', re.IGNORECASE)

	# Find and enable and/or update all matches
	for file in ("Configuration.h", "Configuration_adv.h"):
		fullpath = config_path(file)
		lines = fullpath.read_text().split('\n')
		found = False
		for i in range(len(lines)):
			line = lines[i]
			match = regex.match(line)
			if match and match[4].upper() == name.upper():
				found = True
				# For boolean options un/comment the define
				if val in ("on", "", None):
					newline = re.sub(r'^(\s*)//+\s*(#define)(\s{1,3})?(\s*)', r'\1\2 \4', line)
				elif val == "off":
					newline = re.sub(r'^(\s*)(#define)(\s{1,3})?(\s*)', r'\1//\2 \4', line)
				else:
					# For options with values, enable and set the value
					newline = match[1] + match[3] + match[4] + match[5] + val
					if match[8]:
						sp = match[7] if match[7] else ' '
						newline += sp + match[8]
				lines[i] = newline
				blab(f"Set {name} to {val}")

		# If the option was found, write the modified lines
		if found:
			fullpath.write_text('\n'.join(lines))
			break

	# If the option didn't appear in either config file, add it
	if not found:
		# OFF options are added as disabled items so they appear
		# in config dumps. Useful for custom settings.
		prefix = ""
		if val == "off":
			prefix, val = "//", ""	# Item doesn't appear in config dump
			#val = "false"			# Item appears in config dump

		# Uppercase the option unless already mixed/uppercase
		added = name.upper() if name.islower() else name

		# Add the provided value after the name
		if val != "on" and val != "" and val is not None:
			added += " " + val

		# Prepend the new option after the first set of #define lines
		fullpath = config_path("Configuration.h")
		with fullpath.open() as f:
			lines = f.readlines()
			linenum = 0
			gotdef = False
			for line in lines:
				isdef = line.startswith("#define")
				if not gotdef:
					gotdef = isdef
				elif not isdef:
					break
				linenum += 1
			lines.insert(linenum, f"{prefix}#define {added} // Added by config.ini\n")
			fullpath.write_text('\n'.join(lines))

# Fetch configuration files from GitHub given the path.
# Return True if any files were fetched.
def fetch_example(url):
	if url.endswith("/"): url = url[:-1]
	if url.startswith('http'):
		url = url.replace("%", "%25").replace(" ", "%20")
	else:
		brch = "bugfix-2.1.x"
		if '@' in path: path, brch = map(str.strip, path.split('@'))
		url = f"https://raw.githubusercontent.com/MarlinFirmware/Configurations/{brch}/config/{url}"

	# Find a suitable fetch command
	if shutil.which("curl") is not None:
		fetch = "curl -L -s -S -f -o"
	elif shutil.which("wget") is not None:
		fetch = "wget -q -O"
	else:
		blab("Couldn't find curl or wget", -1)
		return False

	import os

	# Reset configurations to default
	os.system("git reset --hard HEAD")

	# Try to fetch the remote files
	gotfile = False
	for fn in ("Configuration.h", "Configuration_adv.h", "_Bootscreen.h", "_Statusscreen.h"):
		if os.system(f"{fetch} wgot {url}/{fn} >/dev/null 2>&1") == 0:
			shutil.move('wgot', config_path(fn))
			gotfile = True

	if Path('wgot').exists(): shutil.rmtree('wgot')

	return gotfile

def section_items(cp, sectkey):
	return cp.items(sectkey) if sectkey in cp.sections() else []

# Apply all items from a config section
def apply_ini_by_name(cp, sect):
	iniok = True
	if sect in ('config:base', 'config:root'):
		iniok = False
		items = section_items(cp, 'config:base') + section_items(cp, 'config:root')
	else:
		items = cp.items(sect)

	for item in items:
		if iniok or not item[0].startswith('ini_'):
			apply_opt(item[0], item[1])

# Apply all config sections from a parsed file
def apply_all_sections(cp):
	for sect in cp.sections():
		if sect.startswith('config:'):
			apply_ini_by_name(cp, sect)

# Apply certain config sections from a parsed file
def apply_sections(cp, ckey='all'):
	blab(f"Apply section key: {ckey}")
	if ckey == 'all':
		apply_all_sections(cp)
	else:
		# Apply the base/root config.ini settings after external files are done
		if ckey in ('base', 'root'):
			apply_ini_by_name(cp, 'config:base')

		# Apply historically 'Configuration.h' settings everywhere
		if ckey == 'basic':
			apply_ini_by_name(cp, 'config:basic')

		# Apply historically Configuration_adv.h settings everywhere
		# (Some of which rely on defines in 'Conditionals_LCD.h')
		elif ckey in ('adv', 'advanced'):
			apply_ini_by_name(cp, 'config:advanced')

		# Apply a specific config:<name> section directly
		elif ckey.startswith('config:'):
			apply_ini_by_name(cp, ckey)

# Apply settings from a top level config.ini
def apply_config_ini(cp):
	blab("=" * 20 + " Gather 'config.ini' entries...")

	# Pre-scan for ini_use_config to get config_keys
	base_items = section_items(cp, 'config:base') + section_items(cp, 'config:root')
	config_keys = ['base']
	for ikey, ival in base_items:
		if ikey == 'ini_use_config':
			config_keys = map(str.strip, ival.split(','))

	# For each ini_use_config item perform an action
	for ckey in config_keys:
		addbase = False

		# For a key ending in .ini load and parse another .ini file
		if ckey.endswith('.ini'):
			sect = 'base'
			if '@' in ckey: sect, ckey = ckey.split('@')
			other_ini = configparser.ConfigParser()
			other_ini.read(config_path(ckey))
			apply_sections(other_ini, sect)

		# (Allow 'example/' as a shortcut for 'examples/')
		elif ckey.startswith('example/'):
			ckey = 'examples' + ckey[7:]

		# For 'examples/<path>' fetch an example set from GitHub.
		# For https?:// do a direct fetch of the URL.
		elif ckey.startswith('examples/') or ckey.startswith('http'):
			fetch_example(ckey)
			ckey = 'base'

		# Apply keyed sections after external files are done
		apply_sections(cp, 'config:' + ckey)

if __name__ == "__main__":
	#
	# From command line use the given file name
	#
	import sys
	args = sys.argv[1:]
	if len(args) > 0:
		if args[0].endswith('.ini'):
			ini_file = args[0]
		else:
			print("Usage: %s <.ini file>" % sys.argv[0])
	else:
		ini_file = config_path('config.ini')

	if ini_file:
		user_ini = configparser.ConfigParser()
		user_ini.read(ini_file)
		apply_config_ini(user_ini)

else:
	#
	# From within PlatformIO use the loaded INI file
	#
	import pioutil
	if pioutil.is_pio_build():

		Import("env")

		try:
			verbose = int(env.GetProjectOption('custom_verbose'))
		except:
			pass

		from platformio.project.config import ProjectConfig
		apply_config_ini(ProjectConfig())