<?php

namespace Bootstrapper;

use Bootstrapper\Exceptions\ControlGroupException;

/**
 * Creates Bootstrap 3 compliant control groups (for forms)
 *
 * @package Bootstrapper
 */
class ControlGroup extends RenderedObject
{

    /**
     * @var array The attributes for control group
     */
    protected $attributes = [];

    /**
     * @var array The contents of the control groups
     */
    protected $contents = [];

    /**
     * @var string The size of the control group
     */
    protected $controlSize;

    /**
     * @var string The label of control group
     */
    protected $label;

    /**
     * @var string The size of the label
     */
    protected $labelSize;

    /**
     * @var string The help text for the input
     */
    protected $help;

    /**
     * @var Form Laravel's form builder
     */
    protected $formBuilder;

    /**
     * @param Form $formBuilder An instance of the Bootstrapper form builder
     */
    public function __construct(Form $formBuilder)
    {
        $this->formBuilder = $formBuilder;
    }

    /**
     * Renders the control group
     *
     * @return string
     */
    public function render()
    {
        $attributes = new Attributes(
            $this->attributes,
            ['class' => 'form-group']
        );
        $string = "<div {$attributes}>";

        if ($this->label) {
            $string .= $this->renderLabel();
        }

        if ($this->controlSize) {
            $string .= $this->createControlDiv();
        }

        if (is_array($this->contents)) {
            $string .= $this->renderArrayContents();
        } else {
            $string .= $this->contents;
        }

        $string .= $this->help;

        if ($this->controlSize) {
            $string .= "</div>";
        }

        $string .= "</div>";

        return $string;
    }

    /**
     * Set the attributes of the control group
     *
     * @param array $attributes The attributes array
     * @return $this
     */
    public function withAttributes(array $attributes)
    {
        $this->attributes = $attributes;

        return $this;
    }

    /**
     * Adds the contents to the control group
     *
     * @param string $contents    The contents of the control group
     * @param null   $controlSize |int The size of the form control
     * @return $this
     * @throws ControlGroupException If is $controlSize set and not between 1
     *                            and 12
     */
    public function withContents($contents, $controlSize = null)
    {
        if (isset($controlSize) && $this->sizeIsInvalid($controlSize)) {
            throw new ControlGroupException(
                'That content size is incorrect - it must be between 1 and 12'
            );
        }

        $this->contents = $contents;
        $this->controlSize = $controlSize;

        return $this;
    }

    /**
     * Sets the label of the control group
     *
     * @param string $label     The label
     * @param null   $labelSize |int The size of the label
     * @return $this
     * @throws ControlGroupException If is $labelSize set and not between 1
     *                          and 12
     */
    public function withLabel($label, $labelSize = null)
    {
        if (isset($labelSize) && $this->sizeIsInvalid($labelSize)) {
            throw new ControlGroupException(
                'That label size is incorrect - it must be between 1 and 12'
            );
        }

        $this->label = $label;
        $this->labelSize = $labelSize;

        return $this;
    }

    /**
     * Adds a help block
     *
     * @param string $help The help information
     * @return $this
     */
    public function withHelp($help)
    {
        $this->help = $help;

        return $this;
    }

    /**
     * Generates a full control group with a label, control and help block
     *
     * @param string $label       The label
     * @param string $control     The form control
     * @param string $help        The help text
     * @param int    $labelSize   The size of the label
     * @param int    $controlSize The size of the form control
     * @return $this
     * @throws ControlGroupException if the sizes are invalid
     */
    public function generate(
        $label,
        $control,
        $help = null,
        $labelSize = null,
        $controlSize = null
    ) {
        if ($this->sizesAreInvalid($labelSize, $controlSize)) {
            throw new ControlGroupException(
                'The label size + control size must be between 1 and 12'
            );
        }

        return $this->withLabel($label, $labelSize)
            ->withContents($control, $controlSize)
            ->withHelp($help);
    }

    /**
     * Renders the contents if given as an array
     *
     * @return string
     */
    protected function renderArrayContents()
    {
        $string = '';
        foreach ($this->contents as $item) {
            if (isset($item['label'])) {
                $string .= call_user_func_array(
                        [$this->formBuilder, 'label'],
                        $item['label']
                    ) . ' ';
            }

            $input_args = $item['input'];
            $type = $input_args['type'];
            unset($input_args['type']);

            $string .= call_user_func_array(
                [$this->formBuilder, $type],
                $input_args
            );

            $string .= '<br />';
        }

        return $string;
    }

    /**
     * Renders the label
     *
     * @return string
     */
    public function renderLabel()
    {
        $string = '';

        if ($this->labelSize) {
            $this->controlSize = $this->controlSize ?: 12 - $this->labelSize;

            $this->label = preg_replace(
                "/class=('|\")(.*)('|\")/i",
                sprintf('class=${1}${2} col-sm-%s${3}', $this->labelSize),
                $this->label
            );
        }

        $string .= $this->label;
        return $string;
    }

    /**
     * Creates the div to surround the form control
     *
     * @return string
     */
    public function createControlDiv()
    {
        return sprintf("<div class='col-sm-%s'>", $this->controlSize);
    }

    /**
     * Checks if both the label size and control size are invalid
     *
     * @param int $labelSize   The size of the label
     * @param int $controlSize The size of the control group
     * @return bool
     */
    protected function sizesAreInvalid($labelSize = null, $controlSize = null)
    {
        // If both are null then we have a valid size
        if (!isset($labelSize) && !isset($controlSize)) {
            return false;
        }

        // So at least one of these is null
        if (isset($labelSize)) {
            if ($this->sizeIsInvalid($labelSize)) {
                return true;
            }
        } else {
            $labelSize = 0;
        }

        if (isset($controlSize)) {
            if ($this->sizeIsInvalid($controlSize)) {
                return true;
            }
        } else {
            $controlSize = 0;
        }

        return $this->sizeIsInvalid($labelSize + $controlSize);
    }

    /**
     * Checks if the size is invalid
     *
     * @param int The $size size
     * @return bool True if the size is below 1 or greater than 11,
     *            false otherwise
     */
    protected function sizeIsInvalid($size)
    {
        return $size < 1 || $size > 11;
    }
}
