<?php

namespace BootstrapUI\View\Helper;

trait OptionsAwareTrait
{
    /**
     * A list of allowed styles.
     *
     * @var array
     */
    public $classList = [
        'primary',
        'secondary',
        'success',
        'danger',
        'warning',
        'info',
        'light',
        'dark',
        'link',
        'sm',
        'lg'
    ];

    /**
     * A list of all elements to which styles can be applied to.
     *
     * @var array
     */
    public $elementList = [
        'alert',
        'badge',
        'bg',
        'border',
        'btn',
        'btn-outline',
        'list-group-item',
        'input-group'
    ];

    /**
     * Contains the logic for applying style classes for buttons.
     *
     * @param array $data An array of HTML attributes and options.
     * @return array An array of HTML attributes and options.
     */
    public function applyButtonClasses(array $data)
    {
        $allClasses = $this->genAllClassNames('btn');

        if ($this->hasAnyClass($allClasses, $data)) {
            $data = $this->injectClasses('btn', $data);
        } else {
            $data = $this->injectClasses(['btn', 'secondary'], $data);
        }

        return $this->renameClasses('btn', $data);
    }

    /**
     * Renames any CSS classes found in the options.
     *
     * @param string $element UI element to which the classname is applied.
     * @param array $options An array of HTML attributes and options.
     * @return array An array of HTML attributes and options.
     */
    public function renameClasses($element, array $options)
    {
        $options += ['class' => []];

        $options['class'] = $this->_toClassArray($options['class']);
        $classes = [];
        foreach ($options['class'] as $name) {
            $classes[] = in_array($name, $this->classList)
                ? $this->genClassName($element, $name)
                : $name;
        }
        $options['class'] = trim(implode(' ', $classes));

        return $options;
    }

    /**
     * Checks if `$options['class']` contains any one of the class names.
     *
     * @param array|string $classes Name of class(es) to check.
     * @param array $options An array of HTML attributes and options.
     * @return bool True if any one of the class names was found.
     */
    public function hasAnyClass($classes, array $options)
    {
        $options += ['class' => []];

        $options['class'] = $this->_toClassArray($options['class']);
        $classes = $this->_toClassArray($classes);

        foreach ($classes as $class) {
            if (in_array($class, $options['class'])) {
                return true;
            }
        }

        return false;
    }

    /**
     * Injects classes into `$options['class']` when they don't already exist. If a class is defined
     * in `$options['skip']` then it will not be injected. This method removes `$options['skip']` before
     * returning.
     *
     * @param array|string $classes Name of class(es) to inject.
     * @param array $options An array of HTML attributes and options.
     * @return array An array of HTML attributes and options.
     */
    public function injectClasses($classes, array $options)
    {
        $options += ['class' => [], 'skip' => []];

        $options['class'] = $this->_toClassArray($options['class']);
        $options['skip'] = $this->_toClassArray($options['skip']);
        $classes = $this->_toClassArray($classes);

        foreach ($classes as $class) {
            if (!in_array($class, $options['class']) && !in_array($class, $options['skip'])) {
                array_push($options['class'], $class);
            }
        }

        unset($options['skip']);
        $options['class'] = trim(implode(' ', $options['class']));

        return $options;
    }

    /**
     * Removes classes from `$options['class']`.
     *
     * @param array|string $classes Name of class(es) to remove.
     * @param array $options An array of HTML attributes and options.
     * @return array An array of HTML attributes and options.
     */
    public function removeClasses($classes, array $options)
    {
        $options += ['class' => []];

        $options['class'] = $this->_toClassArray($options['class']);
        $classes = $this->_toClassArray($classes);

        foreach ($classes as $class) {
            $indices = array_keys($options['class'], $class);
            foreach ($indices as $index) {
                if ($index !== false) {
                    unset($options['class'][$index]);
                }
            }
        }

        $options['class'] = trim(implode(' ', $options['class']));

        return $options;
    }

    /**
     * Checks if `$classes` are part of the `$options['class']`.
     *
     * @param array|string $classes Name of class(es) to check.
     * @param array $options An array of HTML attributes and options.
     * @return bool False if one or more class(es) do not exist.
     */
    public function checkClasses($classes, array $options)
    {
        if (empty($options['class'])) {
            return false;
        }

        $options['class'] = $this->_toClassArray($options['class']);
        $classes = $this->_toClassArray($classes);

        foreach ($classes as $class) {
            if (!in_array($class, $options['class'])) {
                return false;
            }
        }

        return true;
    }

    /**
     * Normalizes class strings/arrays.
     *
     * @param mixed $mixed One or more classes.
     * @return array Classes as array.
     */
    protected function _toClassArray($mixed)
    {
        if (!is_array($mixed)) {
            $mixed = explode(' ', $mixed);
        }

        return $mixed;
    }

    /**
     * Generates the classname of the given element
     *
     * @param string $element UI element to which the class can be applied (e.g. btn).
     * @param string $class CSS class, which can be applied to the element.
     * @return bool|string String of generated class, false if element/class not in list.
     */
    public function genClassName($element, $class)
    {
        if (!in_array($element, $this->elementList)) {
            return false;
        }

        if (!in_array($class, $this->classList)) {
            return false;
        }

        return $element . '-' . $class;
    }

    /**
     * Generates a list of all classnames of a element
     *
     * @param string $element UI element
     * @return array Array of all generated and raw styles
     */
    public function genAllClassNames($element)
    {
        $classes = [];
        foreach ($this->classList as $class) {
            $classes[] = $this->genClassName($element, $class);
        }

        return array_merge($this->classList, $classes);
    }
}
