<?php
namespace ModulesAbstract\FluxRest;

/**
 * Upload files to TINA
 * @author Lucian Sirbu
 */
abstract class ControllerUploader extends ControllerAbstract
{
	/**
	 * (non-PHPdoc)
	 * @see \ModulesAbstract\FluxRest\ControllerAbstract::initialize()
	 */
	protected function initialize()
	{
		parent::initialize();
		$this->options['initialize']['WaitFile']['maxTimeout'] = 60*60*1;	// wait up to 1h if no file is available
	}

	/**
	 * Parse the specified filename & content
	 * @param string $filename
	 * @param string $fileContent
	 */
	abstract protected function parseAndSaveFileContents(Response\Main $mainResponse, $filename, $fileContent, \Entity\FluxInstance $fluxInstance, $rawRequest);

	/**
	 * (non-PHPdoc)
	 * @see \ModulesAbstract\FluxRest\ControllerAbstract::actionRestAuthenticate()
	 */
	protected function actionRestAuthenticate($request, Response\Main $mainResponse)
	{
		parent::actionRestAuthenticate($request, $mainResponse);

		$fluxInstance = $this->getAppTokenDetails($request->appToken);

		$op = new Response\Operation\SetConfirmedPath();
		$op->path = $fluxInstance->confirmedPath;
		$mainResponse->appendOperation($op);

		if ($fluxInstance->ignoredFiles) {
			foreach (array_filter($fluxInstance->ignoredFiles) as $pattern) {
				$op = new Response\Operation\SetIgnoredFile();
				$op->pattern = $pattern;
				$mainResponse->appendOperation($op);
			}
		}

		$op = new Response\Operation\GetCommands();
		$mainResponse->appendOperation($op);
	}

	/**
	 * {@inheritDoc}
	 * @see \ModulesAbstract\FluxRest\ControllerAbstract::onAuthenticate_localInitialization()
	 */
	protected function onAuthenticate_localInitialization($request, Response\Main $mainResponse)
	{
		$fluxInstance = $this->getAppTokenDetails($request->appToken);
		if (!$fluxInstance->basePath) {
			// instance not correctly configured
			$op = new Response\Operation\NotifyUserLog();
			$op->text = sprintf("<span class='warning'>%s</span>", "Instance not configured. base path is not set");
			$mainResponse->appendOperation($op);

			$op = new Response\Operation\Wait();
			$op->setTimeoutLimits(0, 60*60)->setTimeout(\Engine\RandomGenerator::randomFloat(15, 2*60));
			$mainResponse->appendOperation($op);

			$op = new Response\Operation\Authenticate();
			$mainResponse->appendOperation($op);

			\Engine\Os::sleep(\Engine\RandomGenerator::randomFloat(2.5, 10));
			return false;
		}

		// send the client settings
		$op = new Response\Operation\SetBasePath();
		$op->path = $fluxInstance->basePath;
		$mainResponse->appendOperation($op);

		return true;
	}

	/**
	 * Determine the filename that should be set on the local computer
	 * @param string $filename
	 * @return string
	 */
	protected function determineLocalConfirmedFilePath(\Entity\FluxInstance $fluxInstance, $filename)
	{
		switch ($fluxInstance->createMonthBasedFolders) {
			case 'Ym':
				$ts = $this->timestamp;

				$this->queueSessionData['WaitFile']['lastIndex']++;
				if ($this->queueSessionData['WaitFile']['lastIndex']>9999) {
					$this->queueSessionData['WaitFile']['lastIndex'] = 1;	// reset the file index
				}

				return sprintf("%s/%s/%s.%04d.fluX.%s.txt",
					$fluxInstance->confirmedPath,
					date("Y-m", (int)$ts),
					date("YmdHis", (int)$ts),
					$this->queueSessionData['WaitFile']['lastIndex'],
					$filename
				);
			break;

			case 'Ym/d':
				$ts = $this->timestamp;

				$this->queueSessionData['WaitFile']['lastIndex']++;
				if ($this->queueSessionData['WaitFile']['lastIndex']>9999) {
					$this->queueSessionData['WaitFile']['lastIndex'] = 1;	// reset the file index
				}

				return sprintf("%s/%s/%s/%s.%04d.fluX.%s.txt",
					$fluxInstance->confirmedPath,
					date("Y-m", (int)$ts),
					date("d", (int)$ts),
					date("YmdHis", (int)$ts),
					$this->queueSessionData['WaitFile']['lastIndex'],
					$filename
				);
			break;

			case 'none':
				return sprintf("%s/%s",
					$fluxInstance->confirmedPath,
					$filename
				);
			break;

			default:
				throw new \Exception(sprintf("[%s] Dont know how to handle createMonthBasedFolders=%s", __METHOD__, $fluxInstance->createMonthBasedFolders));
		}
	}

	/**
	 * Determine the filename that should be set on the local computer, when errors are found
	 * @param string $filename
	 * @return string
	 */
	protected function determineLocalSkippedFilePath(\Entity\FluxInstance $fluxInstance, $filename)
	{
		switch ($fluxInstance->createMonthBasedFolders) {
			case 'Ym':
			case 'Ym/d':
				$ts = $this->timestamp;

				$this->queueSessionData['WaitFile']['lastIndex']++;
				if ($this->queueSessionData['WaitFile']['lastIndex']>9999) {
					$this->queueSessionData['WaitFile']['lastIndex'] = 1;	// reset the file index
				}

				return sprintf("%s/%s skipped/%s.%04d.fluX.%s.txt",
					$fluxInstance->confirmedPath,
					date("Y-m", (int)$ts),
					date("YmdHis", (int)$ts),
					$this->queueSessionData['WaitFile']['lastIndex'],
					$filename
				);
			break;

			case 'none':
				return sprintf("%s/%s",		// TODO: should I also move to the skipped folder on errors?
					$fluxInstance->confirmedPath,
					$filename
				);
			break;

			default:
				throw new \Exception(sprintf("[%s] Dont know how to handle createMonthBasedFolders=%s", __METHOD__, $fluxInstance->createMonthBasedFolders));
		}
	}

	protected function getFilePathRelativeToFilesRoot($path)
	{
		global $CFG;
		$filePath = realpath($path);
		// APP_REAL_PATH is used because this controller is also called from uTests
		$regexp = "/^".preg_quote(realpath(APP_REAL_PATH . $CFG['paths']['files']).'/', '/')."/";
		$filePath = preg_replace($regexp, "", $filePath);
		return $filePath;
	}

	/**
	 * (non-PHPdoc)
	 * @see \ModulesAbstract\FluxRest\ControllerAbstract::actionRestGetCommands()
	 */
	protected function actionRestGetCommands($request, Response\Main $mainResponse)
	{
		try {
			$this->actionRestCommonCommands($request, $mainResponse);
		}
		catch (\Exception\IO\Base $ex) {
			$op = new Response\Operation\NotifyUserLog();
			$op->text = sprintf('Retrying...');
			$mainResponse->appendOperation($op);

			$op = new Response\Operation\GetCommands();
			$mainResponse->appendOperation($op);
			return;
		}

		if ($this->queueSessionData['actionRestAuthenticate']['agent']=='FluX' && $this->queueSessionData['actionRestAuthenticate']['version'] && version_compare($this->queueSessionData['actionRestAuthenticate']['version'], '1.7')>=0) {
			$requestPeriod = 60*60*4;	// 4h default
			if (!isset($this->queueSessionData['SendFilesList']['lastCount'])) {
				$requestPeriod = 0;	// at first, start by requesting the files count
			}

			if ($this->queueSessionData['SendFilesList']['lastCount']>100) {
				$requestPeriod = 60*2;	// if we do have someting in the queue, re-request the count every 2 minutes
			}

			if (($this->timestamp - $this->queueSessionData['SendFilesList']['lastRequest'])>$requestPeriod) {	// re-request 15minutes
				$op = new Response\Operation\SendFilesList();
				$op->sendCount = true;
				$mainResponse->appendOperation($op);
			}
		}

		// the actual client-file handling
		$lastTimeout = $this->queueSessionData['WaitFile']['lastTimeout'];
		$op = new Response\Operation\WaitFile();
		$op->setTimeoutLimits(0.0, $this->options['initialize']['WaitFile']['maxTimeout'])
			->setTimeout(0.01 + $lastTimeout*2);

		$this->queueSessionData['WaitFile']['lastTimeout'] = $op->getTimeout();

		$op->onTimeout->appendOperation(new Response\Operation\GetCommands());

		$fluxInstance = $this->getRequestTokenDetails($request->token);

		$opw = new Response\Operation\Wait();
		$opw->setTimeoutLimits(0, 5)->setTimeout(1);
		$opw->display = "none";
		$mainResponse->appendOperation($opw);
		$op->onFileFound->appendOperation($opw);

		$opOnFF = new Response\Operation\SendFirstFile();
		$opOnFF->sortMethod = $fluxInstance->filesSortMethod;

		$op->onFileFound->appendOperation($opOnFF);

		$mainResponse->appendOperation($op);
	}

	/**
	 * The client sent a file to the server
	 * @param Array $request
	 * @param Response\Main $mainResponse
	 * @throws Exception
	 */
	protected function actionRestSendFile($request, Response\Main $mainResponse)
	{
		$this->validateRequestAuthenticated($request);

		$filename = $request->fileName;
		$fileContent = $request->fileContent;

		$fileContent = $this->uncompressToString($fileContent, $request->fileEncoding, $request->fileCompression);

		$fileHash = $request->fileHash;
		$hash= [];
		preg_match("/^(?P<type>(none|md5))(:(?P<value>.+))?$/", $fileHash, $hash);
		switch ($hash['type']) {
			case 'none':
			break;
			case 'md5':
				// chack against md5-sum
				$md5 = md5($fileContent);

				if ($md5!=strtolower($hash['value'])) {
					throw new Exception("Invalid file hash({$hash['type']}): {$md5} vs '{$hash['value']}' supplied by you");
				}
			break;

			default:
				throw new Exception("Unknown filehash type: '{$hash['type']}'");
		}

		$fluxInstance = $this->getRequestTokenDetails($request->token);

		// the actual file parsing
		$this->parseAndSaveFileContents($mainResponse, $filename, $fileContent, $fluxInstance, $request);

		$this->queueSessionData['WaitFile']['lastTimeout'] = 0; // reset the file timer, send the next one immediatly


		// append the standard list of commands
		$this->actionRestGetCommands($request, $mainResponse);
	}

	/**
	 * The client sent the file list/file count to the server
	 * @param Array $request
	 * @param Response\Main $mainResponse
	 */
	protected function actionRestSendFilesList($request, Response\Main $mainResponse)
	{
		$this->validateRequestAuthenticated($request);

		$this->queueSessionData['SendFilesList']['lastCount'] = $request->count;

		if ($request->count>100) {
			$op = new Response\Operation\NotifyUserLog();
			$op->text = sprintf("<b>Still got %s files in the queue</b>, sent %s files until now", number_format($request->count), number_format($this->queueSessionData['stats']['calls']['SendFile']+1));
			$mainResponse->appendOperation($op);

			if ($this->queueSessionData['SendFile']['lastRequest'] && $this->queueSessionData['stats']['calls']['SendFile']) {
				$this->queueSessionData['SendFilesList']['stats'][]= [
					'timestamp_parseAndSaveFileContents' => $this->queueSessionData['SendFile']['lastRequest'],
					'calls_SendFile' => $this->queueSessionData['stats']['calls']['SendFile'],
				];

				$this->queueSessionData['SendFilesList']['stats'] = array_slice($this->queueSessionData['SendFilesList']['stats'], -5);

				if (count($this->queueSessionData['SendFilesList']['stats'])>2) {
					$calls = 0;
					$duration = 0;
					$lstat = null;
					foreach ($this->queueSessionData['SendFilesList']['stats'] as $stat) {
						if ($lstat) {
							$calls+= $stat['calls_SendFile'] - $lstat['calls_SendFile'];
							$duration+= $stat['timestamp_parseAndSaveFileContents'] - $lstat['timestamp_parseAndSaveFileContents'];
						}
						$lstat = $stat;
					}

					/*
					// debug
					\App\Logger\FluxInstance::getInstance()->info(sprintf("[%s] DEBUG: %s:%s ", __METHOD__, __METHOD__, __LINE__), Array(
						'duration' => $duration,
						'calls' => $calls,
						'stats' => new \Engine\Logger\Context\ArrayAssoc($this->queueSessionData['SendFilesList']['stats']),
					));
					*/

					// estimate ETA
					// avoid misleading results, it cannot take more than 60s to parse one file
					if ($calls) {
						$ETA_perFile = $duration/$calls;
						if ($ETA_perFile<60) {
							$ETA_filesH = ($request->count*$ETA_perFile)/(60*60);
							$op = new Response\Operation\NotifyUserLog();
							if ($ETA_filesH>(24*2)) {
								$op->text = sprintf("On average, it takes about %.1fs to scan & send & parse one file. To parse all remaining files it will take: %.1f days (estimated)", $ETA_perFile, $ETA_filesH/24);
							}
							else {
								$op->text = sprintf("On average, it takes about %.1fs to scan & send & parse one file. To parse all remaining files it will take: %.1fh (estimated)", $ETA_perFile, $ETA_filesH);
							}
							$mainResponse->appendOperation($op);
						}
					}
				}
			}
		}
		else {
			$this->queueSessionData['SendFilesList']['stats'] = [];
		}
	}
}
