<?php
/**
 * @copyright (c) Kufer Software GmbH, Loisachstrasse 4, D-84453 Mühldorf
 */

class ArrayOperations
{
  
  public static function mapAttribut(array $pArray, string $pAttributname): array
  {
    return array_map(function($pItem) use ($pAttributname) {
      return $pItem->{$pAttributname};
    }, $pArray);
  }

  
  public static function mapUniqueAttribute(array $pArray, string $pAttributname): array
  {
    $result = array();
    foreach ($pArray as $element) {
      $result[$element->{$pAttributname}] = true;
    }

    return array_keys($result);
  }

  
  public static function setAttributAsKey(array $pArray, string $pAttributname): array
  {
    $result = array();
    foreach ($pArray as $item) {
      $result[$item->{$pAttributname}] = $item;
    }

    return $result;
  }

  
  public static function setAttributAsKeyArray(array $pArray, string $pAttributname): array
  {
    return self::group($pArray, function($item) use ($pAttributname) {
      return $item->{$pAttributname};
    });
  }

  
  public static function group(array $pArray, callable $pGetKey): array
  {
    $result = array();
    foreach ($pArray as $item) {
      $key = call_user_func($pGetKey, $item);
      if (!isset($result[$key])) {
        $result[$key] = array();
      }
      $result[$key][] = $item;
    }

    return $result;
  }

  
  public static function in_array_r($needle, array $haystack, bool $strict = false): bool
  {
    foreach ($haystack as $item) {
      if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && self::in_array_r($needle, $item, $strict))) {
        return true;
      }
    }

    return false;
  }

  
  public static function array_xor(array $array): bool
  {
    if (!is_array($array) || count($array) === 0) {
      return false;
    }

    $count = 0;
    foreach ($array as $value) {
      if ($value) {
        $count++;
        if ($count >= 2) {
          return false;
        }
      }
    }

    return $count === 1;
  }

  
  public static function checkDatamodulValues(array $pArray, string $pVarname, $pValue): bool
  {
    foreach ($pArray as $dm) {
      if ($dm->getVarValue($pVarname) !== $pValue) {
        return false;
      }
    }

    return true;
  }

  
  public static function getIntValues(array $pArray): array
  {
    $result = array();
    foreach ($pArray as $item) {
      if (CleanOperations::isInt($item)) {
        $result[] = CleanOperations::cleanInt($item);
      }
    }

    return $result;
  }

  public static function doesIntersect(array $pArray1, array $pArray2): bool
  {
    return count(array_intersect($pArray1, $pArray2)) > 0;
  }

  
  public static function usearch($needle, array $haystack, callable $compareFunction)
  {
    foreach ($haystack as $key => $val) {
      if (call_user_func($compareFunction, $needle, $val)) {
        return $key;
      }
    }

    return false;
  }

  public static function in_array_u($needle, array $haystack, callable $compareFunction): bool
  {
    return self::usearch($needle, $haystack, $compareFunction) !== false;
  }

  
  public static function sortObjectsByValueArray(array $objectAry, array $attributAry, string $attributName): array
  {
    $objectAry = \ArrayOperations::setAttributAsKey($objectAry, $attributName);

    return array_map(function($id) use ($objectAry) {
      return $objectAry[$id];
    }, $attributAry);
  }

  public static function tail(array $array): array
  {
    return array_slice($array, 1);
  }

  public static function head(array $array)
  {
    return $array[0];
  }

  
  public static function any(array $array, callable $predicate): bool
  {
    foreach ($array as $element) {
      if (call_user_func($predicate, $element)) {
        return true;
      }
    }

    return false;
  }

  
  public static function all(array $array, callable $predicate): bool
  {
    if (empty($array)) {
      return false;
    }

    foreach ($array as $element) {
      if (!call_user_func($predicate, $element)) {
        return false;
      }
    }

    return true;
  }

  
  public static function flatten(array $array): array
  {
    $result = array();
    array_walk_recursive($array, function($element, $key) use (&$result) { $result[] = $element; });

    return $result;
  }

  
  public static function flattenPreserveKeys(array $array): array
  {
    $result = array();
    array_walk_recursive($array, function($element, $key) use (&$result) { $result[$key] = $element; });

    return $result;
  }

  
  public static function filterPreserveKeys(array $array, callable $function, int $mode = 0): array
  {
    return array_filter($array, $function, $mode);
  }

  
  public static function filterReassignKeys(array $array, callable $function, int $mode = 0): array
  {
    return array_values(array_filter($array, $function, $mode));
  }

  
  public static function mapRecursive(callable $function, array $array): array
  {
    return array_map(function($value) use ($function) {
      if (is_array($value)) {
        return self::mapRecursive($function, $value);
      } else {
        return call_user_func($function, $value);
      }
    }, $array);
  }

  
  public static function mapArrayAndObjectRecursive(callable $function, $traversable)
  {
    if (is_object($traversable)) {
      $result = clone($traversable);
      foreach ($traversable as $key => $value) {
        $result->{$key} = self::mapArrayAndObjectRecursive($function, $value);
      }

      return $result;
    } elseif (is_array($traversable)) {
      return array_map(function($value) use ($function) {
        return self::mapArrayAndObjectRecursive($function, $value);
      }, $traversable);
    } else {
      return call_user_func($function, $traversable);
    }
  }

  
  public static function find(callable $function, array $array)
  {
    foreach ($array as $key => $value) {
      if (call_user_func($function, $value)) {
        return $key;
      }
    }

    return null;
  }

  
  public static function keyFirst(array $arr)
  {
    if (function_exists('array_key_first')) {
      return array_key_first($arr);
    } else {
      return self::array_key_first($arr);
    }
  }

  private static function array_key_first(array $arr)
  {
    foreach ($arr as $key => $unused) {
      return $key;
    }

    return null;
  }

  
  public static function firstValue(array $ary)
  {
    return \reset($ary);
  }

  
  public static function lastValue(array $ary)
  {
    return \end($ary);
  }

  
  public static function findByAttr(array $array, string $attr, $value)
  {
    foreach ($array as $item) {
      if (isset($item->{$attr}) && $item->{$attr} === $value) {
        return $item;
      }
    }

    return null;
  }

  
  public static function replace(array $array, $old, $new): array
  {
    return array_map(function($value) use ($old, $new) {
      return $value == $old ? $new : $value;
    }, $array);
  }

  
  public static function setMultidimKeys(array &$array, array $keys): void
  {
    if (empty($keys)) {
      throw new InvalidArgumentException('Übergebener Parameter $keys muss mindestens ein Element enthalten');
    }

    $current = &$array;
    foreach ($keys as $key) {
      if (!isset($current[$key])) {
        $current[$key] = array();
      }
      $current = &$current[$key];
    }
  }

  
  public static function xrange($start, $end, $step = 1): iterable
  {
    $step = abs($step);
    if ($start > $end) {
      for ($i = $start; $i >= $end; $i -= $step) {
        yield $i;
      }
    } else {
      for ($i = $start; $i <= $end; $i += $step) {
        yield $i;
      }
    }
  }

  
  public static function uniqueByKey(iterable $input, string $attr): array
  {
    $result = array();
    
    $keyAry = array();

    foreach ($input as $element) {
      $key = (string)$element->{$attr};
      
      if (!isset($keyAry[$key])) {
        $keyAry[$key] = true;
        $result[] = $element;
      }
    }

    return $result;
  }

  
  public static function usortBool(array $array, callable $function): array
  {
    $functionInt = function($a, $b) use ($function) {
      $isBigger = call_user_func($function, $a, $b);

      return ($isBigger) ? '1' : '-1';
    };
    usort($array, $functionInt);

    return $array;
  }

  
  public static function addIntOrAssign(array $array, string $key, int $value): array
  {
    if (isset($array[$key])) {
      $array[$key] += $value;
    } else {
      $array[$key] = $value;
    }

    return $array;
  }

  
  public static function reduceWithIntermediateResults(array $array, callable $function, $initial): array
  {
    $zwischenergebnis = $initial;
    $result = array($zwischenergebnis);
    foreach ($array as $value) {
      $zwischenergebnis = call_user_func($function, $zwischenergebnis, $value);
      $result[] = $zwischenergebnis;
    }

    return $result;
  }
}