<?php
use ORM\EntityInterface;
use ORM\Entities\Category;
use ORM\Entities\Page;
use ORM\Entities\Post;
use ORM\Entities\User;

use Template\Template as Template;
use Template\Factory as TemplateFactory;

use Parsers\ArgumentParser;
use Parsers\FunctionParser;
use Parsers\EmoticonParser;
use Parsers\MarkdownParser;

#===============================================================================
# Create generic pagination template
#===============================================================================
function createPaginationTemplate($current, $last, string $location): Template {
	$params = http_build_query(array_merge($_GET, ['site' => '__SITE__']));
	$params = str_replace('%', '%%', $params);
	$params = str_replace('__SITE__', '%d', $params);

	$Pagination = TemplateFactory::build('pagination');
	$Pagination->set('THIS', $current);
	$Pagination->set('LAST', $last);
	$Pagination->set('HREF', "{$location}?{$params}");

	return $Pagination;
}

#===============================================================================
# Helper function to reduce duplicate code
#===============================================================================
function generateCategoryItemTemplate(Category $Category, bool $is_root = false): Template {
	$CategoryRepository = Application::getRepository('Category');

	foreach($CategoryRepository->findWithParents($Category->getID()) as $Category) {
		$category_data = generateItemTemplateData($Category);
		$category_list[] = $category_data;
	}

	$Template = TemplateFactory::build('category/item');
	$Template->set('IS_ROOT', $is_root);
	$Template->set('CATEGORY', $category_data ?? []);
	$Template->set('CATEGORIES', $category_list ?? []);
	$Template->set('COUNT', [
		'POST' => $CategoryRepository->getNumberOfPosts($Category),
		'CHILDREN' => $CategoryRepository->getNumberOfChildren($Category)
	]);

	return $Template;
}

#===============================================================================
# Helper function to reduce duplicate code
#===============================================================================
function generatePageItemTemplate(Page $Page, User $User): Template {
	$Template = TemplateFactory::build('page/item');
	$Template->set('PAGE', generateItemTemplateData($Page));
	$Template->set('USER', generateItemTemplateData($User));

	return $Template;
}

#===============================================================================
# Helper function to reduce duplicate code
#===============================================================================
function generatePostItemTemplate(Post $Post, User $User): Template {
	$CategoryRepository = Application::getRepository('Category');

	foreach($CategoryRepository->findWithParents($Post->get('category')) as $Category) {
		$category_data = generateItemTemplateData($Category);
		$categories[] = $category_data;
	}

	$Template = TemplateFactory::build('post/item');
	$Template->set('POST', generateItemTemplateData($Post));
	$Template->set('USER', generateItemTemplateData($User));
	$Template->set('CATEGORY', $category_data ?? []);
	$Template->set('CATEGORIES', $categories ?? []);

	return $Template;
}

#===============================================================================
# Helper function to reduce duplicate code
#===============================================================================
function generateUserItemTemplate(User $User): Template {
	$Template = TemplateFactory::build('user/item');
	$Template->set('USER', generateItemTemplateData($User));

	return $Template;
}

#===============================================================================
# Helper function to reduce duplicate code
#===============================================================================
function generateItemTemplateData(EntityInterface $Entity): array {
	$ArgumentParser = new ArgumentParser;
	$FunctionParser = new FunctionParser;
	$MarkdownParser = new MarkdownParser;

	$FunctionParser->registerFromArray(
		Application::getContentFunctions()
	);

	$attribute = $Entity->getAll(['password']);
	$attribute = array_change_key_case($attribute, CASE_UPPER);

	$text = $Entity->get('body');
	$text = $FunctionParser->transform($text);

	$arguments = $ArgumentParser->parse($Entity->get('argv') ?? '');

	$images = $MarkdownParser->parse($text)['img']['src'] ?? [];
	$images = array_map('htmlentities', $images);

	return [
		'URL' => Application::getEntityURL($Entity),
		'ARGV' => $arguments,
		'ATTR' => $attribute,

		'PREV' => false,
		'NEXT' => false,

		'FILE' => [
			'LIST' => $images,
		],

		'BODY' => [
			'TEXT' => function() use($text) {
				return $text;
			},
			'HTML' => function() use($Entity) {
				return parseEntityContent($Entity);
			}
		]
	];
}

#===============================================================================
# Generate a nested tree from a category data array
#===============================================================================
function generateCategoryDataTree(array $category_data, $root = 0): array {
	foreach($category_data as &$category){
		$tree[intval($category['PARENT'])][] = &$category;
		unset($category['PARENT']);
	}

	foreach($category_data as &$category){
		if (isset($tree[$category['ID']])){
			$category['CHILDS'] = $tree[$category['ID']];
		}
	}

	return $tree[$root] ?? [];
}

#===============================================================================
# Parse entity content
#===============================================================================
function parseEntityContent(EntityInterface $Entity): string {
	$text = $Entity->get('body');

	$FunctionParser = new FunctionParser;
	$FunctionParser->registerFromArray(
		Application::getContentFunctions()
	);

	$text = $FunctionParser->transform($text);

	if(Application::get('WRAP_EMOTICONS')) {
		$EmoticonParser = new EmoticonParser;
		$text = $EmoticonParser->transform($text);
	}

	$MarkdownParser = new MarkdownParser;
	return $MarkdownParser->transform($text);
}

#===============================================================================
# Parser for datetime formatted strings [YYYY-MM-DD HH:II:SS]
#===============================================================================
function parseDatetime($datetime, $format): string {
	list($date, $time) = explode(' ', $datetime);

	list($DATE['Y'], $DATE['M'], $DATE['D']) = explode('-', $date);
	list($TIME['H'], $TIME['M'], $TIME['S']) = explode(':', $time);

	$unixtime = strtotime($datetime);

	return strtr($format, [
		'[Y]' => $DATE['Y'],
		'[M]' => $DATE['M'],
		'[D]' => $DATE['D'],
		'[H]' => $TIME['H'],
		'[I]' => $TIME['M'],
		'[S]' => $TIME['S'],
		'[W]' => strftime('%A', $unixtime),
		'[F]' => strftime('%B', $unixtime),
		'[DATE]' => $date,
		'[TIME]' => $time,
		'[RFC2822]' => date('r', $unixtime)
	]);
}

#===============================================================================
# Get emoticons with their explanations
#===============================================================================
function getUnicodeEmoticons(): array {
	return (new EmoticonParser)->getEmoticons();
}

#===============================================================================
# Wrapper function for htmlspecialchars()
#===============================================================================
function escapeHTML($string): string {
	return htmlspecialchars($string);
}

#===============================================================================
# Remove all double line breaks from string
#===============================================================================
function removeDoubleLineBreaks($string): string {
	return preg_replace('#(\r?\n){2,}#', "\n\n", $string);
}

#===============================================================================
# Remove all multiple whitespace characters
#===============================================================================
function removeWhitespace($string): string {
	return preg_replace('/\s+/S', ' ', trim($string));
}

#===============================================================================
# Return truncated string
#===============================================================================
function truncate($string, $length, $replace = '') {
	if(mb_strlen($string) > $length) {
		$truncated = preg_replace("/^(.{0,{$length}}\\b).*/su", '$1', $string);
		$truncated = trim($truncated);

		# The additional trim call is useful, because if $truncated is empty,
		# then there will be an unnecessary space between those two variables
		# if $replace is preceded by a space (for example: " […]").
		return trim("{$truncated}{$replace}");
	}

	return $string;
}

#===============================================================================
# Return excerpt content
#===============================================================================
function excerpt($string, $length = 500, $replace = ' […]') {
	$string = strip_tags($string);
	$string = removeDoubleLineBreaks($string);
	$string = truncate($string, $length, $replace);
	$string = nl2br($string);

	return $string;
}

#===============================================================================
# Return content for meta description
#===============================================================================
function description($string, $length = 200, $replace = ' […]') {
	$string = strip_tags($string);
	$string = removeWhitespace($string);
	$string = truncate($string, $length, $replace);

	return $string;
}

#===============================================================================
# Generate a valid slug URL part from a string
#===============================================================================
function generateSlug($string, $separator = '-') {
	$string = strtr(mb_strtolower($string), [
		'ä' => 'ae',
		'ö' => 'oe',
		'ü' => 'ue',
		'ß' => 'ss'
	]);

	$string = preg_replace('#[^[:lower:][:digit:]]+#', $separator, $string);

	return trim($string, $separator);
}

#===========================================================================
# Callback for (CATEGORY|PAGE|POST|USER) content function
#===========================================================================
function getEntityMarkdownLink($ns, $id, $text = null, $info = null): string {
	if(!$Entity = Application::getRepository($ns)->find($id)) {
		return sprintf('`{%s: *Reference error*}`', strtoupper($ns));
	}

	$title = $Entity->get('name') ?? $Entity->get('fullname');
	$href = Application::getEntityURL($Entity);
	$text = $text ?: "»{$title}«";

	if($info === null) {
		$info = sprintf('%s »%s«',
			Application::getLanguage()->text(strtolower($ns)),
			$title);
	}

	# Hotfix: Replace double quotes with single quotes because currently we
	# have no sane way to escape double quotes in a string intended to be
	# used as the title for a Markdown formatted link.
	$info = str_replace('"', "'", $info);

	return sprintf('[%s](%s "%s")',	$text, $href, $info);
}

#===========================================================================
# Callback for (CATEGORY|PAGE|POST|USER)_URL content function
#===========================================================================
function getEntityURL($ns, $id): string {
	if(!$Entity = Application::getRepository($ns)->find($id)) {
		return sprintf('`{%s_URL: *Reference error*}`', strtoupper($ns));
	}

	return Application::getEntityURL($Entity);
}

#===============================================================================
# Function for use in templates to get data of a category
#===============================================================================
function CATEGORY(int $id): array {
	$Repository = Application::getRepository('Category');

	if($Category = $Repository->find($id)) {
		return generateItemTemplateData($Category);
	}

	return [];
}

#===============================================================================
# Function for use in templates to get data of a page
#===============================================================================
function PAGE(int $id): array {
	$Repository = Application::getRepository('Page');

	if($Page = $Repository->find($id)) {
		return generateItemTemplateData($Page);
	}

	return [];
}

#===============================================================================
# Function for use in templates to get data of a post
#===============================================================================
function POST(int $id): array {
	$Repository = Application::getRepository('Post');

	if($Post = $Repository->find($id)) {
		return generateItemTemplateData($Post);
	}

	return [];
}

#===============================================================================
# Function for use in templates to get data of a user
#===============================================================================
function USER(int $id): array {
	$Repository = Application::getRepository('User');

	if($User = $Repository->find($id)) {
		return generateItemTemplateData($User);
	}

	return [];
}

#===========================================================================
# Register (BAS|FIL)E_URL content functions
#===========================================================================
Application::addContentFunction('BASE_URL',
function($extend = '') {
	return Application::getURL($extend);
});

Application::addContentFunction('FILE_URL',
function($extend = '') {
	return Application::getFileURL($extend);
});

#===========================================================================
# Register (CATEGORY|PAGE|POST|USER)(_URL)? content functions
#===========================================================================
foreach(['CATEGORY', 'PAGE', 'POST', 'USER'] as $function) {
	$entity = ucfirst(strtolower($function));

	// Get Markdown formatted entity link
	Application::addContentFunction($function,
	function($id, $text = null, $title = null) use($entity) {
		return getEntityMarkdownLink($entity, $id, $text, $title);
	});

	// Get URL to entity
	Application::addContentFunction(sprintf('%s_URL', $function),
	function($id) use($entity) {
		return getEntityURL($entity, $id);
	});
}