<?php

namespace SDK\Build\PGO;

use SDK\Exception;

class Config
{
	const MODE_INIT = 0;
	const MODE_RUN = 1;
	const MODE_REINIT = 2; /* currently unused */
	const MODE_CHECK_INIT = 3;

	/** @var int */
	protected $mode;

	/** @var int */
	protected $last_port = 8081;

	/** @var array<string,mixed> */
	protected $sections = array();

	/** @var string */
	protected $scenario = "default";

	/** @var array<mixed> */
	protected $tpl_vars = array();

	/** @var array<mixed> */
	protected $srv = array();

	public function __construct(int $mode = self::MODE_RUN)
	{
		if (self::MODE_CHECK_INIT == $mode) {
			// XXX The check is simple right now, so this is sufficient. 
			return;
		}

		if (!$this->isInitialized()) {
			$this->initWorkDir();
		}

		if (self::MODE_REINIT == $mode) {
			$fn = $this->getWorkSectionsFilename();
			if (file_exists($fn)) {
				unlink($fn);
			}
			$mode = self::MODE_INIT;
		}

		$this->mode = $mode;


		if (self::MODE_INIT == $mode) {
			foreach (array("nginx", "mariadb", "postgresql", "php") as $i) {
				$this->importSectionFromDir($i, $this->getTplDir() . DIRECTORY_SEPARATOR . $i);
			}
		} else if (self::MODE_RUN == $mode) {
			$fn = $this->getWorkSectionsFilename();
			if (!file_exists($fn)) {
				throw new Exception("Required config doesn't exist under '$fn'.");
			}
			$s = file_get_contents($fn);
			$this->sections = json_decode($s, true);
			foreach($this->sections as $k => $v) {
				$this->importTplVars($k, $v);
			}

		} else {
			throw new Exception("Unknown config mode '$mode'.");
		}
	}

	protected function initWorkDir() : void
	{
		if (!mkdir($this->getWorkDir())) {
			throw new Exception("Failed to create " . $this->getWorkDir());
		}
	}

	/** @return bool */
	public function isInitialized()
	{
		/* XXX Could be some better check. */
		return is_dir($this->getWorkDir());
	}


	public function getToolsDir() : string
	{
		$base = $this->getWorkDir();

		return $base . DIRECTORY_SEPARATOR . "tools";
	}

	public function getWorkDir() : string
	{
		$base = getenv("PHP_SDK_ROOT_PATH");

		return $base . DIRECTORY_SEPARATOR . "pgo" . DIRECTORY_SEPARATOR . "work";
	}

	public function getPkgCacheDir() : string
	{
		$base = $this->getWorkDir();

		return $base . DIRECTORY_SEPARATOR . "package_cache";
	}

	public function getJobDir(string $name = NULL) : string
	{
		$ret = $this->getWorkDir() . DIRECTORY_SEPARATOR . "job";

		if ($name) {
			$ret .= DIRECTORY_SEPARATOR . $name;
		}

		return $ret;
	}

	public function getSrvDir(string $name = NULL) : string
	{
		$ret = $this->getWorkDir() . DIRECTORY_SEPARATOR . "server";

		if ($name) {
			$ret .= DIRECTORY_SEPARATOR . $name;
		}

		return $ret;
	}

	public function getHtdocs(string $name = NULL) : string
	{
		$ret = $this->getWorkDir() . DIRECTORY_SEPARATOR . "htdocs";

		if ($name) {
			$ret .= DIRECTORY_SEPARATOR . $name;
		}

		return $ret;
	}

	public function getTplDir(string $name = NULL) : string
	{
		$ret = getenv("PHP_SDK_ROOT_PATH") . DIRECTORY_SEPARATOR . "pgo" . DIRECTORY_SEPARATOR . "tpl";

		if ($name) {
			$ret .= DIRECTORY_SEPARATOR . $name;
		}

		return $ret;
	}

	public function getCaseWorkDir(string $name = NULL) : string
	{
		$ret = $this->getWorkDir() . DIRECTORY_SEPARATOR . "htdocs";

		if ($name) {
			$ret .= DIRECTORY_SEPARATOR . $name;
		}

		return $ret;
	}

	public function getCasesTplDir(string $name = NULL) : string
	{
		$ret = getenv("PHP_SDK_ROOT_PATH") . DIRECTORY_SEPARATOR . "pgo" . DIRECTORY_SEPARATOR . "cases";

		if ($name) {
			$ret .= DIRECTORY_SEPARATOR . $name;
		}

		return $ret;
	}

	/** @param string ...$args */
	public function sectionItemExists(...$args) : bool
	{
		$i = 0;
		$k = strtolower($args[$i]);
		$it = $this->sections;

		while (array_key_exists($k, $it)) {
			$it = $it[$k];

			if (++$i >= count($args)) break;

			$k = strtolower($args[$i]);
		}

		return $i == count($args);
	}

	/**
	 * @param string  ...$args
	 * @return mixed
	 */
	public function getSectionItem(...$args)
	{
		$i = 0;
		$k = strtolower($args[$i]);
		$it = $this->sections;

		while (array_key_exists($k, $it)) {
			$it = $it[$k];

			if (++$i >= count($args)) break;

			$k = strtolower($args[$i]);
		}

		if ($i != count($args)) {
			return NULL;
		}

		return $it;
	}

	/** @param string|int ...$args */
	public function setSectionItem(...$args) : void
	{
		$val = array_pop($args);

		$i = 0;
		$k = strtolower($args[$i]);
		$it = &$this->sections;

		while (true) {
			$it = &$it[$k];
			if (++$i >= count($args)) break;
			$k = strtolower($args[$i]);
		}

		$it = $val;

		$this->syncTplVars();
		$this->dump();
	}

	public function importSectionFromDir(string $name, string $dir) : void
	{
		$fn = $dir . DIRECTORY_SEPARATOR . "phpsdk_pgo.json";
		if (!file_exists($fn)) {
			throw new Exception("Couldn't import section, file '$fn' doesn't exist.");
		}

		$s = file_get_contents($fn);
		$this->setSectionItem($name, json_decode($s, true));
	}

	protected function syncTplVars() : void
	{
		$this->tpl_vars = array();
		foreach ($this->sections as $k => $v) {
			$this->importTplVars($k, $v);	
		}
	}

	/** @param string ...$args */
	public function buildTplVarName(...$args) : string
	{
		$tpl_k = array("PHP_SDK_PGO");
		
	 	foreach ($args as $a) {
			$tpl_k[] = strtoupper($a);
		}

		return implode("_", $tpl_k);
	}

	/** @param array<string,mixed> $section */
	protected function importTplVars(string $section_name, array $section) : void
	{
		foreach($section as $k0 => $v0) {

			if (is_array($v0)) {
				if (substr($k0, -4) == ":env") {
					/* Don't put env vars as tpl vars for now. */
					continue;	
				}
				$this->importTplVars($section_name . "_" . $k0, $v0);
			} else {
				$tpl_k = $this->buildTplVarName($section_name, $k0);
				$this->tpl_vars[$tpl_k] = $v0;
			}
		}	
	}

	/** @param array<mixed> $additional_vars */
	public function processTpl(string $s, array $additional_vars = array()) : string
	{
		$vars = array_merge($this->tpl_vars, $additional_vars);

		$s = str_replace(array_keys($vars), array_values($vars), $s);

		return $s;
	}

	/** @param array<mixed> $additional_vars */
	public function processTplFile(string $tpl_fn, string $dst_fn, array $additional_vars = array()) : void
	{
		if (!file_exists($tpl_fn)) {
			throw new Exception("Template file '$tpl_fn' doesn't exist.");
		}

		$s = file_get_contents($tpl_fn);	
		if (false === $s) {
			throw new Exception("Couldn't read '$tpl_fn'.");
		}

		$s = $this->processTpl($s, $additional_vars);

		if (false === file_put_contents($dst_fn, $s)) {
			throw new Exception("Failed to write '$dst_fn'.");
		}
	}

	/** @return string */
	public function getWorkSectionsFilename()
	{
		return $this->getWorkDir() . DIRECTORY_SEPARATOR . "phpsdk_pgo.json";
	}

	public function dump(string $fn = NULL) : void
	{
		$fn = $fn ? $fn : $this->getWorkSectionsFilename();

		$s = json_encode($this->sections, JSON_PRETTY_PRINT);

		$ret = file_put_contents($fn, $s);
		if (false === $ret || strlen($s) !== $ret) {
			throw new Exception("Errors with writing to '$fn'.");
		}
	}

	public function setScenario(string $scenario) : void
	{
		if (!in_array($scenario, array("default", "cache"), true)) {
			throw new Exception("Unknown scenario '$scenario'.");
		}
		$this->scenario = $scenario;
	}

	public function getScenario() : string
	{
		return $this->scenario;
	}

	public function getNextPort() : int
	{
		return ++$this->last_port;
	}

	public function setLastPort(int $port) : void
	{
		$this->last_port = $port;
	}

	public function getSdkPhpCmd() : string
	{
		return getenv("PHP_SDK_PHP_CMD");
	}

	/**
	 * @param Interfaces\Server $item
	 * @return void
	 */
	public function addSrv($item) : void
	{
		$name = strtolower($item->getName());

		if (isset($this->srv[$name])) {
			throw new Exception("Server '$name' already exists.");
		}

		/* XXX Additional checks could not harm. */
		$this->srv[$name] = $item;
	}

	/** @return array<string,Interfaces\Server>|Interfaces\Server|null */
	public function getSrv(?string $name = NULL)
	{
		$ret = NULL;

		$name = strtolower($name);

		if (!$name) {
			return NULL;
		} else if ("all" == $name) {
			return $this->srv;
		} else if (isset($this->srv[$name])) {
			return $this->srv[$name];
		}

		return $ret;
	}
}
