<?php

namespace EesyPHP;

use League\MimeTypeDetection\ExtensionMimeTypeDetector;

/*
 * Parser/formater values helpers
 */
function format_size($size, $digit=False) {
  if (!$digit && $digit!==0) $digit=2;
  if ($size>=1099511627776)
    return number_format($size/1099511627776,$digit)."To";
  elseif ($size>=1073741824)
    return number_format($size/1073741824,$digit)."Go";
  else if ($size>=1048576)
    return number_format($size/1048576,$digit)."Mo";
  else if ($size>=1024)
    return number_format($size/1024,$digit)."Ko";
  else
    return $size."o";
}

/*
 * Generic Data/value helpers
 */
function vardump($data) {
  ob_start();
  var_dump($data);
  $data = ob_get_contents();
  ob_end_clean();
  return $data;
}

/**
 * Format a callable object for logging
 * @param  string|array $callable The callable object
 * @return string The callable object string representation
 */
function format_callable($callable) {
  if (is_string($callable))
    return $callable."()";
  if (is_array($callable))
    if (is_string($callable[0]))
      return $callable[0]."::".$callable[1]."()";
    elseif (is_object($callable[0]))
      return get_class($callable[0])."->".$callable[1]."()";
    else
      return "Unkown->".$callable[1]."()";
  // @phpstan-ignore-next-line
  return vardump($callable);
}

/**
 * Check if given value is empty
 * @param mixed $value
 * @return bool
 */
function check_is_empty($value) {
  switch(gettype($value)) {
    case "boolean":
    case "integer":
    case "double":
    case "object":
    case "resource":
      return False;
    case "array":
    case "string":
      if ($value == "0") return false;
      return empty($value);
    case "NULL":
      return True;
  }
  return empty($value);
}

/**
 * Ensure the given value is an array and return an array with this value if not
 * @param mixed $value
 * @return array
 */
function ensure_is_array($value) {
    if (is_array($value))
        return $value;
    if (check_is_empty($value))
        return array();
    return array($value);
}

/**
 * Get a specific configuration variable value
 *
 * @param mixed $value The value to cast
 * @param string $type The type of expected value. The configuration variable
 *                     value will be cast as this type. Could be : bool, int,
 *                     float or string.
 * @param bool $split If true, $type=='array' and $value is a string, split
 *                    the value by comma (optional, default: false)
 * @return mixed The cast value
 **/
function cast($value, $type, $split=false) {
    if (strpos($type, 'array_of_') === 0) {
      $type = substr($type, 9);
      $values = array();
      foreach(ensure_is_array($value) as $key => $value)
        $values[$key] = cast($value, $type);
      return $values;
    }
    switch($type) {
        case 'bool':
        case 'boolean':
            return boolval($value);
        case 'int':
        case 'integer':
            return intval($value);
        case 'float':
            return floatval($value);
        case 'str':
        case 'string':
            return strval($value);
        case 'array':
            if ($split && is_string($value))
              $value = preg_split('/ *, */', $value);
            return ensure_is_array($value);
    }
    return $value;
}

/*
 * Generic file/directory helpers
 */

/**
 * Dump file content to propose its download
 * @param string $file_path The file path
 * @param string|null $mime_type The file MIME type (optional, default: auto-detected)
 * @param int|null $max_age Max age in second of this file in browser cache (optional,
 *                 default: null=1h, set to False to disable Cache-Control header)
 * @return never
 */
function dump_file($file_path, $mime_type=null, $max_age=null) {
  if (is_file($file_path)) {
    if (is_null($mime_type)) {
      $detector = new ExtensionMimeTypeDetector();
      $mime_type = $detector->detectMimeTypeFromFile($file_path);
      Log::trace('MIME type detected for "%s" is "%s"', $file_path, $mime_type);
    }
    header("Content-Type: $mime_type");
    $last_modified_time = filemtime($file_path);
    $etag = md5_file($file_path);
    $max_age = is_null($max_age)?3600:$max_age;
    if ($max_age)
      header("Cache-Control: max-age=$max_age, must-revalidate");
    header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT");
    header("Etag: $etag");

    if (
      (
        isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
        @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time
      ) || (
        isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
        trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag
      )
    ) {
      header("HTTP/1.1 304 Not Modified");
      exit();
    }

    header('Pragma: public');
    header('Content-Length: ' . filesize($file_path));
    readfile($file_path);
    exit();
  }
  Url :: trigger_error_404();
}

function delete_directory($dir, $recursive=true) {
  $files = array_diff(scandir($dir), array('.','..'));
  if ($recursive) {
    foreach ($files as $file) {
      if (is_dir("$dir/$file")) {
        if (!delete_directory("$dir/$file", true)) {
          Log :: error("delete_directory($dir) : Fail to delete sub-directory '$dir/$file'.");
          return false;
        }
      }
      else if (!unlink("$dir/$file")) {
        Log :: error("delete_directory($dir) : Fail to delete '$dir/$file'.");
        return false;
      }
    }
  }
  else if (!empty($files)) {
    Log :: error("delete_directory($dir) : Directory is not empty.");
    return false;
  }
  return rmdir($dir);
}

/*
 * Run external command helper
 */
/**
 * Run external command
 *
 * @param $command string|array        The command. It's could be an array of the command with its
 *                                     arguments.
 * @param $data_stdin string|null      The command arguments (optional, default: null)
 * @param $escape_command_args boolean If true, the command will be escaped
 *                                     (optional, default: true)
 * @param string|null $cwd             The initial working dir for the command
 *                                     (optional, default: null = use current PHP process working
 *                                     directory)
 *
 * @return false|array An array of return code, stdout and stderr result or False in case of fatal
 *                     error
 **/
function run_external_command($command, $data_stdin=null, $escape_command_args=true, $cwd=null) {
  if (is_array($command))
    $command = implode(' ', $command);
  if ($escape_command_args)
    $command = escapeshellcmd($command);
  Log :: debug("Run external command: '$command'");
  $descriptorspec = array(
    0 => array("pipe", "r"),  // stdin
    1 => array("pipe", "w"),  // stdout
    2 => array("pipe", "w"),  // stderr
  );
  $process = proc_open($command, $descriptorspec, $pipes, $cwd);

  if (!is_resource($process)) {
    Log :: error("Fail to run external command: '$command'");
    return false;
  }

  if (!is_null($data_stdin)) {
    fwrite($pipes[0], $data_stdin);
  }
  fclose($pipes[0]);

  $stdout = stream_get_contents($pipes[1]);
  fclose($pipes[1]);

  $stderr = stream_get_contents($pipes[2]);
  fclose($pipes[2]);

  $return_value = proc_close($process);

  $error = (!empty($stderr) || $return_value != 0);
  Log :: log(
    ($error?'ERROR':'DEBUG'),
    "External command ".($error?"error":"result").":\n".
    "\tCommand : $command\n".
    "\tReturn code: $return_value\n".
    "\tOutput:\n".
    "\t\t- Stdout :\n$stdout\n\n".
    "\t\t- Stderr :\n$stderr"
  );

  return array($return_value, $stdout, $stderr);
}


/**
 * Check an AJAX request and trigger a fatal error on fail
 *
 * Check if session key is present and valid and set AJAX
 * mode.
 *
 * @param string|null $session_key string The current session key (optional)
 *
 * @return void
 **/
function check_ajax_request($session_key=null) {
  Url :: api_mode(true);

  if (Session :: check_key($session_key))
    Tpl :: fatal_error('Invalid request');

  if (Tpl :: debug_ajax())
    Log :: debug("Ajax Request : ".vardump($_REQUEST));
}

/**
 * Just mark message for translation
 * @param  string $msg  The message to translate
 * @return string The message without transformation
 */
function ___($msg) {
  return $msg;
}

/**
 * Format duration
 * @param int|float $value The duration to format
 * @param string|null $unit The unit of provide value (optional, default: 's')
 * @param string|null $precision Minimal precision to displayed (optional, default: 's')
 * @param string|null $separator The separator between time block (optional, default: null = one space)
 * @return string|false The formated duration as string, or false in case of error
 */
function format_duration($value, $unit=null, $precision=null, $separator=null) {
  $units = array(
    ___('d') => 86400000000000,
    ___('h') => 3600000000000,
    ___('m') => 60000000000,
    ___('s') => 1000000000,
    ___('ms') => 1000000,
    ___('ns') => 1,
  );
  if (!$unit) $unit = 's';
  if (!array_key_exists($unit, $units)) return false;
  if (!$precision) $precision = 's';
  if (!array_key_exists($precision, $units)) return false;
  if ($value == 0) return "0".I18n :: _($precision);
  $value = $value * $units[$unit];
  $result = array();
  foreach ($units as $unit => $factor) {
    if ($value >= $factor) {
      $vunit = floor($value/$factor);
      $value = $value - ($vunit * $factor);
      $result[] = $vunit._($unit);
    }
    if ($unit == $precision) break;
  }
  // 0 ?
  if (empty($result)) return I18n :: _('Less than 1%s', I18n :: _($precision));
  return implode(is_null($separator)?' ':strval($separator), $result);
}

/**
 * Implode array's keys & values (ex: 'k1=v1, k2=v2, ...')
 * @param array<string|int,mixed>  $values        Array to implode
 * @param boolean $quoted        Set to false to disable values quotting (optional, default: true)
 * @param string  $separator     Values separator (opional, default: ", ")
 * @param string  $kv_separator  Key/value separator (optional, default: "=")
 * @return string Imploded array string
 */
function implode_with_keys($values, $quoted=true, $separator=', ', $kv_separator='=') {
  $result = [];
  $quoted = $quoted?'"':'';
  foreach ($values as $key => $value)
    $result[] = "$key$kv_separator$quoted$value$quoted";
  return implode($separator, $result);
}

# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab
