<?php

/**
 * Questo file è parte di Pongho.
 *
 * @author    Daniele Termini
 * @copyright Web Agency Meta Line S.r.l.
 * @package   Application\Admin
 */

namespace Application\Admin\Form\Generator;

use Application\Admin\Form\Field\ActionsField;
use Application\Admin\Form\Field\LinkTypeField;
use Application\Admin\Form\Field\TableChecklistField;
use Application\Admin\Form\Repeater\RowContent;
use Application\Admin\Form\Repeater\RowMain;
use Application\Admin\Form\SidebarFieldset;
use Application\Admin\Form\StructureFieldsetInterface;
use Application\Core\Model\File;
use Pongho\Form\BaseField;
use Pongho\Form\Field;
use Pongho\Form\Field\ButtonField;
use Pongho\Form\Field\DateField;
use Pongho\Form\Field\DateTimeField;
use Pongho\Form\Field\FileField;
use Pongho\Form\Field\HiddenField;
use Pongho\Form\Field\ImageField;
use Pongho\Form\Field\TimeField;
use Pongho\Form\Fieldset;
use Pongho\Form\Form;
use Pongho\Form\Generator\Html5Generator;
use Pongho\Form\Repeater\NestedRepeaterRow;
use Pongho\Form\Repeater\NestedSortableInterface;
use Pongho\Form\Repeater\Repeater;
use Pongho\Form\Repeater\RepeaterRow;
use Pongho\Form\Repeater\SortableInterface;
use Pongho\Form\Table\TableFieldset;
use Pongho\Form\Table\TableRow;
use Pongho\Utilities\DateTime;

class AdminGenerator extends Html5Generator
{
    /** @var bool  */
    protected $in_sidebar = false;

    /**
     * {@inheritdoc}
     */
    public function renderGroup(Fieldset $group)
    {
        // Sposto gli errori del Repeater fuori dal Repeater
        if ($group instanceof Repeater) {
            return $this->getErrorsCode($group)
                . $this->getWarningsCode($group)
                . $this->getStartCode($group)
                . $this->getContentCode($group)
                . $this->getEndCode($group);
        }

        return parent::renderGroup($group);
    }


    /**
     * {@inheritdoc}
     */
    public function getStartCode(Fieldset $group)
    {
        /** @var \Pongho\Form\Fieldset $group */

        if ($group instanceof Form) {
            $group->addCssClass('pongho-form');
        }

        /**
         * Structure :: START
         */
        if ($group instanceof StructureFieldsetInterface) {
            if ($group instanceof SidebarFieldset) {
                $this->in_sidebar = true;
            }

            return $group->getStartCode();
        }

        /**
         * Repeater :: START
         */

        if ($group instanceof Repeater) {
            return $group->getStartCode();
        }

        if ($group instanceof RepeaterRow || $group instanceof NestedRepeaterRow) {
            if ($group->hasWarning()) {
                $group->addCssClass('alert');
            }

            return $group->getStartCode();
        }

        if ($group instanceof RowMain) {
            return $group->getStartCode();
        }

        if ($group instanceof RowContent) {
            return $group->getStartCode();
        }

        /**
         * Repeater :: END
         */

        if ($group instanceof TableFieldset) {
            if (!$group->hasCssClass('pongho-table')) {
                $group->addCssClass('pongho-table');
            }

            return $group->getStartCode();
        }

        if ($group instanceof TableRow) {
            return $group->getStartCode();
        }

        $code = parent::getStartCode($group);

        if (!($group instanceof Form)) {
            if (!$group->getSetting('hide_label', false)) {
                $code .= '<h3>' . $group->getLabel() . '</h3>';
            }

            if ($group->hasCssClass('panel-accordion')) {
                $code .= '<div>';
            }

            $description = $group->getDescription();
            if ($description) {
                // Potrei avere dei tag block che non devono stare all'interno di un <p>, ma posso consentire i tag inline
                if ($description === strip_tags($description, '<a><b><i><u><sup><sub><span><strong><br><br/><br />')) {
                    $code .= '<p>' . $description . '</p>';
                } else {
                    $code .= $description;
                }
            }
        }

        if ($group instanceof Form) {
            /** @var \Pongho\Form\Form $group */
            if ($group->hasHandled() && !$group->hasErrors()) {
                $code .= '<div id="box-info" class="box info">' . $this->language->get('saved_successful') . '</div>';
            } else {
                $code .= $group->renderErrors();
            }

            $code .= $group->renderWarnings();
        }

        return $code;
    }

    /**
     * {@inheritdoc}
     */
    public function getEndCode(Fieldset $group)
    {
        /** @var \Pongho\Form\Fieldset $group */

        if ($group instanceof SidebarFieldset) {
            $this->in_sidebar = false;
        }

        /** Repeater */
        if ($group instanceof Repeater) {
            return $group->getEndCode();
        }

        /** Form */
        if ($group instanceof Form) {
            return $group->getEndCode();
        }

        /** Pannelli */
        $code = '';

        if ($group->hasCssClass('panel-accordion')) {
            $code .= '</div>';
        }

        return $code . parent::getEndCode($group);
    }

    /**
     * Restituisce il codice del pulsante Add Row per il repeater
     *
     * @return string
     */
    public function renderRepeaterAddButton()
    {
        return '<div class="actions"><a class="repeater-add-row pongho-button">' . $this->language->get('add_item') . '</a></div>';
    }

    /**
     * {@inheritdoc}
     */
    protected function renderAlerts(array $alerts, $type)
    {
        $code = '';

        foreach ($alerts as $field => $alert) {
            $code .= '<li>' . sprintf($this->language->get($alert), $this->language->get($field)) . '</li>';
        }

        return '<ul id="box-' . $type . '" class="box ' . $type . '">' . $code . '</ul>';
    }

    /**
     * {@inheritdoc}
     */
    public function renderWarnings(array $warnings)
    {
        if ($warnings === []) {
            return '';
        }

        return $this->renderAlerts($warnings, 'alert');
    }

    /**
     * Restituisce una stringa con gli attributi base settati in un campo
     *
     * @param bool      $suppress_value
     * @return string
     */
    protected function getAttributesForReadonly(BaseField $field, $suppress_value = false)
    {
        $classes = array_combine($field->getCssClasses(), $field->getCssClasses());

        // Rimuovo eventuali classi di competenza del Generatore che sono state inserite nella configurazione dei campi
        unset($classes['pongho-field']);
        unset($classes['pongho-button']);
        unset($classes['input_text']);
        unset($classes['input_date']);
        unset($classes['input_time']);
        unset($classes['input_datetime']);

        $classes = implode(' ', $classes);

        return sprintf(
            ' id="%s"%s%s',
            $field->getId(),
            ($classes ? ' class="' . $classes . '"' : ''),
            ($suppress_value ? '' : ' data-value="' . $this->valueToString($field) . '"')
        );
    }

    /**
     * Converte in stringa il valore di un campo
     *
     * @return string
     */
    protected function valueToString(Basefield $field)
    {
        if ($field instanceof DateField && $field->getValue() instanceof DateTime) {
            /** @var DateField $field */
            return $field->getValue()->format($field->getFormat());
        } elseif ($field instanceof LinkTypeField) {
            if ($link_type = $field->getValueRaw()) {
                return $link_type->id;
            }
        }

        return $field->getValue();
    }

    /**
     * {@inheritdoc}
     */
    public function renderTag(BaseField $field)
    {
        /** @var \Pongho\Form\BaseField|\Pongho\Form\Field\DateField|\Pongho\Form\Field\SelectField $field */
        if ($field instanceof ButtonField || $field instanceof ActionsField) {
            $field->addCssClass('pongho-button');

            return $field->getTag();
        }

        $tag = null;

        if ($field->hasError()) {
            $field->addCssClass('error');
        }

        if ($field->hasWarning()) {
            $field->addCssClass('warning');
        }

        // Mantenere l'ordine, ImageField è anche istanza di FileField!
        if ($field instanceof ImageField) {
            if ($field->isReadonly()) {
                $file = File::find($field->getValue());

                if ($file) {
                    $src = src(
                        $file,
                        $field->getSetting('image_size', 'adminpreview'),
                        $field->getSetting('image_not_found')
                    );

                    $tag = '<img' . $this->getAttributesForReadonly($field) . ' src="' . $src . '" />';
                } else {
                    $tag = '-';
                }
            } else {
                $tag = $field->getTag() . $this->getImageFieldActions($field);
            }
        } elseif ($field instanceof FileField) {
            if ($field->isReadonly()) {
                $field->addCssClass('icn icn-file');
                $tag = '<span' . $this->getAttributesForReadonly($field) . '></span>';
            } else {
                $tag = $field->getTag() . $this->getFileFieldActions($field);
            }
        } elseif ($field instanceof TimeField) {
            if ($field->isReadonly()) {
                $tag = '<span' . $this->getAttributesForReadonly($field) . '>' . $this->valueToString($field) . '</span>';
            } elseif (!$field->hasCssClass('input_time')) {
                $field->addCssClass('input_time');
            }
        } elseif ($field instanceof DateTimeField) {
            if ($field->isReadonly()) {
                $tag = '<span' . $this->getAttributesForReadonly($field) . '>' . $this->valueToString($field) . '</span>';
            } elseif (!$field->hasCssClass('input_datetime')) {
                $field->addCssClass('input_datetime');
            }
        } elseif ($field instanceof DateField) {
            if ($field->isReadonly()) {
                $tag = '<span' . $this->getAttributesForReadonly($field) . '>' . $this->valueToString($field) . '</span>';
            } elseif (!$field->hasCssClass('input_date')) {
                $field->addCssClass('input_date');

                if ($field->hasSetting('datePickerOptions')) {
                    $field->setAttribute(
                        'data-options',
                        htmlentities(json_encode($field->getSetting('datePickerOptions')), ENT_QUOTES, 'UTF-8')
                    );
                }

                if ($field->getSetting('nullable', false) === true) {
                    $field->setAttribute('data-nullable', $this->language->get('reset'));
                }
            }
        } elseif ($field instanceof HiddenField) {
            if ($field->isReadonly()) {
                return '';
            } else {
                return $field->getTag();
            }
        } elseif ($field instanceof LinkTypeField) {
            if ($field->isReadonly()) {
                /** @var \Application\core\Model\LinkType $link_type */
                if ($link_type = $field->getValueRaw()) {
                    $url = $link_type->getPermalink();
                    $tag = '<span' . $this->getAttributesForReadonly($field) . '><a href="' . $url . '">' . $url . '</a></span>';
                } else {
                    $tag = '';
                }
            } else {
                $tag = '<div class="link-type-wrapper">' . $field->getTag() . $this->renderDescription($field) . '</div>';
            }
        } elseif ($field instanceof Field\PasswordField) {
            if ($field->isReadonly()) {
                return '';
            }
        } elseif ($field instanceof Field\ColorPickerField) {
            if ($field->isReadonly()) {
                $field->setAttribute('disabled', 'disabled');
            }
        } elseif ($field instanceof Field\TextField) {
            if ($field->isReadonly()) {
                $tag = '<span' . $this->getAttributesForReadonly($field, true) . '>' . html_escape((string) $field->getValue()) . '</span>';
            } elseif (!$field->hasCssClass('input_text')) {
                $field->addCssClass('input_text');
            }
        } elseif ($field instanceof Field\TextareaField) {
            if ($field->isReadonly()) {
                if ($field->hasCssClass('tinymce')) {
                    $text = str_replace(["\n", "\r"], '', (string) $field->getValue());
                    $text = str_replace("'", '&#039;', $text);

                    $tag = <<<HTML
<iframe{$this->getAttributesForReadonly($field, true)} style="border: 0 none; height: 200px; width: 100%;"></iframe>
<script>
(function () {
    var d = document.getElementById('{$field->getId()}').contentWindow.document;
    console.log(d);
    d.open(); d.write('{$text}'); d.close();
}());
</script>
HTML;
                } else {
                    $text = nl2br(html_escape((string) $field->getValue()));
                    $tag = '<div' . $this->getAttributesForReadonly($field, true) . '>' . $text . '</div>';
                }
            }
        } elseif ($field instanceof Field\CheckboxField) {
            if ($field->isReadonly()) {
                $tag = '<span' . $this->getAttributesForReadonly($field) . '>' . ($field->getValue() ? $this->language->get('yes') : $this->language->get('no')) . '</span>';
            }
        } elseif ($field instanceof Field\SelectField) {
            // Select, Checklist, Radio
            if ($field->isReadonly()) {
                $field_options = $field->getOptions();

                if ($field->isMultiple()) {
                    $tag = '';

                    foreach ($field->getValue() as $value) {
                        $_value = $this->searchArray($field_options, $value);

                        $tag .= '<li data-value="' . $value . '">' . html_escape((string) $_value) . '</li>';
                    }

                    $tag = '<ul' . $this->getAttributesForReadonly($field, true) . '>' . $tag . '</ul>';
                } else {
                    $_value = $this->searchArray($field_options, $field->getValue());

                    $tag = '<span' . $this->getAttributesForReadonly($field) . '>' . html_escape((string) $_value) . '</span>';
                }
            }
        }

        // Default
        if ($tag === null) {
            $tag = $field->getTag();
        }

        return $tag;
    }

    /**
     * {@inheritdoc}
     */
    public function render(Field $field, array $options = [])
    {
        if ($field instanceof Fieldset) {
            return $this->renderGroup($field);
        }

        /** @var \Pongho\Form\BaseField|\Pongho\Form\Field\DateField|\Pongho\Form\Field\SelectField $field */
        if ($field instanceof ButtonField || $field instanceof ActionsField) {
            $field->addCssClass('pongho-button');

            return $this->renderTag($field);
        }

        $field->addCssClass('pongho-field');

        $tag = $this->renderTag($field);

        // Se il campo è contenuto in, o è, uno dei seguenti non devo creare tutta la struttura di contentimento
        if ($field->getContainer() instanceof RowMain) {
            return $tag;
        } elseif ($field->getContainer() instanceof TableRow) {
            return $tag;
        } elseif ($field instanceof HiddenField) {
            return $tag;
        } elseif ($field instanceof TableChecklistField) {
            return $tag;
        }

        if ($field instanceof Field\CheckboxField && $this->in_sidebar && !$field->getDescription() && !$field->isReadonly()) {
            return $this->getSidebarCheckboxcode($field->getLabel(), $tag, $field->isRequired());
        }

        if ($field->getSetting('label-wrap', false) && !$field->isReadonly()) {
            $tag = $this->getLabelCode(
                $tag . ' <span>' . $field->getDescription() . '</span>',
                $field->getId()
            );
            $description = '';
        } else {
            $description = $field->getDescription();
        }

        return $this->renderStructure(
            $this->renderLabel($field),
            $tag,
            $description,
            $field->isReadonly()
        );
    }

    /**
     * Cerca una chiave all'interno di un array e ne restituisce il valore
     *
     * @param       $key
     * @return array|mixed
     */
    protected function searchArray(array $array, $key)
    {
        $_value = $key;

        if (is_bool($key)) {
            $key = (int) $key;
        } elseif (is_null($key)) {
            $key = '';
        }

        $iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array), \RecursiveIteratorIterator::CHILD_FIRST);
        foreach ($iterator as $k => $v) {
            if ($k === $key) {
                $_value = $v;
                break;
            }
        }

        if (is_array($_value)) {
            $_value = reset($_value);
        }

        return $_value;
    }

    /**
     * @return string
     */
    protected function getImageFieldActions(ImageField $field)
    {
        $actions = '';

        if ($file_id = $field->getValue()) {
            $actions = ' <span>' . $field->getPreviewLinkCode($this->language->get('preview'))
                . ' ' . $field->getDeleteLinkCode($this->language->get('delete')) . '</span>';
        }

        return $actions;
    }

    /**
     * @return string
     */
    protected function getFileFieldActions(FileField $field)
    {
        $actions = '';

        if ($file_id = $field->getValue()) {
            $actions = ' <span>' . $field->getDownloadLinkCode($this->language->get('download'))
                . ($field->isReadonly() ? '' : ' ' . $field->getDeleteLinkCode($this->language->get('delete'))) . '</span>';
        }

        return $actions;
    }

    /**
     * {@inheritdoc}
     */
    public function renderLabel(Field $field)
    {
        /** @var BaseField $field */
        return $this->getLabelCode(
            $field->getLabel(),
            $field->getId(),
            $field->isRequired() || $field->hasAttribute('required'),
            'control-label'
        );
    }

    /**
     * {@inheritdoc}
     */
    public function renderDescription(Field $field)
    {
        $description = $field->getDescription();

        return $this->getDescriptionCode($description);
    }

    /**
     * {@inheritdoc}
     */
    public function renderPreviewImage(ImageField $field)
    {
        return '<div class="slide-preview">' . parent::renderPreviewImage($field) . '</div>';
    }

    /**
     * Renderizza la struttura di contenimento per un campo
     *
     * @param string $label
     * @param string $tag
     * @param string $description
     * @param bool   $readonly
     * @param string $class
     * @return string
     */
    public function renderStructure($label, $tag, $description = '', $readonly = false, $class = '')
    {
        $classes = [
            'control-group',
        ];

        if ($readonly) {
            $classes[] = 'readonly';
        }

        if ($class) {
            $classes[] = $class;
        }

        return $this->getStructureCode($label, $tag, $description, $readonly, implode(' ', $classes));
    }

    /**
     * @param string $label
     * @param string $tag
     * @param string $description
     * @param bool   $readonly
     * @param string $class
     * @return string
     */
    protected function getStructureCode($label, $tag, $description = '', $readonly = false, $class = '')
    {
        return sprintf(
            '<div class="%s">%s<div class="controls">%s%s</div></div>',
            $class,
            $label,
            $tag,
            $readonly ? '' : $this->getDescriptionCode($description)
        );
    }

    /**
     * @param string $label
     * @param string $tag
     * @param bool   $required
     * @param string $class
     * @return string
     */
    protected function getSidebarCheckboxcode($label, $tag, $required = false, $class = '')
    {
        return $this->renderStructure(
            '',
            sprintf(
                '<label>%s <span>%s%s</span></label>',
                $tag,
                $label,
                $this->getRequiredCode($required)
            ),
            '',
            false,
            $class
        );
    }

    /**
     * @param string $label
     * @param string $class
     * @param string $field_id
     * @param bool   $required
     * @return string
     */
    protected function getLabelCode($label, $field_id, $required = false, $class = '')
    {
        if ($class) {
            $class = ' class="' . $class . '"';
        }

        return sprintf(
            '<label' . $class . ' for="%2$s">%1$s%3$s</label>',
            $label,
            $field_id,
            $this->getRequiredCode($required)
        );
    }

    /**
     * @param bool $required
     * @return string
     */
    protected function getRequiredCode($required = false)
    {
        return $required ? ' <i>*</i>' : '';
    }

    /**
     * @param $description
     * @return string
     */
    protected function getDescriptionCode($description)
    {
        if (!empty($description) && strip_tags((string) $description) === $description) {
            return ' <small>' . $description . '</small>';
        }

        return $description;
    }
}
