<?php
namespace Bitrix\Main\Data;

use \Bitrix\Main\IO;
use \Bitrix\Main\Server;

class CacheEngineFiles
	implements ICacheEngine
{
	private $TTL;

	//cache stats
	private $written = false;
	private $read = false;

	public function isAvailable()
	{
		return true;
	}

	public function clean($baseDir, $initDir = false, $filename = false)
	{
		if(strlen($filename))
		{
			$fn = IO\Path::Combine(\Bitrix\Main\Server::getRoot(), $baseDir, $initDir, $filename);
			$file = new IO\File($fn);

			//This checks for Zend Server CE in order to supress warnings
			if(function_exists('accelerator_reset'))
			{
				try
				{
					$file->MarkWritable();
					if($file->Delete())
					{
						Server::resetAccelerator();
						return true;
					}
				}
				catch (\Exception $ex)
				{

				}
			}
			else
			{
				if($file->IsExists())
				{
					$file->MarkWritable();
					if($file->Delete())
					{
						Server::resetAccelerator();
						return true;
					}
				}
			}
			return false;
		}
		else
		{
			static $bAgentAdded = false;
			$bDelayedDelete = false;

			$source = IO\Path::Combine($baseDir, $initDir);
			$sourceDir = new IO\Directory(IO\Path::ConvertRelativeToAbsolute($source));

			if($sourceDir->IsExists())
			{
				$target = $source.".~";
				for($i = 0; $i < 9; $i++) //try to get new directory name no more than ten times
				{
					$suffix = rand(0, 999999);
					$targetDir = new IO\Directory(IO\Path::ConvertRelativeToAbsolute($target.$suffix));
					if(!$targetDir->IsExists())
					{
						$con = \Bitrix\Main\Application::getDbConnection();
						$res = $con->queryExecute(
							"INSERT INTO b_cache_tag (SITE_ID, CACHE_SALT, RELATIVE_PATH, TAG)
							VALUES ('*', '*', '".$con->getSqlHelper()->forSql($target.$suffix)."', '*')");
						if($res)
						{
							if($sourceDir->rename($targetDir->GetPath()))
								$bDelayedDelete = true;
						}
						break;
					}
				}
			}

			if($bDelayedDelete)
			{
				if(!$bAgentAdded)
				{
					$bAgentAdded = true;

					/* TODO: Агенты */
					$rsAgents = \CAgent::GetList(array("ID"=>"DESC"), array("NAME" => "CacheEngineFiles::delayedDelete(%"));
					if(!$rsAgents->Fetch())
					{
						$res = \CAgent::AddAgent(
							"CacheEngineFiles::delayedDelete();",
							"main", //module
							"Y", //period
							1 //interval
						);
					}
				}
			}
			else
			{
				$sourceDir->Delete();
			}

			Server::resetAccelerator();
		}
	}

	public function read(&$arAllVars, $baseDir, $initDir, $filename, $TTL)
	{
		$fn = IO\Path::Combine(\Bitrix\Main\Server::getRoot(), $baseDir, $initDir, $filename);
		$file = new IO\File($fn);

		if(!$file->IsExists())
			return false;

		$ser_content = "";
		$dateexpire = 0;
		$datecreate = 0;

		$handle = null;
		if (is_array($arAllVars))
		{
			$INCLUDE_FROM_CACHE = 'Y';

			if (!include(IO\Path::ConvertLogicalToPhysical($fn)))
				return false;
		}
		else
		{
			if (!($file instanceof IO\IFileStream))
				return false;

			$handle = $file->open("r");
			if(!$handle)
				return false;

			$datecreate = fread($handle, 2);
			if($datecreate == "BX")
			{
				$datecreate = fread($handle, 12);
				$dateexpire = fread($handle, 12);
			}
			else
			{
				$datecreate .= fread($handle, 10);
			}
		}

		/* We suppress warning here in order not to break
		the compression under Zend Server */
		$this->read = $file->GetFileSize();

		if(intval($datecreate) < (mktime() - $TTL))
			return false;

		if(is_array($arAllVars))
		{
			$arAllVars = unserialize($ser_content);
		}
		else
		{
			$arAllVars = fread($handle, $this->read);
			fclose($handle);
		}

		return true;
	}

	public function write($arAllVars, $baseDir, $initDir, $filename, $TTL)
	{
		$fn = IO\Path::Combine(\Bitrix\Main\Server::getRoot(), $baseDir, $initDir, $filename);
		$file = new IO\File($fn);

		$fnTmp = IO\Path::Combine(\Bitrix\Main\Server::getRoot(), $baseDir, $initDir, md5(mt_rand()).".tmp");
		$fileTmp = new IO\File($fnTmp);

		$dir = $file->GetDirectory();
		if (!$dir->IsExists())
			$dir->Create();

		if(is_array($arAllVars))
		{
			$contents = "<?";
			$contents .= "\nif(\$INCLUDE_FROM_CACHE!='Y')return false;";
			$contents .= "\n\$datecreate = '".str_pad(mktime(), 12, "0", STR_PAD_LEFT)."';";
			$contents .= "\n\$dateexpire = '".str_pad(mktime() + IntVal($TTL), 12, "0", STR_PAD_LEFT)."';";
			$contents .= "\n\$ser_content = '".str_replace("'", "\'", str_replace("\\", "\\\\", serialize($arAllVars)))."';";
			$contents .= "\nreturn true;";
			$contents .= "\n?>";
		}
		else
		{
			$contents = "BX".str_pad(mktime(), 12, "0", STR_PAD_LEFT).str_pad(mktime() + IntVal($this->TTL), 12, "0", STR_PAD_LEFT);
			$contents .= $arAllVars;
		}

		$this->written = $fileTmp->PutContents($contents);

		$len = \Bitrix\Main\Text\String::strlenBytes($contents);

		//This checks for Zend Server CE in order to supress warnings
		if(function_exists('accelerator_reset'))
		{
			try
			{
				$file->Delete();
			}
			catch (\Exception $ex)
			{

			}
		}
		elseif($file->IsExists())
			$file->Delete();

		if($this->written === $len)
			$fileTmp->rename($fn);

		//This checks for Zend Server CE in order to supress warnings
		if(function_exists('accelerator_reset'))
		{
			try
			{
				IO\File::DeleteFile($fnTmp);
			}
			catch (\Exception $ex)
			{

			}
		}
		elseif(IO\File::IsFileExists($fnTmp))
			IO\File::DeleteFile($fnTmp);
	}

	public function isCacheExpired($path)
	{
		$file = new IO\File($path);
		if(!$file->IsExists())
			return true;

		if (!($file instanceof IO\IFileStream))
			return true;

		$dfile = $file->open("r");
		$str_tmp = fread($dfile, 150);
		fclose($dfile);

		if(
			preg_match("/dateexpire\s*=\s*'([\d]+)'/im", $str_tmp, $arTmp)
			|| preg_match("/^BX\\d{12}(\\d{12})/", $str_tmp, $arTmp)
			|| preg_match("/^(\\d{12})/", $str_tmp, $arTmp)
		)
		{
			if(strlen($arTmp[1]) <= 0 || doubleval($arTmp[1]) < mktime())
				return true;
		}

		return false;
	}

	protected function deleteOneDir($etime = 0)
	{
		$bDeleteFromQueue = false;

		$con = \Bitrix\Main\Application::getDbConnection();
		$rs = $con->query("SELECT * from b_cache_tag WHERE TAG='*'", 0, 1);
		if($ar = $rs->fetch())
		{
			$dir_name = \Bitrix\Main\IO\Path::ConvertRelativeToAbsolute($ar["RELATIVE_PATH"]);
			$dir = new \Bitrix\Main\IO\Directory($dir_name);
			if($dir->IsExists())
			{
				$arChildren = $dir->GetChildren();
				$Counter = 0;
				foreach ($arChildren as $child)
				{
					$child->Delete();
					$Counter++;
					if(time() > $etime)
						break;
				}

				if($Counter == 0)
				{
					$dir->Delete();
					$bDeleteFromQueue = true;
				}
			}
			else
			{
				$bDeleteFromQueue = true;
			}

			if($bDeleteFromQueue)
			{
				$con->queryExecute(
					"DELETE FROM b_cache_tag
					WHERE SITE_ID = '".$con->getSqlHelper()->forSql($ar["SITE_ID"])."'
					AND CACHE_SALT = '".$con->getSqlHelper()->forSql($ar["CACHE_SALT"])."'
					AND RELATIVE_PATH = '".$con->getSqlHelper()->forSql($ar["RELATIVE_PATH"])."'");
			}
		}
	}

	function delayedDelete($count = 1, $level = 1)
	{
		$etime = time()+2;
		for($i = 0; $i < $count; $i++)
		{
			self::deleteOneDir($etime);
			if(time() > $etime)
				break;
		}

		$con = \Bitrix\Main\Application::getDbConnection();
		//try to adjust cache cleanup speed to cache cleanups
		$rs = $con->query("SELECT * from b_cache_tag WHERE TAG='**'");
		if($ar = $rs->fetch())
			$last_count = intval($ar["RELATIVE_PATH"]);
		else
			$last_count = 0;
		$bWasStatRecFound = is_array($ar);

		$this_count = $con->queryScalar("SELECT count(1) CNT from b_cache_tag WHERE TAG='*'");

		$delta = $this_count - $last_count;
		if($delta > 0)
			$count = intval($this_count/3600)+1; //Rest of the queue in an hour
		elseif($count < 1)
			$count = 1;

		if($bWasStatRecFound)
		{
			if($last_count != $this_count)
				$con->queryExecute("UPDATE b_cache_tag SET RELATIVE_PATH='".$this_count."' WHERE TAG='**'");
		}
		else
		{
			$con->queryExecute("INSERT INTO b_cache_tag (TAG, RELATIVE_PATH) VALUES ('**', '".$this_count."')");
		}

		if($this_count > 0)
			return "CacheEngineFiles::delayedDelete(".$count.");";
		else
			return "";
	}
}
