<?php
/*
 * Copyright (c) 2013, webvariants GbR, http://www.webvariants.de
 *
 * This file is released under the terms of the MIT license. You can find the
 * complete text in the attached LICENSE file or online at:
 *
 * http://www.opensource.org/licenses/mit-license.php
 */

/**
 * MetaListHelper bietet die Möglichkeit, über bestimmte Parameter Mengen von
 * Artikeln zurückzugeben. Dabei kann nach Metainfos gefiltert und/oder sortiert
 * werden.
 *
 * @author  Christian Zozmann
 * @ingroup external
 */
class WV2_MetaListHelper {
	protected $metaFilterActive;
	protected $metaFilter;
	protected $metaSortActive;
	protected $metaSortToken;
	protected $metaSortValues;
	protected $articleTypes;
	protected $categoryID;
	protected $walkCategoryRecursive;
	protected $filterByCategory;
	protected $sortby;
	protected $direction;
	protected $offset;
	protected $onlineOnly;
	protected $filterSelf;
	protected $filterByUser;
	protected $userName;

	/**
	 * Konstruktor
	 *
	 * Erzeugt einen neuen Helper für Listen, die auf Meta-Ergebnissen basieren.
	 *
	 * @param array $params  enthält Parameter zum Initialisieren des Helpers
	 */
	public function __construct($params) {
		$level = error_reporting(0);

		$this->metaFilterActive = (bool) $params['metaFilterActive'];

		if ($this->metaFilterActive) {
			$this->metaFilter = $params['metaFilter'];
		}

		$this->metaSortActive = (bool) $params['metaSortActive'];

		if ($this->metaSortActive) {
			$this->metaSortToken     = $params['metaSortToken'];
			$this->metaSortDirection = strtolower($params['metaSortDirection']) == 'asc' ? 'asc' : 'desc';
		}

		$this->articleTypes          = empty($params['articleTypes']) ? array() : $params['articleTypes'];
		$this->categoryID            = empty($params['categoryId']) ? 0 : (int) $params['categoryId'];
		$this->walkCategoryRecursive = (bool) $params['walkCategoryRecursive'];
		$this->sortby                = empty($params['sortby']) ? null : $params['sortby'];
		$this->direction             = empty($params['direction']) ? 'ASC' : $params['direction'];
		$this->filterByCategory      = (bool) $params['filterByCategory'];
		$this->offset                = empty($params['offset']) ? 0 : (int) $params['offset'];
		$this->onlineOnly            = isset($params['onlineOnly']) ? (bool) $params['onlineOnly'] : true;
		$this->filterSelf            = !empty($params['filterSelf']);
		$this->filterByUser          = (bool) $params['filterByUser'];
		$this->userName              = empty($params['userName']) ? null : $params['userName'];

		error_reporting($level);
	}

	/**
	 * Gibt alle Kategorien zurück aus denen Artikel gewählt werden können
	 *
	 * @param  int  $categoryID  die Artikelkategorie
	 * @param  bool $onlineOnly  wenn true, werden nur online Artikel berücksichtigt
	 * @return array             Liste aller enthaltenen Kategrien (sly_Model_Category-Objekte)
	 */
	protected function getCategoriesRecursive($categoryID = null, $onlineOnly = true) {
		if ($categoryID == null) $categoryID = $this->categoryID;

		$return = array($categoryID);

		if ($this->walkCategoryRecursive) {
			$category = sly_Util_Category::findById($categoryID);

			foreach ($category->getChildren($onlineOnly) as $child) {
				$return = array_merge($return, $this->getCategoriesRecursive($child->getId(), $onlineOnly));
			}
		}

		return $return;
	}

	/**
	 * Gibt die erlaubten Artikeltypen zurück
	 *
	 * @return array  Liste aller Artikeltypen (Strings mit den internen Namen)
	 */
	protected function getArticleTypes() {
		if (empty($this->articleTypes)) {
			$types = sly_Service_Factory::getArticleTypeService()->getArticleTypes();
			$this->articleTypes = array_keys($types);
		}

		return $this->articleTypes;
	}

	/**
	 * Filtert eine Artikelmenge nach Values von Metadaten
	 *
	 * @param  array $articles  die Artikel (sly_Model_Article-Objekte) zum Sortieren
	 * @return array            das gefilterte Artikel-Array
	 */
	private static function filterArticlesByMetaData($articles, $metainfo, $values) {
		$datatype = _WV2_Info::getDatatypeWithParams($metainfo);
		$params   = $datatype['params'];
		$return   = array();

		foreach ($articles as $article) {
			$articleValue = WV2_MetaProvider::articleData($metainfo, '', null, $article->getID(), true);

			if (!empty($articleValue)) {
				$value     = reset($articleValue); // bei raw=true wird das nicht bereits von articleData erledigt
				$contained = WV_Datatype::call($datatype['datatype'], 'isValueContained', array($values, $value, $params, WV_Datatype::FILTER_NOCASE));

				if ($contained) $return[] = $article;
			}
		}

		return $return;
	}

	/**
	 * Filtert eine Artikelmenge nach Kategoriezugehörigkeit
	 *
	 * @param  array $articles  Liste von Artikeln (sly_Model_Article-Objekte)
	 * @return array            das gefilterte Artikel Array
	 */
	private function filterArticlesByCategory($articles) {
		$categories = $this->getCategoriesRecursive();
		$return     = array();

		foreach ($articles as $article) {
			if (in_array($article->getCategoryId(), $categories)) $return[] = $article;
		}

		return $return;
	}

	/**
	 * compare function to compare Articles by their MetaData
	 *
	 * @param  sly_Model_Article $a
	 * @param  sly_Model_Article $b
	 * @return int
	 */
	private function compareByMetaData($a, $b) {
		$aMetaValue = $a->getMeta($this->metaSortToken);
		$bMetaValue = $b->getMeta($this->metaSortToken);

		// Dank raw=true dürfen wir das selber übernehmen.
		if (is_array($aMetaValue)) $aMetaValue = reset($aMetaValue);
		if (is_array($bMetaValue)) $bMetaValue = reset($bMetaValue);

		if (is_numeric($aMetaValue) && is_numeric($bMetaValue)) {
			$res = $aMetaValue == $bMetaValue;
			if (!$res) {
				$res = $aMetaValue > $bMetaValue ? 1 : -1;
			}
		}
		else {
			$res = strcmp($aMetaValue, $bMetaValue);
		}

		return $this->metaSortDirection == 'asc' ? $res : $res*-1;
	}

	/**
	 * Sortiert eine Artikelmenge nach Metadaten
	 *
	 * @param  array $articles  die Artikel (sly_Model_Article-Objekte) zum Sortieren
	 * @return array            sly_Model_Article
	 */
	private function sortArticlesByMetaData($articles) {
		usort($articles, array($this, 'compareByMetaData'));
		return $articles;
	}

	/**
	 * Gibt eine Menge von Artikeln zurück
	 *
	 * Die Parameter, nach denen die Artikel gefunden wurden, sind
	 * Klassenvariablen.
	 *
	 * @param  int $maxArticles  die maximale Anzahl von Artikeln, die zurückgegeben werden
	 * @param  int $startIdx     das Offset für die Artikelsuche (für Pager etc.)
	 * @return array             ($articles, $articlesCount): $articles sind gefundene Artikel (sly_Model_Article-Objekte),
	 *                           $articlesCount die Anzahl an maximal findbaren ohne $startIdx und $maxarticles zu beachten
	 */
	public function getArticles($maxArticles = '', $startIdx = 0) {
		$startIdx      = abs(intval($startIdx)) + $this->offset;
		$articles      = $this->getMatchingArticles();
		$articlesCount = count($articles);
		$perPage       = empty($maxArticles) ? $articlesCount : (int) $maxArticles;
		$articles      = array_slice($articles, $startIdx, $perPage);

		return array($articles, $articlesCount);
	}

	/**
	 * Gibt die Artikel zurück, die auf einer Seite mit dem gesuchten Artikel sind.
	 *
	 * Die Parameter, nach denen die Artikel gefunden wurden, sind
	 * Klassenvariablen.
	 *
	 * @param  int $maxArticles  die maximale Anzahl von Artikeln, die zurückgegeben werden
	 * @param  int $articleID    Artikel für gewünschte Seite
	 * @return array             ($articles, $articlesCount, $page):
	 *                           $articles       gefundene Artikel (sly_Model_Article-Objekte),
	 *                           $articlesCount  Anzahl an maximal findbaren ohne $startIdx und $maxarticles zu beachten
	 *                           $page           aktuelle Seite
	 */
	public function getArticlesPageByArticle($maxArticles, $articleID) {
		if (empty($articleID) || empty($maxArticles)) return array(array(), 0);

		$articles      = $this->getMatchingArticles();
		$articlesCount = count($articles);
		$perPage       = empty($maxArticles) ? $articlesCount : (int) $maxArticles;

		foreach ($articles as $key => $article) {
			if ($article->getId() == $articleID) break;
		}

		if (!isset($key)) return array(array(), 0);

		$page     = intval($key/$maxArticles);
		$startIdx = abs($page*$maxArticles);
		$articles = array_slice($articles, $startIdx, $perPage);

		return array($articles, $articlesCount, $page);
	}

	/**
	 * Gibt eine Menge von Artikeln zurück
	 *
	 * Die Parameter, nach denen die Artikel gefunden wurden, sind
	 * Klassenvariablen.
	 *
	 * @return array  Array von passenden sly_Model_Article-Objekten
	 */
	protected function getMatchingArticles() {
		$articles = array();

		foreach ($this->getArticleTypes() as $type) {
			if ($this->metaFilterActive) {
				$firstFilter = true; // sollen wir die eine liste mit dem value holen, oder filtern?
				$this->metaFilterActive = false;

				foreach ($this->metaFilter as $token => $values) {
					if (empty($values)) continue;

					if ($firstFilter) {
						$sortby      = $this->sortby === null ? 'article.pos' : $this->sortby;
						$new         = WV2_MetaProvider::getArticlesWithMetaData($token, $this->onlineOnly, $values, WV_Datatype::FILTER_NOCASE | WV_Datatype::FILTER_BY_KEY, $sortby, $type);
						$firstFilter = false;
					}
					else {
						$new = self::filterArticlesByMetaData($new, $token, $values);
					}
					$this->metaFilterActive = true;
				}
			}

			if (!$this->metaFilterActive) {
				$sortby = $this->sortby === null ? 'pos' : $this->sortby;
				$new = WV2_MetaProvider::getArticlesByType($type, $this->onlineOnly, $sortby, $this->direction);
			}

			$articles = array_merge($articles, $new);
		}

		if ($this->filterByCategory) {
			$articles = $this->filterArticlesByCategory($articles);
		}

		if ($this->filterSelf) {
			$current = sly_Core::getCurrentArticleId();

			if ($current != -1) {
				foreach ($articles as $key => $article) {
					if ($article->getId() == $current) {
						unset($articles[$key]);
						$articles = array_values($articles);
						break;
					}
				}
			}
		}

		if ($this->filterByUser) {
			$articles = $this->filterByUser($articles);
		}

		if ($this->metaSortActive) {
			$articles = $this->sortArticlesByMetaData($articles);
		}

		return $articles;
	}

	/**
	 * Filtert eine Artikelmenge nach Benutzerzugehörigkeit
	 *
	 * @param  array $articles  Liste von Artikeln (sly_Model_Article-Objekte)
	 * @return array            das gefilterte Artikel Array
	 */
	private function filterByUser($articles) {
		$return = array();

		foreach ($articles as $article) {
			if ($article->getCreateUser() === $this->userName) $return[] = $article;
		}

		return $return;
	}
}
