<?php

namespace LdapRecord\Models;

use Closure;
use LdapRecord\Models\Attributes\DistinguishedName;
use LdapRecord\Query\Collection as QueryCollection;
use LdapRecord\Support\Arr;

class Collection extends QueryCollection
{
    /**
     * Get a collection of the model's distinguished names.
     *
     * @return static
     */
    public function modelDns()
    {
        return $this->map(function (Model $model) {
            return $model->getDn();
        });
    }

    /**
     * Determine if the collection contains all of the given models, or any models.
     *
     * @param  mixed  $models
     * @return bool
     */
    public function exists($models = null)
    {
        $models = $this->getArrayableModels($models);

        // If any arguments were given and the result set is
        // empty, we can simply return false here. We can't
        // verify the existence of models without results.
        if (func_num_args() > 0 && empty(array_filter($models))) {
            return false;
        }

        if (! $models) {
            return parent::isNotEmpty();
        }

        foreach ($models as $model) {
            $exists = parent::contains(function (Model $related) use ($model) {
                return $this->compareModelWithRelated($model, $related);
            });

            if (! $exists) {
                return false;
            }
        }

        return true;
    }

    /**
     * Determine if any of the given models are contained in the collection.
     *
     * @param  mixed  $key
     * @param  mixed  $operator
     * @param  mixed  $value
     * @return bool
     */
    public function contains($key, $operator = null, $value = null)
    {
        if (func_num_args() > 1 || $key instanceof Closure) {
            // If we are supplied with more than one argument, or
            // we were passed a closure, we will utilize the
            // parents contains method, for compatibility.
            return parent::contains($key, $operator, $value);
        }

        foreach ($this->getArrayableModels($key) as $model) {
            $exists = parent::contains(function (Model $related) use ($model) {
                return $this->compareModelWithRelated($model, $related);
            });

            if ($exists) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get the provided models as an array.
     *
     * @param  mixed  $models
     * @return array
     */
    protected function getArrayableModels($models = null)
    {
        return $models instanceof QueryCollection
            ? $models->toArray()
            : Arr::wrap($models);
    }

    /**
     * Compare the related model with the given.
     *
     * @param  Model|string  $model
     * @param  Model  $related
     * @return bool
     */
    protected function compareModelWithRelated($model, $related)
    {
        if (is_string($model)) {
            return $this->isValidDn($model)
                ? strcasecmp($related->getDn(), $model) === 0
                : strcasecmp($related->getName(), $model) === 0;
        }

        return $related->is($model);
    }

    /**
     * Determine if the given string is a valid distinguished name.
     *
     * @param  string  $dn
     * @return bool
     */
    protected function isValidDn($dn)
    {
        return ! empty((new DistinguishedName($dn))->components());
    }
}
