<?php

/**
 * Class FormBuilder
 * Generates the forms used in module builder and module builder modules.
 *
 * @copyright MTC media Ltd.
 * @author Rihards Silins
 *
 * @version 4 17/01/2017
 */
namespace Mtc\Modules\ModuleBuilder\Classes\Builders;

class FormBuilder extends PanelBuilder
{
    /**
     * whether or not FormBuilder::start() has been called and FormBuilder::end() yet hasn't.
     *
     * @var bool
     */
    protected $form_open = true;
    /**
     * path to templates for generating forms
     *
     * @var string
     */
    protected $template_path = "/admin/module_builder/builders/form_builder";

    /**
     * FormBuilder class constructor.
     * @param Twig Object $twig
     */
    public function __construct($twig)
    {
        $this->twig = $twig;
    }

    /**
     * FormBuilder::start()
     * Starts form.
     *
     * @param Object model
     * @param mixed[] params
     *
     * @return string html
     */
    public function start($model = null, $params = array())
    {
        $this->start_microtime = microtime(true);

        if (!empty($model)) {
            if (is_string($model)) {
                $this->model = null;
                $this->model_name = $model;
            } else {
                $this->model = $model;
                $this->model_name = get_class($model);
            }
        }

        $this->form_open = true;

        $this->input_default_params = array(
            'id'                                => null,
            'tag'                               => 'input', // only used on undefined/custom types
            'type'                              => 'text',
            'button_type'                       => 'button',
            'class'                             => '',
            'relationship_value_property_name'  => 'id',
            'value'                             => null,
            'default_value'                     => null,
            'attr_name'                         => null,
            'input_name'                        => null,
            'scoped_name'                       => true,
            'datetime_db_format'                => 'Y-m-d H:i:s',
            'datetime_frontend_format'          => 'd/m/Y H:i',
            'preview_image_path'                => 'uploads/images/cms/small',
            'file_path'                         => 'uploads/files',
            'container'                         => array(
                'div' => array(
                    'class' => 'row',
                ),
            ),
            'label'                             => null,
            'info'                              => '',
            'dynamic_options_select'            => array(
                'name',
            ),
            'dynamic_options_filter'            => array(
                array('active', '=', 1),
            ),
            'dynamic_options_order'             => array(
                array('name','ASC'),
            ),
        );

        //default attributes for html form tag
        $attributes = array(
            'id'        => null,
            'method'    => 'post',
            'action'    => '',
            'enctype'   => 'multipart/form-data',
            'class'     => '',
            'model_name'=> $this->model_name
        );

        // process params
        $check_simple_not_empty_parameters = array(
            "id",
            "method",
            "action",
            "button_type",
            "enctype"
        );
        foreach ($check_simple_not_empty_parameters as $check_simple_not_empty_parameter) {
            if (!empty($params[$check_simple_not_empty_parameter])) {
                $attributes[$check_simple_not_empty_parameter] = $params[$check_simple_not_empty_parameter];
                unset($params[$check_simple_not_empty_parameter]);
            }
        }

        if (isset($params['class'])) {
            $attributes['class'] = $params['class'];
            unset($params['class']);
        }

        $check_simple_isset_parameters = array(
            "scoped_name",
            "preview_image_path",
            "file_path",
            "dynamic_options_select",
            "dynamic_options_filter",
            "dynamic_options_order"
        );
        foreach ($check_simple_isset_parameters as $check_simple_isset_parameter) {
            if (isset($params[$check_simple_isset_parameter])) {
                $this->input_default_params[$check_simple_isset_parameter] = $params[$check_simple_isset_parameter];
                unset($params[$check_simple_isset_parameter]);
            }
        }

        if (isset($params['input_default_params'])) {
            $this->input_default_params = array_merge(
                $this->input_default_params,
                $params['input_default_params']
            );
            unset($params['input_default_params']);
        }

        $additional_attributes = $this->filterAdditionalAttributes($params);

        $html = $this->twig->render(
            $this->template_path."/tag_form_open.twig",
            array(
                'attributes' => $attributes,
                'additional_attributes' => $additional_attributes
            )
        );

        return $html;
    }

    /**
     * FormBuilder::end()
     * Ends form.
     *
     * @param mixed[] params
     *
     * @return string html
     */
    public function end($params = array())
    {
        $this->model = null;
        $this->form_open = false;
        $duration = microtime(true) - $this->start_microtime;

        $html = $this->twig->render(
            $this->template_path."/tag_form_close.twig",
            array(
                'duration' => $duration
            )
        );

        return $html;
    }

    /**
     * FormBuilder::input()
     * Creates & returns a html input.
     *
     * @param mixed[] $params
     *
     * @return string $html
     */
    public function input($params)
    {
        $attributes = $this->input_default_params;

        // process params

        if (!is_array($params)) {
            $attributes['attr_name'] = $params;
            $params = array();
        } else {
            $attributes['attr_name'] = key($params);
            $params = $params[$attributes['attr_name']];
            if (isset($params['attribute_name'])) {
                $attributes['attr_name'] = $params['attribute_name'];
            }
        }

        $attributes['scoped_name'] = $this->input_default_params['scoped_name'];
        if (isset($params['scoped_name'])) {
            $attributes['scoped_name'] = $params['scoped_name'];
            unset($params['scoped_name']);
        } elseif (!empty($params['type']) && $params['type'] === 'submit') {
            $attributes['scoped_name'] = false;
        }

        // if there is no model_name - it can't be scoped
        if (empty($this->model_name)) {
            $attributes['scoped_name'] = false;
        }

        if ($attributes['scoped_name']) {
            $attributes['input_name'] = $this->model_name.'['.$attributes['attr_name'].']';
        } else {
            $attributes['input_name'] = $attributes['attr_name'];
        }

        $attributes['label'] = $this->labelFromAttrName($attributes['attr_name']);
        $attributes['id'] = $attributes['input_name'];

        $check_simple_isset_parameters = array(
            "label",
            "value",
            "default_value",
            "class",
            "id",
            "preview_image_path",
            "file_path",
            "dynamic_options_select",
            "dynamic_options_filter",
            "dynamic_options_order",
            "container",
            "relationship_value_property_name",
            "options_empty_default",
            "rules"
        );
        foreach ($check_simple_isset_parameters as $check_simple_isset_parameter) {
            if (isset($params[$check_simple_isset_parameter])) {
                $attributes[$check_simple_isset_parameter] = $params[$check_simple_isset_parameter];
                unset($params[$check_simple_isset_parameter]);
            }
        }

        $check_simple_not_empty_parameters = array(
            "type",
            "tag",
            "info",
            "datetime_db_format",
            "datetime_frontend_format",
            "crop_adjust"
        );
        foreach ($check_simple_not_empty_parameters as $check_simple_not_empty_parameter) {
            if (!empty($params[$check_simple_not_empty_parameter])) {
                $attributes[$check_simple_not_empty_parameter] = $params[$check_simple_not_empty_parameter];
                unset($params[$check_simple_not_empty_parameter]);
            }
        }

        if (isset($params['options']) && !is_array($params['options'])) {
            $attributes['options'] = array();
            $options_object_name = $params['options'];

            $option_lister = $options_object_name::where('id', '>', 0);

            foreach ($attributes['dynamic_options_filter'] as $filter) {
                $option_lister = $option_lister->where($filter[0], $filter[1], $filter[2]);
            }

            foreach ($attributes['dynamic_options_order'] as $order) {
                $option_lister = $option_lister->orderBy($order[0], $order[1]);
            }

            $options_object_raw_list = $option_lister->get();
            foreach ($options_object_raw_list as $key => $options_object_raw_item) {
                $attributes['options'][$options_object_raw_item->id] = '';
                foreach ($attributes['dynamic_options_select'] as $select) {
                    $attributes['options'][$options_object_raw_item->id] .= $options_object_raw_item->{$select}.' ';
                }
            }
            unset($params['options']);
        } elseif (isset($params['options']) && is_array($params['options'])) {
            $attributes['options'] = $params['options'];
            unset($params['options']);
        }

        if (isset($attributes['options_empty_default']) && $attributes['options_empty_default'] !== false) {
            $original_options = $attributes['options'];
            $attributes['options'] = [];
            $attributes['options'][""] = $attributes['options_empty_default'];
            foreach ($original_options as $key => $value) {
                $attributes['options'][$key] = $value;
            }
            unset($original_options);
        }

        // additional attributes
        $additional_attributes = $this->filterAdditionalAttributes($params);

        // usability adjust
        if ($attributes['type'] == 'coordinates') {
            $attributes['type'] = 'text';
            $attributes['class'] = 'coordinates';
        }
        if ($attributes['type'] == 'combobox_select' || $attributes['type'] == 'combobox_selects') {
            $attributes['class'] .= ' combobox';
        }
        if ($attributes['type'] == 'images') {
            $attributes['relationship_value_property_name'] = 'path';
        }

        // Figure out the value of the input based on all the attributes/params

        if (isset($attributes['value'])) {
            // If 'value' param is passed into the input then display it as it has top priority
            $input_value = $attributes['value'];
        } elseif (
            $attributes['scoped_name'] &&
            !empty($this->model_name) &&
            isset($_REQUEST[$this->model_name][$attributes['attr_name']]) &&
            !method_exists($this->model, $attributes['attr_name']) &&
            !in_array($attributes['type'], ["image", "images", "file", "files"])
        ) {
            // Then check the request (assuming requests are scoped). Maybe it's a unsuccessfull form submit and we want to preserve what
            //  the user wanted to  input so he can try again.
            $input_value = $_REQUEST[$this->model_name][$attributes['attr_name']];
        } elseif (
            isset($_REQUEST[$attributes['attr_name']]) &&
            !method_exists($this->model, $attributes['attr_name']) &&
            !in_array($attributes['type'], ["image", "images", "file", "files"])
        ) {
            //Then check the request (assuming requests are not scoped). Maybe it's a unsuccessfull form submit and we want to preserve what
            // the user wanted to  input so he can try again.
            $input_value = $_REQUEST[$attributes['attr_name']];
        } elseif (!empty($this->model) && isset($this->model->{$attributes['attr_name']}) && !method_exists($this->model, $attributes['attr_name'])) {
            // Then maybe the model already has a value for this property preset
            $input_value = $this->model->{$attributes['attr_name']};
        } elseif (!empty($this->model) && method_exists($this->model, $attributes['attr_name'])) {
            // Then maybe the model has a relation function for this property
            $input_value = array();
            $results = $this->model->{$attributes['attr_name']}()->getResults();
            foreach ($results as $value) {
                $input_value[] = $value->{$attributes['relationship_value_property_name']};
            }
        } elseif (isset($attributes['default_value'])) {
            // Then maybe theres a default value a new model should be using
            $input_value = $attributes['default_value'];
        } else {
            // If none of the above the value is empty
            $input_value = '';
        }
        if ($attributes['type'] == 'fieldset') {
            return;
        }

        $html = '';

        if ($attributes['type'] == 'datetime') {
            if (!empty($input_value)) {
                $datetime = date_create_from_format($attributes['datetime_db_format'], $input_value);
                if ($datetime !== false) {
                    $input_value = $datetime->format($attributes['datetime_frontend_format']);
                }
            }

            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['input_name']);

            $html .= $this->twig->render(
                $this->template_path."/input_datetime.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value
                )
            );

            $html .= $this->info($attributes['info']);
            $html .= $this->containerEnd($attributes['container']);

        } elseif ($attributes['type'] == 'image') {
            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['input_name']);

            $html .= $this->twig->render(
                $this->template_path."/input_image.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value,
                    'info'                  => $this->info($attributes['info'])
                )
            );

            $html .= $this->containerEnd($attributes['container']);

        } elseif ($attributes['type'] == 'file') {
            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['input_name']);

            $html .= $this->twig->render(
                $this->template_path."/input_file.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value,
                    'info'                  => $this->info($attributes['info'])
                )
            );

            $html .= $this->containerEnd($attributes['container']);

        } elseif ($attributes['type'] == 'textarea') {
            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['input_name']);

            $html .= $this->twig->render(
                $this->template_path."/input_textarea.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value
                )
            );

            $html .= $this->info($attributes['info']);
            $html .= $this->containerEnd($attributes['container']);

        } elseif ($attributes['type'] == 'select') {
            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['input_name']);

            $html .= $this->twig->render(
                $this->template_path."/input_select.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value
                )
            );

            $html .= $this->info($attributes['info']);
            $html .= $this->containerEnd($attributes['container']);

        } elseif ($attributes['type'] == 'combobox_selects') {
            $html .= $this->containerStart($attributes['container']);
            $i = 0;
            if (empty($input_value) || count($input_value) == 0) {
                $html .= $this->multiInput(
                    "combobox_select",
                    $attributes,
                    $additional_attributes,
                    $i
                );
            } else {
                foreach ($input_value as $input_value_item) {
                    $html .= $this->multiInput(
                        "combobox_select",
                        $attributes,
                        $additional_attributes,
                        $i,
                        $input_value_item
                    );
                    $i++;
                }
            }
            $html .= $this->containerEnd($attributes['container']);
        } elseif ($attributes['type'] == 'combobox_select') {
            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['id']);
            $html .= $this->twig->render(
                $this->template_path."/combobox_select.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value,
                )
            );
            $html .= $this->info($attributes['info']);
            $html .= $this->containerEnd($attributes['container']);
        } elseif ($attributes['type'] == 'images') {
            $html .= $this->containerStart($attributes['container']);
            $i = 0;
            if (empty($input_value) || count($input_value) == 0) {
                $html .= $this->multiInput(
                    "input_multi_image",
                    $attributes,
                    $additional_attributes,
                    $i
                );
            } else {
                foreach ($input_value as $input_value_item) {
                    $html .= $this->multiInput(
                        "input_multi_image",
                        $attributes,
                        $additional_attributes,
                        $i,
                        $input_value_item
                    );
                    $i++;
                }
            }
            $html .= $this->containerEnd($attributes['container']);
        } elseif ($attributes['type'] == 'checkbox') {
            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['input_name']);

            $html .= $this->twig->render(
                $this->template_path."/input_checkbox.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value
                )
            );

            $html .= $this->info($attributes['info']);
            $html .= $this->containerEnd($attributes['container']);

        } elseif ($attributes['type'] == 'hidden') {
            $html .= $this->twig->render(
                $this->template_path."/input_hidden.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value
                )
            );
        } elseif ($attributes['type'] == 'button') {
            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['input_name']);

            $html .= $this->twig->render(
                $this->template_path."/input_button.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value
                )
            );

            $html .= $this->info($attributes['info']);
            $html .= $this->containerEnd($attributes['container']);

        } elseif ($attributes['type'] == 'submit') {
            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['input_name']);

            $html .= $this->twig->render(
                $this->template_path."/input_submit.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value
                )
            );

            $html .= $this->info($attributes['info']);
            $html .= $this->containerEnd($attributes['container']);

        } elseif ($attributes['type'] == 'datalist') {
            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['input_name']);

            $html .= $this->twig->render(
                $this->template_path."/input_datalist.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value
                )
            );

            $html .= $this->info($attributes['info']);
            $html .= $this->containerEnd($attributes['container']);
        } else {
            $html .= $this->containerStart($attributes['container']);
            $html .= $this->label($attributes, $attributes['input_name']);

            $html .= $this->twig->render(
                $this->template_path."/input_text.twig",
                array(
                    'attributes'            => $attributes,
                    'additional_attributes' => $additional_attributes,
                    'input_value'           => $input_value
                )
            );

            $html .= $this->info($attributes['info']);
            $html .= $this->containerEnd($attributes['container']);
        }

        return $html;
    }

    /**
     * Multi Input type generation.
     * @param string $template_name
     * @param mixed[] $attributes
     * @param mixed[] $additional_attributes
     * @param int $index
     * @param mixed $input_value
     * @return string $html
     */
    protected function multiInput($template_name, $attributes, $additional_attributes, $index, $input_value = null)
    {
        $html = "";
        $html .= $this->containerStart(array(
            'div' => array(
                'class' => 'rowRow'
            )
        ));
        $twig_vars = array(
            'attributes'            => $attributes,
            'additional_attributes' => $additional_attributes,
            'input_value'          => $input_value
        );
        $twig_vars['attributes']['input_name'] .= "[]";
        $twig_vars['attributes']['id'] = $attributes['id']."[".$index."]";
        $html .= $this->label($attributes, $twig_vars['attributes']['id']);
        $html .= $this->twig->render(
            $this->template_path."/".$template_name.".twig",
            $twig_vars
        );
        $html .= $this->twig->render(
            $this->template_path."/multi_input_buttons.twig"
        );
        $html .= $this->info($attributes['info']);
        $html .= $this->containerEnd(array(
            'div' => array()
        ));
        return $html;
    }

    /**
     * FormBuilder::label()
     * Generate label html from all the params.
     *
     * @param mixed[] attributes
     * @param string for
     * @param string[] additional_attributes
     *
     * @return string label_html
     */
    public function label($attributes, $for = '', $additional_attributes = array())
    {
        $html = '';

        if ($attributes['label'] === false) {
            return $html;
        }

        if (!empty($attributes['rules']['required'])) {
            $attributes['label'] = $attributes['label'] . " *";
        }

        $html .= $this->twig->render(
            $this->template_path."/label.twig",
            array(
                'for' => $for,
                'label_name' => $attributes['label'],
                'additional_attributes' => $additional_attributes
            )
        );

        return $html;
    }

    /**
     * FormBuilder::info()
     * Generate info icon tooltip html.
     *
     * @param string info
     *
     * @return string info_icon_tooltip_html
     */
    public function info($info)
    {
        $html = '';
        if (!empty($info)) {
            $html .= $this->twig->render(
                $this->template_path."/info.twig",
                array(
                    'info' => $info
                )
            );
        }

        return $html;
    }

    /**
     * FormBuilder::fieldset()
     * Generate fieldset with given input fields.
     *
     * @param string $contents
     * @param array $params
     * @author Haroldas Latonas <haroldas.latonas@mtcmedia.co.uk>
     * @date 2017-03-03 14:00
     * @return string fieldset_html
     */
    public function fieldset($contents, $params)
    {
        if (empty($contents)) {
            return '';
        }
        $label = '';
        if ( isset($params['label']) ) {
            $label = $params['label'];
        }

        // Prepare id name for css/js
        $params['id_name'] = \Util::slugify($label, true);
        return $this->twig->render(
            $this->template_path . "/fieldset.twig",
            array(
                'contents'  => $contents,
                'params'    => $params
            )
        );
    }

    /**
     * FormBuilder::buildForm($model_form)
     * Builds form from given form model
     *
     * Method groups each text field into neat array structure:
     * fieldset
     *  - fieldset_params
     *      - label
     *      - hidden (default:false)
     *      - class (List of classes)
     *  - fieldset_items
     *      - Input field model
     *
     * This structure later get rendered to html and passed to
     * FormBuilder::fieldset($contents, $params) to render field inside fieldset.
     *
     * @param array $model_form
     * @author Haroldas Latonas <haroldas.latonas@mtcmedia.co.uk>
     * @date 2017-03-03 14:00
     * @return string html representation of form model
     */
    public function buildForm($model_form) {

        $fieldset_array = [];

        foreach ($model_form as $attribute => $model_form_row) {
            if (isset($model_form_row['type']) && $model_form_row['type'] == "fieldset") { // Skip fieldset model
                continue;
            }
            // Create array item for new fieldset array structure
            if (empty($model_form_row['fieldset'])) {
                // Fallback to MODEL name
                // Groups all fields w/o fieldset under same fieldset named after Model
                $fieldset = ucwords(MODEL_SINGULAR);
            } else {
                $fieldset = $model_form_row['fieldset'];
            }

            // Define params attribute
            if (isset($model_form[$fieldset])) {
                $fieldset_model['params'] = $model_form[$fieldset];
                if (empty($model_form[$fieldset]['label'])){
                    $fieldset_label = explode('Fieldset:',$fieldset);
                    $fieldset_model['params']['label'] = $fieldset_label[1] ?? null;
                }
            } else {
                // No model for this fieldset
                // Fill params with default data
                $fieldset_label = explode('Fieldset:',$fieldset);
                if (isset($fieldset_label[1])) {
                    $fieldset_label = $fieldset_label[1];
                } else {
                    $fieldset_label = $fieldset;
                }
                $fieldset_model['params'] = [
                    'type'      => 'fieldset',
                    'label'     => $fieldset_label,
                ];
            }

            // Create fieldset array item
            $fieldset_array[$fieldset] = $fieldset_model;
            // Loop through all input fields again in order to associate each input with fieldset
            foreach ($model_form as $key => $field) {
                // Skip fieldset model
                if (isset($field['type']) && $field['type'] == "fieldset") {
                    continue;
                }
                // If current input field has fieldset name that matches fieldset name from outter loop
                if (
                        ( isset($field['fieldset']) && ($field['fieldset'] == $fieldset) )
                        || (!isset($field['fieldset']) && $fieldset == ucwords(MODEL_SINGULAR))
                ) {
                    // Associate this form input field with fieldset from outter loop.
                    $fieldset_array[$fieldset]['items'][$key] = $field;
                }
            }
        }

        $input_html = "";
        foreach ($fieldset_array as $fieldset) {

            $text_fields_html = "";
            $current_fieldset = null;

            foreach ($fieldset['items'] as $attribute => $model_form_row) {
                $text_fields_html .= $this->input(array(
                    $attribute => $model_form_row,
                ));
            }

            $input_html .= $this->fieldset($text_fields_html, $fieldset['params']);

            unset($text_fields_html);
        }
        return $input_html;
    }
}
