<?php

namespace Snaju\Inception\Util\Loaders;

use Snaju\Inception\Exception\Exception;
use Snaju\Inception\Exception\InvalidCallException;

class Collection
{

    private array $items = [];

    public ?string $name;

    public bool $allowOverwite = false;

    public bool $readOnly = false;

    public bool $throwOverwriteExceptions = false;

    public bool $caseSensitive = false;

    public function __construct(
        array $data = [],
        ?string $name = null,
        bool $overwrite = false,
        bool $readOnly = false,
        bool $throwOverwriteExceptions = false,
        bool $caseSensitive = false
    )
    {
        $this->name = $name;
        $this->allowOverwite = $overwrite;
        $this->readOnly = $readOnly;
        $this->throwOverwriteExceptions = $throwOverwriteExceptions;
        $this->caseSensitive = $caseSensitive;

        if (count($data) > 0) {
            $this->addFromArray($data);
        }
    }

    public function mergeCollection(Collection $collection)
    {
        $this->items = array_merge($this->items, $collection->toArray());
    }

    public function mergeArray(array $a)
    {
        $this->items = array_merge($this->items, $a);
    }

    public function toArray(): array
    {
        return (array)$this->items;
    }

    public function length(): int
    {
        return count($this->items);
    }

    /**
     * Returns true if a item with the specified key exists.
     * @param $key
     * @return bool
     */
    public function keyExists($key): bool
    {
        // If case sensitive, just do normal lookup with isset
        if ($this->caseSensitive) {
            return (bool)isset($this->items[$key]);
        } else {
            // else loop through each key while converting them to lowercase to see if it exists
            foreach ($this->items as $item => $value) {
                if (strtolower($item) === strtolower($key)) {
                    return true;
                }
            }
        }

        return false;
    }

    public function keys(): array
    {
        return array_keys($this->items);
    }

    public function get($key)
    {
        $item = null;

        if ($this->keyExists($key)) {
            // Attempts to retrieve a value by its key as-is. Tries lowercase
            // version if that fails. Lastly returns null if none found.
            if (isset($this->items[$key])) {
                $item = $this->items[$key];
            } else if (isset($this->items[strtolower($key)])) {
                $item = $this->items[strtolower($key)];
            }
        }

        return $item;
    }

    public function getOrSet($key, $value): array
    {
        $item = $this->get($key);
        $response = [
            'created' => $item === null,
            'item' => $item,
        ];

        if ($item === null) {
            $this->add($key, $value);
            $response['item'] = $value;
        }
        return $response;
    }

    public function add($key, $value): Collection
    {
        if ($this->readOnly) {
            throw new InvalidCallException("Collection $this->name is read only.");
        }

        if ($this->keyExists($key) && $this->throwOverwriteExceptions) {
            throw new InvalidCallException('Collection already has key and overwrite is not set to throw.');
        } else if ($this->keyExists($key) && $this->throwOverwriteExceptions === false) {
            return $this;
        } else {
            $this->items[$key] = $value;
        }
        return $this;
    }

    public function addFromArray(array $array): Collection
    {
        foreach ($array as $key => $value) {
            $this->add($key, $value);
        }
        return $this;
    }

    public function remove(string $key): Collection
    {
        if ($this->readOnly) {
            throw new InvalidCallException("Collection $this->name is read only.");
        }

        if ($this->keyExists($key)) {
            unset($this->items[$key]);
        }

        return $this;
    }

    public function containsKey($key): bool
    {
        foreach ($this->items as $k => $v) {
            if (strtolower($k) == strtolower($key)) {
                return true;
            }
        }

        return false;
    }

}