<?php
/**
 * Wiki farm animal infos and statistics class
 *
 * @license  GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author   Etienne MELEARD <etienne.meleard@cru.fr>
 * @desc     Gather and returns various infos and statistics about an animal
 */

if(!defined('DOKU_FARM_PLUGIN')) define('DOKU_FARM_PLUGIN', defined('DOKU_FARMPLUGINLOADED') ? DOKU_INC.'lib/plugins/farm/' : './');

require_once DOKU_FARM_PLUGIN.'animal.class.php';

class dokuwiki_farm_animal_infos {
	var $animal = null;
	
	/**
	 * @param $animal animal object or animal name as string
	 */
	function __construct($animal = null) {
		if(is_a($animal, 'dokuwiki_farm_animal')) {
			if(dokuwiki_farm_animal::exists($animal->getName())) $this->animal = $animal;
		}elseif(is_string($animal)) {
			if(dokuwiki_farm_animal::exists($animal)) $this->animal = new dokuwiki_farm_animal($animal);
		}
	}
	
	/**
	 * Returns the size of a directory contents
	 * @param $p path to the directory
	 * @return directory disk usage in bytes
	 */
	private function dirsize($p = '.') {
		$s = 0;
		foreach(scandir($p) as $i) {
			if($i == '.' || $i == '..') continue;
			if(@is_file($p.'/'.$i)) $s += filesize($p.'/'.$i);
			if(@is_dir($p.'/'.$i)) $s += $this->dirsize($p.'/'.$i);
		}
		return $s;
	}
	
	/**
	 * Format data using a string template
	 * @param $fmt string template
	 * @param $opt array of replacers
	 * @return formated string
	 */
	public function formatedprint($fmt, $opt) {
		$rawreplace = array('url', 'link', 'namespace', 'user');
		foreach($rawreplace as $k) if(isset($opt[$k])) $fmt = str_replace('{'.$k.'}', $opt[$k], $fmt);
		if(isset($opt['date'])) {
			$date = (int)$opt['date'];
			$dfmt = isset($opt['dformat']) ? $opt['dformat'] : '%Y/%m/%d %H:%M';
			$delta = abs(time() - $date);
			$date = strftime($dfmt, $date);
			$fmt = str_replace('{date}', $date, $fmt);
			if(isset($opt['dunits'])) {
				$triggers = array(
					's' => array(1, 60),
					'i' => array(60, 3600),
					'h' => array(3600, 24*3600),
					'd' => array(24*3600, 7*24*3600),
					'w' => array(7*24*3600, 30.5*24*3600),
					'm' => array(30.5*24*3600, 365.25*24*3600),
					'y' => array(365.25*24*3600, 0)
				);
				$prec = null;
				$str = '?';
				foreach($triggers as $t => $v) {
					if($v[1] && $delta >= $v[1]) {
						$prec = $t;
						continue;
					}
					$hg = floor($delta / $v[0]);
					$lw = $prec ? floor(($delta % $v[0]) / $triggers[$prec][0]) : null;
					$str = $hg.' '.$opt['dunits'][$t][($hg > 1) ? 1 : 0].($lw ? ' '.$lw.' '.$opt['dunits'][$prec][($lw > 1) ? 1 : 0] : '');
					break;
				}
				$fmt = str_replace('{delta}', $str, $fmt);
			}
		}
		if(isset($opt['size'])) $fmt = str_replace('{size}', $this->animal->manager->nicesize($opt['size']), $fmt);
		$fmt = preg_replace('`\{[^}]+\}`', '?', $fmt);
		return $fmt;
	}
	
	/**
	 * Shorten a namespace identifier for rendering
	 * @param $ns namespace identifier
	 * @param $maxlen maximum length
	 * @return shortened namespace identifier
	 */
	private function shortenNS($ns, $maxlen=32) {
		$ns = str_replace('/', ':', $ns);
		if(strlen($ns) <= $maxlen) return $ns;
		if(preg_match('`^([^:]+:)([^:]+(:[^:]+))(:.+)$`', $ns, $m)) {
			$st = $m[1];
			$mid = $m[2];
			$end = $m[4];
			while(strlen($st.$end) + 3 <= $maxlen) {
				$st .= substr($mid, 0, 1);
				$end = substr($mid, -1, 1).$end;
				$mid = substr($mid, 1, -1);
			}
			$ns = $st.'...'.$end;
		}
		if(strlen($ns) > $maxlen + 5) $ns = substr($ns, 0, floor($maxlen / 2)).'...'.substr($ns, -1 * floor($maxlen / 2));
		return $ns;
	}
	
	/**
	 * Get the disk usage of the related animal
	 * @param $mode specific item family pointer
	 * @param $nice boolean telling whether to nicely format the size or not
	 * @return size as string
	 */
	public function getSize($mode='', $nice=false) {
		if(!in_array($mode, array('pages', 'media', 'cache', 'index'))) $mode = '';
		if(!$this->animal) return 0;
		$s = $this->dirsize($this->animal->getPath().(($mode != '') ? '/data/'.$mode : ''));
		if($nice) $s = $this->animal->manager->nicesize($s);
		return $s;
	}
	
	/**
	 * Gather the list of files in a directory and 'stat' them
	 * @param $p path
	 * @param $regexp PCRE used to match only specific files
	 * @param $stripinitialpath boolean telling whether striping base path from items paths or not
	 * @param $stripend integer used to specify if the identifiers must be truncated
	 * @param $initialpath base path
	 * @return associative array of 'stat' results
	 */
	public function filesinfo($p='.', $regexp='', $stripinitialpath=false, $stripend=0, $initialpath='') {
		$r = array();
		if($initialpath == '') $initialpath = $p;
		foreach(scandir($p) as $i) {
			if($i == '.' || $i == '..') continue;
			$w = $p.'/'.$i;
			if($stripinitialpath && $initialpath != '') {
				$w = substr($w, strlen($initialpath));
				while(substr($w, 0, 1) == '/') $w = substr($w, 1);
			}
			if($stripend) $w = substr($w, 0, -1 * $stripend);
			if(@is_dir($p.'/'.$i)) $r = array_merge($r, $this->filesinfo($p.'/'.$i, $regexp, $stripinitialpath, $stripend, $initialpath));
			if(@is_file($p.'/'.$i)) if($regexp == '' || preg_match('`'.$regexp.'`i', $i)) $r[$w] = stat($p.'/'.$i);
		}
		return $r;
	}
	
	/**
	 * Returns the average size of the specified items
	 * @param $f array of 'stat' results
	 * @return average size
	 */
	private function avgsize($f) {
		return round(array_sum(array_map(create_function('$e', 'return $e[\'size\'];'), $f)) / count($f));
	}
	
	/**
	 * Returns the total size of the specified items
	 * @param $f array of 'stat' results
	 * @return total size
	 */
	private function totalsize($f) {
		return array_sum(array_map(create_function('$e', 'return $e[\'size\'];'), $f));
	}
	
	/**
	 * Returns the identifier of the biggest item from specified items
	 * @param $f array of 'stat' results
	 * @return file identifier as string
	 */
	private function biggest($f) {
		uasort($f, create_function('$a,$b', 'return $b[\'size\'] - $a[\'size\'];'));
		return key($f);
	}
	
	/**
	 * Returns the identifier of the smallest item from specified items
	 * @param $f array of 'stat' results
	 * @return file identifier as string
	 */
	private function smallest($f) {
		uasort($f, create_function('$a,$b', 'return $a[\'size\'] - $b[\'size\'];'));
		return key($f);
	}
	
	/**
	 * Returns a page metadata
	 * @param $page page identifier
	 * @return array of metadata (DokuWik style)
	 */
	private function getmeta($page) {
		if(!$this->animal) return 0;
		if($page == '') return null;
		if(!@file_exists($this->animal->getPath().'/data/meta/'.$page.'.meta')) return null;
		return unserialize(@file_get_contents($this->animal->getPath().'/data/meta/'.$page.'.meta'));
	}
	
	/**
	 * Extract the last modification date from metadata
	 * @param $m metadata array (DokuWik style)
	 * @return unix timestamp
	 */
	private function getmdate($m) {
		if(isset($m['last_change']['date'])) return (int)$m['last_change']['date'];
		if(isset($m['current']['date']['modified'])) return (int)$m['current']['date']['modified'];
		if(isset($m['current']['date']['created'])) return (int)$m['current']['date']['created'];
		return 0;
	}
	
	/**
	 * Returns the identifier of the oldest modified item from specified items
	 * @param $f array of 'stat' results
	 * @return file identifier as string
	 */
	private function oldest($f) {
		$fm = array();
		foreach($f as $ns => $i) $fm[$ns] = $this->getmdate($this->getmeta($ns));
		uasort($fm, create_function('$a,$b', 'return $a - $b;'));
		return key($fm);
	}
	
	/**
	 * Returns the identifier of the latest modified item from specified items
	 * @param $f array of 'stat' results
	 * @return file identifier as string
	 */
	private function latest($f) {
		$fm = array();
		foreach($f as $ns => $i) $fm[$ns] = $this->getmdate($this->getmeta($ns));
		uasort($fm, create_function('$a,$b', 'return $b - $a;'));
		return key($fm);
	}
	
	/**
	 * Build a namespace url
	 * @param $ns namespace
	 * @param $media boolean telling whether the pointed item is a media ressource or not
	 * @return url string
	 */
	private function elurl($ns, $media=false) {
		if(!$this->animal) return 0;
		return $this->animal->getUrl($ns, $media);
	}
	
	/**
	 * Build a namespace html link
	 * @param $ns namespace
	 * @param $media boolean telling whether the pointed item is a media ressource or not
	 * @return string
	 */
	private function elhtmlurl($ns, $media=false) {
		return '<a href="'.str_replace('&', '&amp;', $this->elurl($ns, $media)).'" target="_blank">'.$this->shortenNS($ns).'</a>';
	}
	
	/**
	 * Returns animal's status
	 * @return string
	 */
	public function getStatus() {
		return $this->animal->getStatus();
	}
	
	/**
	 * Returns animal's lock state
	 * @return string
	 */
	public function getLockState() {
		return $this->animal->getLockState();
	}
	
	/**
	 * Returns animal's creation date
	 * @param $nice boolean telling whether output must be formated
	 * @return unix timestamp or formated date as string
	 */
	public function getCreation($nice=false) {
		global $conf;
		$d = (int)$this->animal->getMetadata('creation_date');
		if(!$nice) return $d;
		return $this->formatedprint(
			$this->animal->manager->getLang('animal_info_creationfmt'),
			array(
				'date' => $d,
				'dformat' => $conf['dformat'],
				'dunits' => $this->animal->manager->getLang('timedeltaunits')
			)
		);
	}
	
	/**
	 * Gather infos about animal's pages
	 * @param $nice boolean telling whether output must be formated
	 * @return array of infos descriptors
	 */
	public function pagesInfo($nice=false) {
		if(!$this->animal) return 0;
		global $conf;
		
		$fi = $this->filesinfo($this->animal->getPath().'/data/pages', '.*\.txt', true, 4);
		$info = array();
		
		// general
		$info['count'] = count($fi);
		
		$info['average'] = $this->avgsize($fi);
		if($nice) $info['average'] = $this->animal->manager->nicesize($info['average']);
		
		$info['total'] = $this->totalsize($fi);
		if($nice) $info['total'] = $this->animal->manager->nicesize($info['total']);
		
		// biggest page
		$bg = $this->biggest($fi);
		if($nice) {
			$info['biggest'] = $this->elhtmlurl($bg).' ('.$this->animal->manager->nicesize($fi[$bg]['size']).')';
		}else{
			$info['biggest'] = array(
				'size' => $fi[$bg]['size'],
				'url' => $this->elurl($bg),
				'namespace' => $bg
			);
		}
		
		// smallest page
		$st = $this->smallest($fi);
		if($nice) {
			$info['smallest'] = $this->elhtmlurl($st).' ('.$this->animal->manager->nicesize($fi[$st]['size']).')';
		}else{
			$info['smallest'] = array(
				'size' => $fi[$st]['size'],
				'url' => $this->elurl($st),
				'namespace' => $st
			);
		}
		
		// oldest page (by edit date)
		$ot = $this->oldest($fi);
		$m = $this->getmeta($ot);
		if($nice) {
			$info['oldest'] = $this->formatedprint(
				$this->animal->manager->getLang('animal_info_pageschangedescriptor'),
				array(
					'link' => $this->elhtmlurl($ot),
					'user' => $m['current']['last_change']['user'],
					'date' => $m['current']['last_change']['date'],
					'dformat' => $conf['dformat'],
					'dunits' => $this->animal->manager->getLang('timedeltaunits')
				)
			);
		}else{
			$info['oldest'] = array(
				'date' => $m['current']['last_change']['date'],
				'user' => $m['current']['last_change']['user'],
				'url' => $this->elurl($ot),
				'namespace' => $ot
			);
		}
		
		// latest edited page
		$lt = $this->latest($fi);
		$m = $this->getmeta($lt);
		if($nice) {
			$info['latest'] = $this->formatedprint(
				$this->animal->manager->getLang('animal_info_pageschangedescriptor'),
				array(
					'link' => $this->elhtmlurl($lt),
					'user' => $m['current']['last_change']['user'],
					'date' => $m['current']['last_change']['date'],
					'dformat' => $conf['dformat'],
					'dunits' => $this->animal->manager->getLang('timedeltaunits')
				)
			);
		}else{
			$info['latest'] = array(
				'date' => $m['current']['last_change']['date'],
				'user' => $m['current']['last_change']['user'],
				'url' => $this->elurl($lt),
				'namespace' => $lt
			);
		}
		
		return $info;
	}
	
	/**
	 * Gather infos about animal's medias
	 * @param $nice boolean telling whether output must be formated
	 * @return array of infos descriptors
	 */
	public function mediasInfo($nice=false) {
		if(!$this->animal) return 0;
		global $conf;
		
		$fi = $this->filesinfo($this->animal->getPath().'/data/media', '', true);
		$info = array();
		
		// general
		$info['count'] = count($fi);
		
		$info['average'] = $this->avgsize($fi);
		if($nice) $info['average'] = $this->animal->manager->nicesize($info['average']);
		
		$info['total'] = $this->totalsize($fi);
		if($nice) $info['total'] = $this->animal->manager->nicesize($info['total']);
		
		// biggest media
		$bg = $this->biggest($fi);
		if($nice) {
			$info['biggest'] = $this->elhtmlurl($bg, true).' ('.$this->animal->manager->nicesize($fi[$bg]['size']).')';
		}else{
			$info['biggest'] = array(
				'size' => $fi[$bg]['size'],
				'url' => $this->elurl($bg, true),
				'namespace' => $bg
			);
		}
		
		// smallest media
		$st = $this->smallest($fi);
		if($nice) {
			$info['smallest'] = $this->elhtmlurl($st, true).' ('.$this->animal->manager->nicesize($fi[$st]['size']).')';
		}else{
			$info['smallest'] = array(
				'size' => $fi[$st]['size'],
				'url' => $this->elurl($st, true),
				'namespace' => $st
			);
		}
		
		return $info;
	}
}
?>
