import os
import pathlib
from glob import glob
from yaml import full_load as _load

"""
Interface to the instance configuration.

The instance configuration is expected to be found under
$HOME/etc/config.yaml and the configuration for the individual
surveys should be under $HOME/etc/surveys/*.yaml. In both cases,
$HOME is the home directory of the user running this script.
"""

def is_relative_to(it, other):
	"""
	is_relative_to() is not present version Python 3.9, so we
	need this kludge to get Dougal to run on OpenSUSE 15.4
	"""

	if "is_relative_to" in dir(it):
		return it.is_relative_to(other)

	return str(it.absolute()).startswith(str(other.absolute()))


prefix = os.environ.get("DOUGAL_ROOT", os.environ.get("HOME", ".")+"/software")

DOUGAL_ROOT = os.environ.get("DOUGAL_ROOT", os.environ.get("HOME", ".")+"/software")
VARDIR = os.environ.get("VARDIR", DOUGAL_ROOT+"/var")
LOCKFILE = os.environ.get("LOCKFILE", VARDIR+"/runner.lock")

def vars ():
	return {
		"DOUGAL_ROOT": DOUGAL_ROOT,
		"VARDIR": VARDIR,
		"LOCKFILE": LOCKFILE
	}

def read (file = None):
	if file is None:
		file = prefix+"/etc/config.yaml"
	with open(file, "r") as fd:
		cfg = _load(fd)

		if "db" in cfg and "connection_string" in cfg["db"] and os.environ.get("PGDATABASE"):
			# This should cause the PostgreSQL library to use the environment variables
			cfg["db"]["connection_string"] = ""
			print("Using environment variables for database connection")

		return cfg

def files (globspec = None, include_archived = False):
	"""
	Read and parse survey configuration files.

	Arguments:

	globspec (string): a glob spec matching the selection of files to read.
	If not provided, a default value will be used.

	include_archived (bool): whether to include surveys marked as archived or
	not. Defaults to ignoring archived surveys.

	Note that file names starting with `.` or `_` (dot or underscore) will
	never be read (and there is no option to override this). Intended for
	quickly and temporarily “disabling” a survey configuration by renaming
	the relevant file.
	"""

	print("This method is obsolete")
	return

	tuples = []

	if globspec is None:
		globspec = prefix+'/etc/surveys/*.yaml'

	for filepath in glob(globspec):
		filepath = os.path.abspath(filepath)
		if os.path.basename(filepath).startswith("_") or os.path.basename(filepath).startswith("."):
			continue

		survey = read(filepath)

		if not include_archived and "archived" in survey and survey["archived"] is not False:
			continue

		tuples.append((filepath, survey))

	return tuples

def surveys (globspec = None, include_archived = False):
	return [i[1] for i in files(globspec, include_archived)]

def rxflags (flagstr):
	"""
	Convert flags string into a Python flags argument.
	"""
	flags = 0
	cases = {
		"i": re.I
	}
	for flag in flagstr:
		flags |= cases.get(flag, 0)
	return flags

def translate_path (file):
	"""
	Translate a path from a Dougal import directory to an actual
	physical path on disk.

	Any user files accessible by Dougal must be under a path prefixed
	by `(config.yaml).imports.paths`. The value of `imports.paths` may
	be either a string, in which case this represents the prefix under
	which all Dougal data resides, or a dictionary where the keys are
	logical paths and their values the corresponding physical path.
	"""
	cfg = read()
	root = pathlib.Path(DOUGAL_ROOT)
	filepath = pathlib.Path(file).resolve()
	import_paths = cfg["imports"]["paths"]

	if filepath.is_absolute():
		if type(import_paths) == str:
			# Substitute the root for the real physical path
			# NOTE: `root` deals with import_paths not being absolute
			prefix = root.joinpath(pathlib.Path(import_paths)).resolve()
			return str(pathlib.Path(prefix).joinpath(*filepath.parts[2:]))
		else:
			# Look for a match on the second path element
			if filepath.parts[1] in import_paths:
				# NOTE: `root` deals with import_paths[…] not being absolute
				prefix = root.joinpath(import_paths[filepath.parts[1]])
				return str(pathlib.Path(prefix).joinpath(*filepath.parts[2:]))
			else:
				# This path is invalid
				raise TypeError("invalid path or file: {0!r}".format(filepath))
	else:
		# A relative filepath is always resolved relative to the logical root
		root = pathlib.Path("/")
		return translate_path(root.joinpath(filepath))

def untranslate_path (file):
	"""
	Attempt to convert a physical path into a logical one.
	See `translate_path()` above for details.
	"""
	cfg = read()
	dougal_root = pathlib.Path(DOUGAL_ROOT)
	filepath = pathlib.Path(file).resolve()
	import_paths = cfg["imports"]["paths"]
	physical_root = pathlib.Path("/")

	if filepath.is_absolute():
		if type(import_paths) == str:
			if is_relative_to(filepath, import_paths):
				physical_root = pathlib.Path("/")
				physical_prefix = pathlib.Path(import_paths)
				return str(root.joinpath(filepath.relative_to(physical_prefix)))
			else:
				raise TypeError("invalid path or file: {0!r}".format(filepath))
		else:
			for key, value in import_paths.items():
				value = dougal_root.joinpath(value)
				physical_prefix = pathlib.Path(value)
				if is_relative_to(filepath, physical_prefix):
					logical_prefix = physical_root.joinpath(pathlib.Path(key)).resolve()
					return str(logical_prefix.joinpath(filepath.relative_to(physical_prefix)))

			# If we got here with no matches, this is not a valid
			# Dougal data path
			raise TypeError("invalid path or file: {0!r}".format(filepath))
	else:
		# A relative filepath is always resolved relative to DOUGAL_ROOT
		return untranslate_path(root.joinpath(filepath))
