<?php
namespace Engine\GenDocument\TextProcessor;

/*
 * The main class contiaining the list of function that are available for calling
 * */
class DefaultFunctions
{
	public $value = null;
	public $useUTF8 = null;

	final public function __construct($value){
		if(extension_loaded('mbstring')){
			$this->useUTF8 = true;
		}

		$this->value = $value;
	}


	function __toString()
	{

		if (is_array($this->value)) {
			return implode(", ", $this->value);
		}

		if (is_object($this->value) and !method_exists($this->value, '__toString')) {
  			return "-OBJECT-".get_class($this->value).'-';
		}

		return (string)$this->value;

	}

	protected function _toTimestamp($value){
		$dt = \DateTime::createFromFormat("Y-m-d", $value);
		if(!$dt){
			$dt = \DateTime::createFromFormat("Y-m-d H:i:s", $value);
		}

		return $dt->getTimestamp();
	}

	/**
	 * Compare this value with another value (string or number)
	 * returns 0/1
	 * */
	public function compare($reference){
		if($this->value==$reference){
			return new static(1);
		}
		return new static(0);
	}

	/**
	 * Compare this number with another number, and select one text to return.
	 * Use "-parent-" to refer to the initial value
	 * Use "-skipFormula-" to stop parsing the rest of the formula
	 * return string
	 * */
	public function ifEq($reference, $trueText, $falseText){
		$eq = ((string)$this->value==(string)$reference);
		$isObj = (is_object($falseText) || is_array($trueText));
		$usedText = ($eq?$trueText:$falseText);

		if (!$isObj) {
			switch ((string)$usedText) {
				case '-skipFormula-':
					return null;
				break;
				case '-parent-':
					return new static($this->value);
				break;
			}
		}

		// else & default
		return new static($usedText);
	}

	/**
	 * Compare this number with another number, and select one text to return.
	 * Use "-parent-" to refer to the initial value
	 * Use "-skipFormula-" to stop parsing the rest of the formula
	 * return string
	 * */
	public function ifNotEq($reference, $trueText, $falseText){
		$eq = ((string)$this->value==(string)$reference);
		$isObj = (is_object($falseText) || is_array($trueText));
		$usedText = ($eq?$falseText:$trueText);

		if (!$isObj) {
			switch ((string)$usedText) {
				case '-skipFormula-':
					return null;
				break;
				case '-parent-':
					return new static($this->value);
				break;
			}
		}

		// else & default
		return new static($usedText);
	}

	/**
	 * Compare this number with another number, and select one text to return if the input data is greater than the reference
	 * Use "-parent-" to refer to the initial value
	 * return string
	 * */
	public function ifGt($reference, $trueText, $falseText){
		if((float)$this->value>(float)$reference){
			if(!(is_object($falseText) || is_array($trueText)) && (string)$trueText=='-parent-'){
				return new static($this->value);
			}else{
				return new static($trueText);
			}
		}else{
			if(!(is_object($falseText) || is_array($trueText)) && (string)$falseText=='-parent-'){
				return new static($this->value);
			}else{
				return new static($falseText);
			}
		}
	}

	/**
	 * Compare this number with another number, and select one text to return if the input data is less than the reference
	 * Use "-parent-" to refer to the initial value
	 * return string
	 * */
	public function ifLt($reference, $trueText, $falseText){
		if((float)$this->value<(float)$reference){
			if(!(is_object($falseText) || is_array($trueText)) && (string)$trueText=='-parent-'){
				return new static($this->value);
			}else{
				return new static($trueText);
			}
		}else{
			if(!(is_object($falseText) || is_array($trueText)) && (string)$falseText=='-parent-'){
				return new static($this->value);
			}else{
				return new static($falseText);
			}
		}
	}

	/**
	 * Compare this value with another value
	 * returns string
	 * */
	public function select($trueText, $falseText){
		if((bool)$this->value){
			return new static($trueText);
		}
		return new static($falseText);
	}

	/** LIST/ARRAY ***************************************************************/

	/**
	 * Convert a list back to string
	 * @param string $separator
	 * @return \Engine\GenDocument\TextProcessor\DefaultTextHandler
	 */
	public function listJoin($separator=", "){

		if ($separator=='NL') {
			$separator = "\n";
		}
		elseif ($separator=='TAB') {
			$separator = "\t";
		}

		return new static(implode($separator, $this->value));
	}

	/**
	 * Filter the list according to the specified filter
	 * return list
	 * */
	public function listFilter($regexp){
		$return = [];

		// loop through the list and see which items match
		foreach($this->value as $str){
			if(preg_match("/{$regexp}/u", $str)){
				$return[]=$str;
			}
		}

		return new static($return);
	}

	/**
	 * Clear the list of empty items
	 * return list
	 * */
	public function listClean(){
		$return = [];

		// loop through the list and see which items match
		foreach($this->value as $str){
			$str = trim($str);
			if($str!==''){
				$return[]=$str;
			}
		}

		return new static($return);
	}

	/**
	 * Sort the list
	 * $order can be ASC, DESC, HUMAN
	 * return list
	 * */
	public function listSort($order='ASC'){
		if($order=='ASC'){
			sort($this->value);
		}elseif($order=='DESC'){
			rsort($this->value);
		}elseif($order=='HUMAN'){
			natcasesort($this->value);
		}

		return new static($this->value);
	}

	/**
	 * Reverse the list
	 * return list
	 * */
	public function listReverse(){
		return new static(array_reverse($this->value));
	}

	/**
	 * Remove duplicate items, leaving only the unique ones
	 * return list
	 * */
	public function listUnique(){
		return new static(array_unique($this->value));
	}

	/**
	 * Return the element count
	 * return list
	 * */
	public function listCount(){
		return new static(count($this->value));
	}

	/**
	 * Return the first element
	 * return list
	 * */
	public function listFirst(){
		return new static(reset($this->value));
	}

	/**
	 * Return the last element
	 * return list
	 * */
	public function listLast(){
		return new static(end($this->value));
	}

	/**
	 * Return the element with the specified key
	 * return string
	 * */
	public function getElement($key){
		if (is_array($this->value)) {
			// is this a multiple-depth array?
			$m = [];
			if (preg_match("/^(?P<key1>[^\.]+)\.(?P<remainingKey>.+)$/", $key, $m)) {
				$remainingKey = $m['remainingKey'];
				$key1 = $m['key1'];

				$obj = new static($this->value[$key1]);
				return $obj->getElement($remainingKey);
			}else{	// simple-access
				return new static($this->value[$key]);
			}
		}else{
			return new static("");
		}
	}

	/**
	 * Return the object attribute with the specified key
	 * return string
	 * */
	public function getAttribute($key){
		if (is_object($this->value) && $key!==null && $key!=='') {
			// is this a multiple-depth array?
			$m = [];
			if (preg_match("/^(?P<key1>[^\.]+)\.(?P<remainingKey>.+)$/", $key, $m)) {
				$remainingKey = $m['remainingKey'];
				$key1 = $m['key1'];

				$obj = new static($this->value->{$key1});
				return $obj->getAttribute($remainingKey);
			}else{	// simple-access
				return new static($this->value->{$key});
			}
		}else{
			return new static("");
		}
	}


	/** DATE *********************************************************************/

	/**
	 * Get the current date, cane be used without variable
	 * return date
	 * */
	public function now(){
		return new static(date("Y-m-d H:i:s"));
	}

	/**
	 * Format the date by using the specified format
	 * 	- Y - year
	 * 	- m - month
	 * 	- d - day
	 *  - H - hour (24h format)
	 *  - h - hour (12h format)
	 * 	- i - minutes
	 * 	- s - seconds
	 * 	- A - AM/PM
	 *
	 * 	- z - day of the year
	 * 	- W - week number in year
	 *
	 * 	- M - short textual representaion of the month
	 *  - F - full textual representaion of the month
	 *  - y - short representation of the year
	 *
	 *  Example: Y-m-d will display the date in the MySQL date format
	 *  return string
	 * */
	public function dateFormat($format){
		$dt = \DateTime::createFromFormat("Y-m-d", $this->value);
		if(!$dt){
			$dt = \DateTime::createFromFormat("Y-m-d H:i:s", $this->value);
		}

		if(!$dt){
			// return blank in case the input format is invalid
			return new static('');
		}

		return new static($dt->format($format));
	}

	public function dateFormatArabic($format){
		$dt = \DateTime::createFromFormat("Y-m-d", $this->value);
		if (!$dt) {
			$dt = \DateTime::createFromFormat("Y-m-d H:i:s", $this->value);
		}
		if (!$dt) {
			// return blank in case the input format is invalid
			return new static('');
		}

		// @see https://brainbell.com/php/localizing-dates-intldateformatter.html
		// @see https://github.com/khaled-alshamaa/ar-php
		$fmt = new \IntlDateFormatter(
			'ar_SA@calendar=ISLAMIC', // 'en_US@calendar=ISLAMIC',	// 'ar_SA@calendar=ISLAMIC',
			\IntlDateFormatter::FULL,
			\IntlDateFormatter::FULL,
			date_default_timezone_get(),
			\IntlDateFormatter::TRADITIONAL
		);

		// @see https://unicode-org.github.io/icu/userguide/format_parse/datetime/
		$formatMapping = [
			'y' => 'yy',
			'Y' => 'yyyy',
			'm' => 'MM',
			'd' => 'dd',
			'H' => 'HH',
			'i' => 'mm',
			's' => 'ss',
			'w' => 'ww',
			'A' => 'a',
		];
		$fmt->setPattern(str_replace(array_keys($formatMapping), array_values($formatMapping), $format));

		return new static($fmt->format($dt->getTimestamp()));
	}

	/**
	 * Format the date as a standard date
	 * return date
	 * */
	public function dateFormatStandard(){
		$dt = (string)$this->dateFormat("Y-m-d");

		return new static(\Engine\DateTime::isoToStd($dt));
	}

	/**
	 * Compare this date with another date
	 * returns 0/1
	 * */
	public function dateCompare($reference){ // TODO: renamed from compare
		$ts1 = $this->_toTimestamp($reference);
		if($ts1==false){
			$ts1 = 0;
		}

		$ts2 = $this->_toTimestamp($this->value);
		if($ts2==false){
			$ts2 = 0;
		}

		if($ts1==$ts2){
			return new static(1);
		}
		return new static(0);
	}

	/** STRING *******************************************************************/

	/**
	 * Transform the string to its uppercase variant
	 * return string
	 * */
	public function uppercase(){
		if($this->useUTF8){
			return new static(mb_strtoupper($this->value, "UTF8"));
		}else{
			return new static(strtoupper($this->value));
		}
	}

	/**
	 * Transform the string to its lowercase variant
	 * return string
	 * */
	public function lowercase(){
		if($this->useUTF8){
			return new static(mb_strtolower($this->value, "UTF8"));
		}else{
			return new static(strtolower($this->value));
		}
	}

	/**
	 * Transform the string to its lowercase variant
	 * return string
	 * */
	public function substring($start=0, $length=-1)
	{
		if ($this->useUTF8) {
			$return = mb_substr($this->value, $start, $length, "UTF8");
		}
		else {
			$return = substr($this->value, $start, $length);
		}

		return new static($return);
	}

	/**
	 * Truncate a string to a certain number of characters
	 * return string
	 * */
	public function truncate($length, $postStr="..."){
		if($this->useUTF8){
			$return = mb_substr($this->value, 0, $length, "UTF8");
		}else{
			$return = substr($this->value, 0, $length);
		}

		if($return!=$this->value){
			$return.=$postStr;
		}

		return new static($return);
	}

	/**
	 * Turns the first character of a string to uppercase
	 * return string
	 * */
	public function capitalize(){
		if($this->useUTF8){
			$return = mb_convert_case($this->value, MB_CASE_TITLE, "UTF8");
		}else{
			$return = ucwords($this->value);
		}

		return new static($return);
	}

	/**
	 * Turns the first character of a string in a sentence to uppercase
	 * return string
	 * */
	public function ucfirst()
	{
		if ($this->useUTF8) {
			$return = mb_strtoupper(mb_substr($this->value, 0, 1, "UTF8")) . mb_substr($this->value, 1, mb_strlen($this->value), "UTF8");
		}
		else {
			$return = ucfirst($this->value);
		}

		return new static($return);
	}

	/**
	 * Break the text into multiple lines
	 * $break: NL - new line, TAB: the tab character
	 * return string
	 * */
	public function wrap($width=80, $break="NL"){
		if($this->useUTF8){
			mb_internal_encoding("UTF-8");
			$words =  mb_split('(\s)', $this->value);

			if($break=='NL'){
				$break = "\n";
			}elseif($break=='TAB'){
				$break = "\t";
			}

			$separator = '';
			$row = '';
			$return = '';
			foreach($words as $word){
				$row .= $separator.$word;
				$separator = ' ';

				if (mb_strlen($row) >= $width){
					if($return){
						$return .= $break . $row;
					}else{
						$return .= $row;
					}

					$row = '';
					$separator = '';
				}
			}

			$return .= $break . $row;

			return new static($return);
		}else{
			if($break=='NL') {
				$break = "\n";
			}
			elseif($break=='TAB') {
				$break = "\t";
			}
			$return = wordwrap($this->value, $width, $break);

			return new static($return);
		}
	}

	/**
	 * Split a text by using a given regexp
	 * @param string $separator
	 * @return \Engine\GenDocument\TextProcessor\DefaultTextHandler
	 */
	public function split($separator=","){
		return new static(preg_split("/{$separator}/", (string)$this->value));
	}

	/**
	 * Replace a text with another text
	 * @param string $search
	 * @param string $replace
	 * @return \Engine\GenDocument\TextProcessor\DefaultTextHandler
	 */
	public function replace($search, $replacement) {
		return new static(str_replace($search, $replacement, $this->value));
	}


	/** NUMBERS ******************************************************************/

	/**
	 * Round to a specified number of decimals
	 * return number
	 * */
	public function round($decimals=2){
		return new static(round((float)$this->value, $decimals));
	}

	/**
	 * Round down the number
	 * return number
	 * */
	public function floor(){
		return new static(floor((float)$this->value));
	}

	/**
	 * Round up the number
	 * return number
	 * */
	public function ceil(){
		return new static(ceil((float)$this->value));
	}

	/**
	 * Add a number
	 * return number
	 * */
	public function numberAdd($number){
		return new static((float)$this->value + (float)$number);
	}

	/**
	 * Substract a number
	 * return number
	 * */
	public function numberSubstract($number){
		return new static((float)$this->value - (float)$number);
	}

	/**
	 * Multiply a number
	 * return number
	 * */
	public function numberMultiply($multiplier){
		return new static((float)$this->value * (float)$multiplier);
	}

	/**
	 * Divide a number
	 * return number
	 * */
	public function numberDivide($divisor){
		if ($divisor) {
			return new static((float)$this->value/(float)$divisor);
		} else {
			return new static(0);
		}
	}

	/**
	 * Format a number as a local string, with thousands and decimal separators
	 * return number
	 * */
	public function numberFormat($decimals=2, $thousands_sep=",", $dec_point="."){
		if (is_numeric($this->value)) {
			return new static( (string)number_format($this->value, $decimals, $dec_point, $thousands_sep) );
		} else {
			return new static('');
		}
	}

	/**
	 * Return the absoulte value of the specified number
	 * return number
	 */
	public function numberAbs(){
		return new static(abs((float)$this->value));
	}

}

