<?php

/** PHPExcel root directory */
if (! defined ( 'PHPEXCEL_ROOT' ))
{
  /**
   *
   * @ignore
   *
   */
  define ( 'PHPEXCEL_ROOT', dirname ( __FILE__ ) . '/../../' );
  require (PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
}

/**
 * MAX_VALUE
 */
define ( 'MAX_VALUE', 1.2e308 );

/**
 * 2 / PI
 */
define ( 'M_2DIVPI', 0.63661977236758134307553505349006 );

/**
 * MAX_ITERATIONS
 */
define ( 'MAX_ITERATIONS', 256 );

/**
 * PRECISION
 */
define ( 'PRECISION', 8.88E-016 );

/**
 * PHPExcel_Calculation_Functions
 *
 * Copyright (c) 2006 - 2015 PHPExcel
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * @category PHPExcel
 * @package PHPExcel_Calculation
 * @copyright Copyright (c) 2006 - 2015 PHPExcel (http://www.codeplex.com/PHPExcel)
 * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
 * @version ##VERSION##, ##DATE##
 */
class PHPExcel_Calculation_Functions
{
  
  /**
   * constants
   */
  const COMPATIBILITY_EXCEL = 'Excel';
  const COMPATIBILITY_GNUMERIC = 'Gnumeric';
  const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc';
  const RETURNDATE_PHP_NUMERIC = 'P';
  const RETURNDATE_PHP_OBJECT = 'O';
  const RETURNDATE_EXCEL = 'E';
  
  /**
   * Compatibility mode to use for error checking and responses
   *
   * @access private
   * @var string
   */
  protected static $compatibilityMode = self::COMPATIBILITY_EXCEL;
  
  /**
   * Data Type to use when returning date values
   *
   * @access private
   * @var string
   */
  protected static $returnDateType = self::RETURNDATE_EXCEL;
  
  /**
   * List of error codes
   *
   * @access private
   * @var array
   */
  protected static $errorCodes = array (
      'null' => '#NULL!',
      'divisionbyzero' => '#DIV/0!',
      'value' => '#VALUE!',
      'reference' => '#REF!',
      'name' => '#NAME?',
      'num' => '#NUM!',
      'na' => '#N/A',
      'gettingdata' => '#GETTING_DATA' 
  );
  
  /**
   * Set the Compatibility Mode
   *
   * @access public
   * @category Function Configuration
   * @param string $compatibilityMode
   *          Compatibility Mode
   *          Permitted values are:
   *          PHPExcel_Calculation_Functions::COMPATIBILITY_EXCEL 'Excel'
   *          PHPExcel_Calculation_Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
   *          PHPExcel_Calculation_Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
   * @return boolean (Success or Failure)
   */
  public static function setCompatibilityMode($compatibilityMode)
  {
    if (($compatibilityMode == self::COMPATIBILITY_EXCEL) || ($compatibilityMode == self::COMPATIBILITY_GNUMERIC) || ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE))
    {
      self::$compatibilityMode = $compatibilityMode;
      return true;
    }
    return false;
  }
  
  /**
   * Return the current Compatibility Mode
   *
   * @access public
   * @category Function Configuration
   * @return string Compatibility Mode
   *         Possible Return values are:
   *         PHPExcel_Calculation_Functions::COMPATIBILITY_EXCEL 'Excel'
   *         PHPExcel_Calculation_Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
   *         PHPExcel_Calculation_Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
   */
  public static function getCompatibilityMode()
  {
    return self::$compatibilityMode;
  }
  
  /**
   * Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object)
   *
   * @access public
   * @category Function Configuration
   * @param string $returnDateType
   *          Return Date Format
   *          Permitted values are:
   *          PHPExcel_Calculation_Functions::RETURNDATE_PHP_NUMERIC 'P'
   *          PHPExcel_Calculation_Functions::RETURNDATE_PHP_OBJECT 'O'
   *          PHPExcel_Calculation_Functions::RETURNDATE_EXCEL 'E'
   * @return boolean Success or failure
   */
  public static function setReturnDateType($returnDateType)
  {
    if (($returnDateType == self::RETURNDATE_PHP_NUMERIC) || ($returnDateType == self::RETURNDATE_PHP_OBJECT) || ($returnDateType == self::RETURNDATE_EXCEL))
    {
      self::$returnDateType = $returnDateType;
      return true;
    }
    return false;
  }
  
  /**
   * Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object)
   *
   * @access public
   * @category Function Configuration
   * @return string Return Date Format
   *         Possible Return values are:
   *         PHPExcel_Calculation_Functions::RETURNDATE_PHP_NUMERIC 'P'
   *         PHPExcel_Calculation_Functions::RETURNDATE_PHP_OBJECT 'O'
   *         PHPExcel_Calculation_Functions::RETURNDATE_EXCEL 'E'
   */
  public static function getReturnDateType()
  {
    return self::$returnDateType;
  }
  
  /**
   * DUMMY
   *
   * @access public
   * @category Error Returns
   * @return string #Not Yet Implemented
   */
  public static function DUMMY()
  {
    return '#Not Yet Implemented';
  }
  
  /**
   * DIV0
   *
   * @access public
   * @category Error Returns
   * @return string #Not Yet Implemented
   */
  public static function DIV0()
  {
    return self::$errorCodes ['divisionbyzero'];
  }
  
  /**
   * NA
   *
   * Excel Function:
   * =NA()
   *
   * Returns the error value #N/A
   * #N/A is the error value that means "no value is available."
   *
   * @access public
   * @category Logical Functions
   * @return string #N/A!
   */
  public static function NA()
  {
    return self::$errorCodes ['na'];
  }
  
  /**
   * NaN
   *
   * Returns the error value #NUM!
   *
   * @access public
   * @category Error Returns
   * @return string #NUM!
   */
  public static function NaN()
  {
    return self::$errorCodes ['num'];
  }
  
  /**
   * NAME
   *
   * Returns the error value #NAME?
   *
   * @access public
   * @category Error Returns
   * @return string #NAME?
   */
  public static function NAME()
  {
    return self::$errorCodes ['name'];
  }
  
  /**
   * REF
   *
   * Returns the error value #REF!
   *
   * @access public
   * @category Error Returns
   * @return string #REF!
   */
  public static function REF()
  {
    return self::$errorCodes ['reference'];
  }
  
  /**
   * NULL
   *
   * Returns the error value #NULL!
   *
   * @access public
   * @category Error Returns
   * @return string #NULL!
   */
  public static function NULL()
  {
    return self::$errorCodes ['null'];
  }
  
  /**
   * VALUE
   *
   * Returns the error value #VALUE!
   *
   * @access public
   * @category Error Returns
   * @return string #VALUE!
   */
  public static function VALUE()
  {
    return self::$errorCodes ['value'];
  }
  public static function isMatrixValue($idx)
  {
    return ((substr_count ( $idx, '.' ) <= 1) || (preg_match ( '/\.[A-Z]/', $idx ) > 0));
  }
  public static function isValue($idx)
  {
    return (substr_count ( $idx, '.' ) == 0);
  }
  public static function isCellValue($idx)
  {
    return (substr_count ( $idx, '.' ) > 1);
  }
  public static function ifCondition($condition)
  {
    $condition = PHPExcel_Calculation_Functions::flattenSingleValue ( $condition );
    if (! isset ( $condition {0} ))
    {
      $condition = '=""';
    }
    if (! in_array ( $condition {0}, array (
        '>',
        '<',
        '=' 
    ) ))
    {
      if (! is_numeric ( $condition ))
      {
        $condition = PHPExcel_Calculation::wrapResult ( strtoupper ( $condition ) );
      }
      return '=' . $condition;
    }
    else
    {
      preg_match ( '/([<>=]+)(.*)/', $condition, $matches );
      list ( , $operator, $operand ) = $matches;
      
      if (! is_numeric ( $operand ))
      {
        $operand = str_replace ( '"', '""', $operand );
        $operand = PHPExcel_Calculation::wrapResult ( strtoupper ( $operand ) );
      }
      
      return $operator . $operand;
    }
  }
  
  /**
   * ERROR_TYPE
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function ERROR_TYPE($value = '')
  {
    $value = self::flattenSingleValue ( $value );
    
    $i = 1;
    foreach ( self::$errorCodes as $errorCode )
    {
      if ($value === $errorCode)
      {
        return $i;
      }
      ++ $i;
    }
    return self::NA ();
  }
  
  /**
   * IS_BLANK
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function IS_BLANK($value = null)
  {
    if (! is_null ( $value ))
    {
      $value = self::flattenSingleValue ( $value );
    }
    
    return is_null ( $value );
  }
  
  /**
   * IS_ERR
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function IS_ERR($value = '')
  {
    $value = self::flattenSingleValue ( $value );
    
    return self::IS_ERROR ( $value ) && (! self::IS_NA ( $value ));
  }
  
  /**
   * IS_ERROR
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function IS_ERROR($value = '')
  {
    $value = self::flattenSingleValue ( $value );
    
    if (! is_string ( $value ))
    {
      return false;
    }
    return in_array ( $value, array_values ( self::$errorCodes ) );
  }
  
  /**
   * IS_NA
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function IS_NA($value = '')
  {
    $value = self::flattenSingleValue ( $value );
    
    return ($value === self::NA ());
  }
  
  /**
   * IS_EVEN
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function IS_EVEN($value = null)
  {
    $value = self::flattenSingleValue ( $value );
    
    if ($value === null)
    {
      return self::NAME ();
    }
    elseif ((is_bool ( $value )) || ((is_string ( $value )) && (! is_numeric ( $value ))))
    {
      return self::VALUE ();
    }
    
    return ($value % 2 == 0);
  }
  
  /**
   * IS_ODD
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function IS_ODD($value = null)
  {
    $value = self::flattenSingleValue ( $value );
    
    if ($value === null)
    {
      return self::NAME ();
    }
    elseif ((is_bool ( $value )) || ((is_string ( $value )) && (! is_numeric ( $value ))))
    {
      return self::VALUE ();
    }
    
    return (abs ( $value ) % 2 == 1);
  }
  
  /**
   * IS_NUMBER
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function IS_NUMBER($value = null)
  {
    $value = self::flattenSingleValue ( $value );
    
    if (is_string ( $value ))
    {
      return false;
    }
    return is_numeric ( $value );
  }
  
  /**
   * IS_LOGICAL
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function IS_LOGICAL($value = null)
  {
    $value = self::flattenSingleValue ( $value );
    
    return is_bool ( $value );
  }
  
  /**
   * IS_TEXT
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function IS_TEXT($value = null)
  {
    $value = self::flattenSingleValue ( $value );
    
    return (is_string ( $value ) && ! self::IS_ERROR ( $value ));
  }
  
  /**
   * IS_NONTEXT
   *
   * @param mixed $value
   *          Value to check
   * @return boolean
   */
  public static function IS_NONTEXT($value = null)
  {
    return ! self::IS_TEXT ( $value );
  }
  
  /**
   * VERSION
   *
   * @return string Version information
   */
  public static function VERSION()
  {
    return 'PHPExcel ##VERSION##, ##DATE##';
  }
  
  /**
   * N
   *
   * Returns a value converted to a number
   *
   * @param
   *          value The value you want converted
   * @return number N converts values listed in the following table
   *         If value is or refers to N returns
   *         A number That number
   *         A date The serial number of that date
   *         TRUE 1
   *         FALSE 0
   *         An error value The error value
   *         Anything else 0
   */
  public static function N($value = null)
  {
    while ( is_array ( $value ) )
    {
      $value = array_shift ( $value );
    }
    
    switch (gettype ( $value ))
    {
      case 'double' :
      case 'float' :
      case 'integer' :
        return $value;
        break;
      case 'boolean' :
        return ( integer ) $value;
        break;
      case 'string' :
        // Errors
        if ((strlen ( $value ) > 0) && ($value {0} == '#'))
        {
          return $value;
        }
        break;
    }
    return 0;
  }
  
  /**
   * TYPE
   *
   * Returns a number that identifies the type of a value
   *
   * @param
   *          value The value you want tested
   * @return number N converts values listed in the following table
   *         If value is or refers to N returns
   *         A number 1
   *         Text 2
   *         Logical Value 4
   *         An error value 16
   *         Array or Matrix 64
   */
  public static function TYPE($value = null)
  {
    $value = self::flattenArrayIndexed ( $value );
    if (is_array ( $value ) && (count ( $value ) > 1))
    {
      end ( $value );
      $a = key ( $value );
      // Range of cells is an error
      if (self::isCellValue ( $a ))
      {
        return 16;
        // Test for Matrix
      }
      elseif (self::isMatrixValue ( $a ))
      {
        return 64;
      }
    }
    elseif (empty ( $value ))
    {
      // Empty Cell
      return 1;
    }
    $value = self::flattenSingleValue ( $value );
    
    if (($value === null) || (is_float ( $value )) || (is_int ( $value )))
    {
      return 1;
    }
    elseif (is_bool ( $value ))
    {
      return 4;
    }
    elseif (is_array ( $value ))
    {
      return 64;
    }
    elseif (is_string ( $value ))
    {
      // Errors
      if ((strlen ( $value ) > 0) && ($value {0} == '#'))
      {
        return 16;
      }
      return 2;
    }
    return 0;
  }
  
  /**
   * Convert a multi-dimensional array to a simple 1-dimensional array
   *
   * @param array $array
   *          Array to be flattened
   * @return array Flattened array
   */
  public static function flattenArray($array)
  {
    if (! is_array ( $array ))
    {
      return ( array ) $array;
    }
    
    $arrayValues = array ();
    foreach ( $array as $value )
    {
      if (is_array ( $value ))
      {
        foreach ( $value as $val )
        {
          if (is_array ( $val ))
          {
            foreach ( $val as $v )
            {
              $arrayValues [] = $v;
            }
          }
          else
          {
            $arrayValues [] = $val;
          }
        }
      }
      else
      {
        $arrayValues [] = $value;
      }
    }
    
    return $arrayValues;
  }
  
  /**
   * Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing
   *
   * @param array $array
   *          Array to be flattened
   * @return array Flattened array
   */
  public static function flattenArrayIndexed($array)
  {
    if (! is_array ( $array ))
    {
      return ( array ) $array;
    }
    
    $arrayValues = array ();
    foreach ( $array as $k1 => $value )
    {
      if (is_array ( $value ))
      {
        foreach ( $value as $k2 => $val )
        {
          if (is_array ( $val ))
          {
            foreach ( $val as $k3 => $v )
            {
              $arrayValues [$k1 . '.' . $k2 . '.' . $k3] = $v;
            }
          }
          else
          {
            $arrayValues [$k1 . '.' . $k2] = $val;
          }
        }
      }
      else
      {
        $arrayValues [$k1] = $value;
      }
    }
    
    return $arrayValues;
  }
  
  /**
   * Convert an array to a single scalar value by extracting the first element
   *
   * @param mixed $value
   *          Array or scalar value
   * @return mixed
   */
  public static function flattenSingleValue($value = '')
  {
    while ( is_array ( $value ) )
    {
      $value = array_pop ( $value );
    }
    
    return $value;
  }
}

//
// There are a few mathematical functions that aren't available on all versions of PHP for all platforms
// These functions aren't available in Windows implementations of PHP prior to version 5.3.0
// So we test if they do exist for this version of PHP/operating platform; and if not we create them
//
if (! function_exists ( 'acosh' ))
{
  function acosh($x)
  {
    return 2 * log ( sqrt ( ($x + 1) / 2 ) + sqrt ( ($x - 1) / 2 ) );
  } // function acosh()
}

if (! function_exists ( 'asinh' ))
{
  function asinh($x)
  {
    return log ( $x + sqrt ( 1 + $x * $x ) );
  } // function asinh()
}

if (! function_exists ( 'atanh' ))
{
  function atanh($x)
  {
    return (log ( 1 + $x ) - log ( 1 - $x )) / 2;
  } // function atanh()
}

//
// Strangely, PHP doesn't have a mb_str_replace multibyte function
// As we'll only ever use this function with UTF-8 characters, we can simply "hard-code" the character set
//
if ((! function_exists ( 'mb_str_replace' )) && (function_exists ( 'mb_substr' )) && (function_exists ( 'mb_strlen' )) && (function_exists ( 'mb_strpos' )))
{
  function mb_str_replace($search, $replace, $subject)
  {
    if (is_array ( $subject ))
    {
      $ret = array ();
      foreach ( $subject as $key => $val )
      {
        $ret [$key] = mb_str_replace ( $search, $replace, $val );
      }
      return $ret;
    }
    
    foreach ( ( array ) $search as $key => $s )
    {
      if ($s == '' && $s !== 0)
      {
        continue;
      }
      $r = ! is_array ( $replace ) ? $replace : (array_key_exists ( $key, $replace ) ? $replace [$key] : '');
      $pos = mb_strpos ( $subject, $s, 0, 'UTF-8' );
      while ( $pos !== false )
      {
        $subject = mb_substr ( $subject, 0, $pos, 'UTF-8' ) . $r . mb_substr ( $subject, $pos + mb_strlen ( $s, 'UTF-8' ), 65535, 'UTF-8' );
        $pos = mb_strpos ( $subject, $s, $pos + mb_strlen ( $r, 'UTF-8' ), 'UTF-8' );
      }
    }
    return $subject;
  }
}
