<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2008 - 2014 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flSearchTags Class
 *
 * Class to provide the functionality to query the tag information.
 *
 * @package fusionLib
 */
class flSearchTags {

	/**
	 * The database.
	 *
	 * @var flDb
	 */
	protected $db;

	/**
	 * The base name for tables.
	 *
	 * @var string
	 */
	protected $tableBase;

	/**
	 * The document storage.
	 *
	 * @var flDbNestedSet
	 */
	protected $docs;

	/**
	 * Construct the object.
	 *
	 * @param flDb $db The database object to use.
	 * @param string $tableBase The base name for database tables.
	 */
	function __construct($db, $tableBase) {
		$this->db = $db;
		$this->tableBase = $tableBase;

		// The document set
		$this->docs = new flDbNestedSet($db, "{$this->tableBase}_docs");
	}

	/**
	 * Get the tags and their weights.
	 *
	 * @param array|string $path Array of category names that is the path to limit the cloud to, '' to search all.
	 * @param bool $shortPath True if using short path format root:node.
	 * @param array $accessPerms Array of acces permissions that the searcher has.
	 * @param int $numTags The number of tags to use.
	 * @return array Array of tags.
	 */
	function getCloud($path, $shortPath = true, $accessPerms = array(), $numTags = 25) {
		// Get the start document and limits
		if((is_string($path) && $path == '') || !count($path)) {
			$root = $lft = $rgt = 0;
		}
		else {
			if($shortPath) {
				if(!($d = $this->docs->getByShortPath($path, 'slug', array('id', 'lft', 'rgt'))))
					return array();
			}
			else {
				if(!($d = $this->docs->getByPath($path, 'slug', array('id', 'lft', 'rgt'))))
					return array();
			}

			$root = $d['id'];
			$lft = $d['lft'];
			$rgt = $d['rgt'];
		}

		// Get the tags and their frequency
		$sql = "SELECT tag, COUNT(*) AS t FROM {$this->tableBase}_tags AS tc,{$this->tableBase}_docs AS td WHERE tc.id=td.id";
		if($root)
			$sql .= " && td.lft BETWEEN $lft AND $rgt";
		$sql .= " && NOT (SELECT COUNT(*) FROM {$this->tableBase}_docs AS c,{$this->tableBase}_docs AS p WHERE c.lft BETWEEN p.lft AND p.rgt && c.id=td.id && p.accessperm NOT IN (''" . str_repeat(',?', count($accessPerms)) . ")) GROUP BY tag ORDER BY tag ASC LIMIT $numTags";
		$result = $this->db->query($sql, $accessPerms);

		// Find the range
		$min = $max = null;
		$tags = array();
		while($row = $result->fetch()) {
			$min = $min === null ? $row['t'] : min($min, $row['t']);
			$max = $max === null ? $row['t'] : max($max, $row['t']);
			$tags[$row['tag']] = $row['t'];
		}

		// Calculate tag weight 0 - 100 in steps of 10
		$cloud = array();
		$mult = $min == $max ? 0 : (100 / ($max - $min));
		foreach($tags as $tag => $t)
			$cloud[$tag] = round(($t - $min) * $mult, -1);

		return $cloud;
	}

	/**
	 * Find the documents for a tag.
	 *
	 * @param string $tag The tag or comma separated list of tags to show.
	 * @param array|string $path Array of category names that is the path to limit the search to, '' to search all.
	 * @param bool $shortPath True if using short path format root:node.
	 * @param bool $returnShortPaths true if returning only root and node id per result rather than full path to result node.
	 * @param array $accessPerms Array of acces permissions that the searcher has.
	 * @param int $start The offset into the results.
	 * @param int $count The number of results to return or 0 to return all.
	 * @return flSearchResults Object holding the results.
	 */
	function find($tag, $path = '', $shortPath = true, $returnShortPaths = true, $accessPerms = array(), $start = 0, $count = 10) {
		// Break tag on commas
		$tags = preg_split('/\s*,\s*/', $tag);
		if(!count($tags))
			return new flSearchResults();

		// Get the start document and limits
		if((is_string($path) && $path == '') || !count($path)) {
			$root = $lft = $rgt = 0;
		}
		else {
			if($shortPath) {
				if(!($d = $this->docs->getByShortPath($path, 'slug', array('id', 'lft', 'rgt'))))
					return new flSearchResults();
			}
			else {
				if(!($d = $this->docs->getByPath($path, 'slug', array('id', 'lft', 'rgt'))))
					return new flSearchResults();
			}

			$root = $d['id'];
			$lft = $d['lft'];
			$rgt = $d['rgt'];
		}

		// Build the query color or colour
		$sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT d.id FROM {$this->tableBase}_tags AS t,{$this->tableBase}_docs AS d WHERE t.id=d.id && tag IN(?" . str_repeat(',?', count($tags) - 1) . ')';
		if($root)
			$sql .= " && lft BETWEEN $lft AND $rgt";
		$sql .= " && NOT (SELECT COUNT(*) FROM {$this->tableBase}_docs AS c,{$this->tableBase}_docs AS p WHERE c.lft BETWEEN p.lft AND p.rgt && c.id=d.id && p.accessperm NOT IN (''" . str_repeat(',?', count($accessPerms)) . ')) ORDER BY publishedat DESC';
		if($count > 0)
			$sql .= " LIMIT  $start,$count";

		// Run query
		$docs = array();
		$result = $this->db->query($sql, array_merge($tags, $accessPerms));
		$totalMatches = $this->db->fetchOne('SELECT FOUND_ROWS()');

		while($row = $result->fetch()) {
			// Get the document path
			if($returnShortPaths)
				$docs[] = $this->docs->getShortPath($row['id'], 'slug');
			else {
				$res = $this->docs->getPath($row['id'], array('slug'));
				$segs = array();
				foreach($res as $d)
					$segs[] = $d['slug'];
				$docs[] = $segs;
			}
		}

		// Return the search results
		return new flSearchResults($docs, $totalMatches);
	}
}
