<?php


namespace App\Utils\Collections;

//use Faker\Test\Provider\Collection;
use InvalidArgumentException;

/**
 * Class GeneralCollection
 * @package App\Utils\Collections
 */
class GeneralCollection
{


    /**
     * @var array
     */
    protected $memory = [];
    /**
     * @var int
     */
    public $length = 0;

    /**
     * GeneralCollection constructor.
     * @param array $memory
     */
    public function __construct(array $memory = [])
    {
        $length = count($memory);
        $j = 0;
        $i = 0;
        while ($j < $length) {
            if (isset($memory[$j])) {
                $this->memory[$i] = $memory[$j];
                $i++;
            }
            $j++;
        }
        $this->length = count($this->memory);
    }


    /**
     * @param callable $callback($value, $id, $length)
     */
    final public function map(callable $callback, bool $internal_change = false) : GeneralCollection {
        if ($internal_change) {
            for ($i = 0; $i < $this->length; $i++) {
                $this->memory[$i] = $callback($this->memory[$i], $i, $this->length);
            }
            return $this;
        } else {
            $memory_temp = [];
            for ($i = 0; $i < $this->length; $i++) {
                $memory_temp[$i] = $callback($this->memory[$i], $i, $this->length);
            }
            return new GeneralCollection($memory_temp);
        }
    }

    /**
     * @param callable $callback($value, $id)
     * @return bool
     */
    final public function every (callable $callback) : bool {
        for ($i = 0; $i < $this->length; $i++) {
            if (!$callback($this->memory[$i], $i)) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param callable $callback($value, $id)
     * @return bool
     */
    final public function filter (callable $callback, bool $internal_change = false) : GeneralCollection {
        if ($internal_change) {
            for ($i = 0; $i < $this->length; $i++) {
                if (!$callback($this->memory[$i], $i)) {
                    $this->removeElement($this->memory[$i]);
                    $this->length--;
                }
            }
            return $this;
        } else {
            $memory_temp = [];
            for ($i = 0; $i < $this->length; $i++) {
                if ($callback($this->memory[$i], $i)) {
                    $memory_temp[] = $this->memory[$i];
                }
            }
            return new GeneralCollection($memory_temp);
        }
    }

    private function removeElement (int $index) {
        for ($i = $index; $i < $this->length - 1; $i++) {
            $this->memory[$i] = $this->memory[$i+1];
        }
        $this->length--;
    }

    /**
     * @param callable $callback($value, $id)
     */
    final public function forEach (callable $callback) : void {
        for ($i = 0; $i < $this->length; $i++) {
            $callback($this->memory[$i], $i);
        }
    }

    /**
     * @param $element
     * @return bool
     */
    final public function includes ($element) : bool {
        for ($i = 0; $i < $this->length; $i++) {
            if ($this->memory[$i] === $element) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param callable $callback($value, $id)
     * @return bool
     */
    final public function some (callable $callback) : bool {
        for ($i = 0; $i < $this->length; $i++) {
            if ($callback($this->memory[$i], $i)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param int $index
     * @return bool
     */
    final public function remove (int $index) : bool {
        if (isset($this->memory[$index])) {
            $this->removeElement($index);
            return true;
        } else {
            return false;
        }
    }

    /**
     * @return bool
     */
    final public function revert () : bool {
        $reverse_array = [];
        for ($i = 0; $i < $this->length; $i++) {
            $reverse_array[$i] = $this->memory[$this->length - $i];
        }
        $this->memory = $reverse_array;
    }


    public function push($element): void
    {
        array_push($this->memory, $element);
        $this->length++;
    }

    public function unshift($element) : void
    {
        array_unshift($this->memory, $element);
        $this->length++;
    }

    public function get(int $index)
    {
        return $this->memory[$index];
    }

    public function pop()
    {
        if ($this->length > 0) {
            $this->length--;
            return array_pop($this->memory);
        } else {
            return null;
        }
    }

    public function shift()
    {
        if ($this->length > 0) {
            $this->length--;
            return array_shift($this->memory);
        } else {
            return null;
        }
    }

    public function find(callable $callback)
    {
        for ($i = 0; $i < $this->length; $i++) {
            if ($callback($this->memory[$i], $i)) {
                return $this->memory[$i];
            }
        }
        return null;
    }

    public function keys () : GeneralCollection {
        $keys = array_keys($this->memory);
        return new GeneralCollection($keys);
    }

    public function join (string $separator = '') : string {
        $response = '';
        $allowed_types = new GeneralCollection(['integer', 'float', 'string', 'boolean']);
        $this->forEach(function ($element, int $i) use (&$response, &$separator, &$allowed_types) {
            if ($allowed_types->includes(gettype($element))) {
                $response .= $i > 0 ? "$separator$element" : $element;
            }
        });
        return $response;
    }

    public function toArray (bool $is_absolute = false) : array {
        $array = [];
        for ($i = 0; $i < $this->length; $i++) {
            $element = $this->get($i);
            if  (is_object($element) && class_parents($element)['App\Utils\Entity']) {
                array_push($array, $element->toArray($is_absolute));
            } else {
                array_push($array, $element);
            }
        }
        return $array;
    }

    public function fill (int $lenght, callable $callback) : GeneralCollection {
        $collection = new GeneralCollection();
        for ($i = $this->length; $i < $lenght + $this->length; $i++) {
            $collection->push($callback($i));
        }
        return $collection;
    }

    public function removeDuplicates (bool $internal_change = false) : GeneralCollection {
        $unique_array = array_unique($this->memory);
        for ($i = 0, $j = 0; $i < $this->length; $i++) {
            if (isset($unique_array[$i])) {
                $unique_array[$j++] = $unique_array[$i];
            }
        }
        if ($internal_change) {
            $this->memory = $unique_array;
            $this->length = $j;
            return $this;
        } else {
            return new GeneralCollection($unique_array);
        }
    }
}