<?php

namespace App\Model;

use App\Tool;
use App\Tool\Utils;
use Pimcore\Log;
use Pimcore\Translation\Translator;
use simplehtmldom\HtmlDocument;

trait ElasticTrait
{
	/**
	 * @var string
	 */
	private $shortClassName = null;

	/**
	 * @var array
	 */
	private $elasticSearchmapping = null;

	protected function getShortClassName(): string
	{
		if (!$this->shortClassName) {
			$reflection = new \ReflectionClass($this);
			$this->shortClassName = $reflection->getShortName();
		}

		return $this->shortClassName;
	}

	private function getObjectMapping(): array
	{
		if (!$this->elasticSearchmapping) {
			$this->elasticSearchmapping = Tool\ElasticSearch::getObjectMapping(
				$this->getShortClassName()
			);
		}

		return $this->elasticSearchmapping;
	}

	private function getIndexSuffix(string $lang, bool $forFulltext = false, bool $forGroup = false): string
	{
		if ($forFulltext) {
			if ($this instanceof Document\Page) {
				return Tool\ElasticSearch::DOCUMENT_FULLTEXT_INDEX_KEY.'_'.$lang;
			} else {
				return Tool\ElasticSearch::OBJECT_FULLTEXT_INDEX_KEY.'_'.$lang;
			}
		} else {
			$index = $forGroup ?
				strtolower($this->getShortClassName()).'_group_'.$lang :
				strtolower($this->getShortClassName()).'_'.$lang;
			return $index;
		}
	}

	public function getElasticBaseData(): array
	{
		return [
			'id' => (int) parent::getId(),
			'parentId' => (int) parent::getParentId(),
			'published' => (bool) parent::getPublished(),
			'path' => parent::getPath(),
			'key' => parent::getKey(),
			'type' => $this->getShortClassName(),
		];
	}

	public function elasticSearchUpdate(): void
	{
		if (parent::getProperty('elastic_search_exclude')) {
			return;
		}

		$elasticClient = Tool\ElasticSearch::getClient();
		$mapping = $this->getObjectMapping();

		if (parent::getId()) {
			foreach ($mapping['meta']['languages'] as $lang) {
				$data = $this->elasticSearchLoad($lang);
				$index = Tool\ElasticSearch::getIndex($this->getIndexSuffix($lang));
				$groupIndex = Tool\ElasticSearch::getIndex($this->getIndexSuffix($lang, false, true));

				if ($index === null) {
					continue;
				}
				$objectData = $data;
				unset($objectData['elasticGroup']);
				$elasticClient->index([
					'index' => $index,
					'id' => $data['id'],
					'body' => $objectData,
				]);

				if (!empty($data['elasticGroup'])) {
					$this->elasticSearchDelete($lang, current($data['elasticGroup'])['id']);

					foreach ($data['elasticGroup'] as $key => $groupData) {
						$elasticClient->index([
							'index' => $groupIndex,
							'id' => $groupData['id'].'-'.$key,
							'body' => $groupData,
						]);
					}
				}
				else {
					$this->elasticSearchDelete($lang, $data['id']);
				}

				unset($data);

				if (!Tool\ElasticSearch::isUpdateRequest()) {
					$elasticClient->indices()->refresh(['index' => $index]);
				}
			}
		}
	}

	public function elasticSearchDelete(string $language = null, string $groupId = null): void
	{
		$elasticClient = Tool\ElasticSearch::getClient();
		$mapping = $this->getObjectMapping();

		foreach ($mapping['meta']['languages'] as $lang) {
			if ($language && $language != $lang) {
				continue;
			}
			$index = Tool\ElasticSearch::getIndex($this->getIndexSuffix($lang));
			$groupIndex = Tool\ElasticSearch::getIndex($this->getIndexSuffix($lang, false, true));
			if ($index === null) {
				continue;
			}
			try {
				if ($groupId) {
					$elasticClient->deleteByQuery([
						'index' => $groupIndex,
						'body' => ['query' => ['term' => ['id' => $groupId]]],
					]);
				} else {
					$elasticClient->delete([
						'index' => $index,
						'id' => parent::getId(),
					]);
					$elasticClient->deleteByQuery([
						'index' => $groupIndex,
						'body' => ['query' => ['term' => ['id' => parent::getId()]]],
					]);
				}
			} catch (\Exception $e) {
				Log\Simple::log('elasticsearch',
					'Could not delete entry for object with ID: '
					.parent::getId().' and index: '.$index
					.'. Exception: '.$e->getMessage()."\n".$e->getTraceAsString()
				);
			}
		}
		if (!Tool\ElasticSearch::isUpdateRequest()) {
			$elasticClient->indices()->refresh(['index' => $index]);
		}
	}

	public function elasticSearchUpdateFulltext(): void
	{
		if (parent::getProperty('elastic_search_exclude')) {
			return;
		}

		$elasticClient = Tool\ElasticSearch::getClient();

		$languages = null;
		if ($this instanceof Document\Page) {
			$language = $this->getProperty('language');
			if (!$language) {
				$language = Utils::getDefaultLanguage();
			}
			$languages[] = $language;
		} else {
			$mapping = $this->getObjectMapping();
			$languages = $mapping['meta']['languages'];
		}

		foreach ($languages as $lang) {
			$index = Tool\ElasticSearch::getIndex($this->getIndexSuffix($lang, true));
			if ($index === null) {
				continue;
			}
			$data = $this->elasticSearchLoadFulltext($lang);
			$elasticClient->index([
				'index' => $index,
				'id' => $data['id'],
				'body' => $data,
			]);
			unset($data);

			if (!Tool\ElasticSearch::isUpdateRequest()) {
				$elasticClient->indices()->refresh(['index' => $index]);
			}
		}
	}

	public function elasticSearchDeleteFulltext(): void
	{
		$elasticClient = Tool\ElasticSearch::getClient();

		$languages = null;
		if ($this instanceof Document\Page) {
			$language = $this->getProperty('language');
			if (!$language) {
				$language = Utils::getDefaultLanguage();
			}
			$languages[] = $language;
		} else {
			$mapping = $this->getObjectMapping();
			$languages = $mapping['meta']['languages'];
		}

		foreach ($languages as $lang) {
			$index = Tool\ElasticSearch::getIndex($this->getIndexSuffix($lang, true));
			if ($index === null) {
				continue;
			}
			try {
				$elasticClient->delete([
					'index' => $index,
					'id' => parent::getId(),
				]);
			} catch (\Exception $e) {
				Log\Simple::log('elasticsearch',
					'Could not delete entry for document or object with ID: '
					.parent::getId().' and index: '.$index
					.'. Exception: '.$e->getMessage()."\n".$e->getTraceAsString()
				);
			}
		}
		if (!Tool\ElasticSearch::isUpdateRequest()) {
			$elasticClient->indices()->refresh(['index' => $index]);
		}
	}

	/**
	 * @param mixed $payload
	 */
	public function createElasticSuggestion(string $string, $payload = null): array
	{
		$tmp = Utils::webalize($string);

		$suggest = ['input' => explode('-', $tmp), 'output' => $string, 'weight' => 1];
		if ($payload) {
			$suggest['payload'] = $payload;
		}

		return $suggest;
	}

	public function normalizeElasticText(string $text): string
	{
		$normalizedText = iconv('UTF-8', 'UTF-8//IGNORE', mb_convert_encoding($text, 'UTF-8', 'UTF-8'));
		$dom = new HtmlDocument(
			'<div>'.html_entity_decode($normalizedText).'</div>'
		);
		if ($dom) {
			$tagList = [
				'audio', 'area', 'base', 'canvas', 'embed', 'head', 'link', 'map', 'meta',
				'noscript', 'object', 'param', 'script', 'source', 'style', 'svg', 'track', 'video',
			];
			foreach ($tagList as $tag) {
				foreach ($dom->find($tag) as $node) {
					$node->outertext = '';
				}
			}

			$normalizedText = $dom->save();
		}

		$normalizedText = preg_replace('/>/', '> ', $normalizedText);
		return preg_replace('/[ \t\r\n]+/', ' ', strip_tags($normalizedText));
	}

	public function renderView(string $template, string $language, array $params): string
	{
		/** @var Twig\Environment $templatingEngine */
		$templatingEngine = \Pimcore::getContainer()->get('pimcore.templating.engine.twig');

		/** @var Translator $translator */
		$translator = \Pimcore::getContainer()->get('translator');
		$translator->setLocale($language);

		// disable layout
		$params['elastic_document_indexing'] = true;
		$params['editmode'] = false;
		$params['language'] = $language;

		$content = $this->normalizeElasticText($templatingEngine->render($template, $params));

		return $content;
	}
}
