<?php

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

namespace Application\Admin\Controller;

use ActiveRecord\Base;
use Application\Core\Utilities\Pagination;
use Pongho\Http\Exception\HttpException;
use Pongho\Http\Exception\HttpNotFoundException;
use Pongho\Http\RedirectResponse;
use Pongho\Utilities\Inflector;

/**
 * Implementa i metodi base per le azioni CRUD: create, read, update e delete.
 *
 * @deprecated
 */
abstract class CrudController extends AdminController
{
    /**
     * Visualizza l’archivio dei record.
     *
     * @param integer $page
     */
    abstract public function displayArchive($page = 1);

    /**
     * Restituisce il nome della classe del modello.
     *
     * @return string
     */
    abstract public function getModelClass();

    /**
     * Restituisce la lista dei campi da elaborare.
     *
     * @param mixed $model
     * @return array
     */
    abstract public function getFields($model);

    /**
     * Restituisce la lista dei campi da elaborare.
     *
     * @param mixed $model
     * @return string
     */
    abstract public function getTitle($model);

    /**
     * {@inheritdoc}
     */
    protected function boot()
    {
        parent::boot();

        // azioni di massa
        $this->displayMassActions();
    }

    /**
     * Azione `index`.
     */
    public function indexAction()
    {
        return $this->displayArchive();
    }

    /**
     * Azione `page`.
     */
    public function pageAction()
    {
        return $this->displayArchive($this->getParameter('page', 1, true));
    }

    /**
     * Azione `add`.
     */
    public function addAction()
    {
        return $this->addEdit();
    }

    /**
     * Azione `edit`.
     */
    public function editAction()
    {
        return $this->addEdit();
    }

    /**
     * Azione `clone`.
     */
    public function cloneAction()
    {
        return $this->addEdit();
    }

    /**
     * Azione `delete`.
     */
    public function deleteAction()
    {
        if (($model = $this->getModel()) === null) {
            throw new HttpNotFoundException('Record not found!');
        }

        try {
            $this->invokeCallBack('before_delete');

            $delete = $model->transaction(function () use ($model) {
                return $model->delete();
            });

            if ($delete) {
                $this->invokeCallBack('after_delete');

                if ($this->getRequest()->isAjax()) {
                    return $this->getHelper()->displayJsonMessage('Riga eliminata!');
                } else {
                    return new RedirectResponse($this->url('/' . $this->getParameter('path') . '/'));
                }
            }

            throw new \RuntimeException('Could not delete the record!');
        } catch (\Exception $exception) {
            if ($this->getRequest()->isAjax()) {
                return $this->getHelper()->displayJsonError($exception->getMessage());
            } else {
                throw $exception;
            }
        }
    }

    /**
     * Azione `mass`.
     */
    public function massAction()
    {
        if ($this->getRequest()->getMethod() !== 'POST') {
            throw new HttpException(405);
        }

        $ids = $_POST['ids'] ?? [];
        $action = $_POST['mass_action'] ?? null;

        if (!empty($ids)) {
            $model_class = $this->getModelClass();
            $rows = $model_class::all([
                'conditions' => [
                    'id IN (' . implode(',', array_keys($ids)) . ')',
                ],
            ]);

            foreach ($rows as $model) {
                $model->transaction(function () use ($model, $action) {
                    return $model->$action();
                });
            }
        }

        return new RedirectResponse($this->getMassActionRedirectUrl());
    }

    /**
     * Risolve le azioni `add`, `edit` e `clone`.
     */
    private function addEdit()
    {
        $this->invokeCallBack('before_initialize');

        $model = $this->getModel();
        $fields = $this->getFieldsObjects($model);
        $errors = [];

        $this->invokeCallBack('after_initialize', $model, $fields);

        if ($this->isSubmit()) {
            $this->invokeCallBack('before_update_model', $model, $fields);

            $model = $this->updateModel($model, $fields);

            $this->invokeCallBack('before_save', $model, $fields);
            $this->invokeCallBack('before_' . $this->action, $model, $fields);

            if ($this->saveModel($model)) {
                $this->invokeCallBack('after_' . $this->action, $model, $fields);
                $this->invokeCallBack('after_save', $model, $fields);
            }

            if ($this->response) {
                return $this->response;
            } elseif ($model->errors->is_empty()) {
                if ($this->getRequest()->isAjax()) {
                    $this->getHelper()->displayJsonMessage('I dati sono stati aggiornati correttamente!');
                } else {
                    match ($this->getAction()) {
                        'add'   => $this->getSession()->set('saved', 'added'),
                        'edit'  => $this->getSession()->set('saved', 'edited'),
                        'clone' => $this->getSession()->set('saved', 'cloned'),
                        default => null,
                    };

                    return $this->getAddEditResponse($model);
                }
            } else {
                foreach ($model->errors->all_as_hash() as $attribute => $message) {
                    if (is_array($message)) {
                        $message[0] = $this->getHelper()->getLocalization()->get($message[0]);
                        $errors[$attribute] = sprintf(...$message);
                    } else {
                        $errors[$attribute] = $this->getHelper()->getLocalization()->get($message);
                    }
                }

                if ($this->getRequest()->isAjax()) {
                    return $this->getHelper()->displayJsonError('Errore nel salvataggio dei dati!', ['errors' => $errors]);
                }
            }
        }

        $this->invokeCallBack('before_set_template', $model);

        $tpl_vars = [
            'title'       => $this->getTitle($model),
            'action_form' => $this->url(substr(current_url(), 7)),
            'model'       => $model,
            'errors'      => $errors,
        ];

        foreach ($fields as $field_name => $field_object) {
            $tpl_vars["field_$field_name"] = $field_object->getViewCode();
            $tpl_vars["class_$field_name"] = $this->getAction() == 'add' ? 'post' : ($model->isValid($field_name) ? 'post' : 'errorpost');
        }

        if ($this->getSession()->has('saved')) {
            $tpl_vars['message'] = $this->getHelper()->getLocalization()->get($this->getSession()->get('saved'));
        }

        $this->getSession()->del('saved');

        $this->getHelper()
            ->addCss(pongho_url(
                '/Application/Admin/Resources/views/css/style-legacy.css?v='
                . filemtime(PONGHO_PATH . '/Application/Admin/Resources/views/css/style-legacy.css')
            ));

        $this->getHelper()->getBodyView()
            ->setTemplatePath($this->getAddEditTemplatePath())
            ->assignVars($tpl_vars);

        $this->invokeCallBack('after_set_template', $model);

        return null;
    }

    /**
     * Restituisce il percorso del template da utilizzare per le action add e edit.
     *
     * @throws \UnexpectedValueException
     * @return string
     *
     * @todo Quando passeremo alla gestione delle form di amministrazione con le
     *       Pongho\Form, potremo usare un template generico per le pagine add/edit.
     */
    protected function getAddEditTemplatePath()
    {
        $ref = new \ReflectionClass($this);
        $class_dir = dirname($ref->getFileName());
        $views_dir = $class_dir . '/../../Resources/views';

        if (!file_exists($views_dir) || !is_dir($views_dir)) {
            throw new \UnexpectedValueException(sprintf('Directory "%s" not exists!', $views_dir));
        }

        $class_name = Inflector::demodulize($ref->getName());
        $filepath = $views_dir . '/' . Inflector::underscore(substr($class_name, 0, -10)) . '_edit.php';

        if (!file_exists($filepath)) {
            throw new \UnexpectedValueException(sprintf('File "%s" not exists!', $filepath));
        }

        return $filepath;
    }

    /**
     * Richiama una callback.
     */
    protected function invokeCallBack(...$args)
    {
        if (!isset($args[0])) {
            throw new \InvalidArgumentException(sprintf('Method "%s()" expects at least 1 argument, 0 given!', __METHOD__));
        }

        $callback_method = Inflector::camelize(array_shift($args), false) . 'Callback';

        if (method_exists($this, $callback_method)) {
            call_user_func_array([$this, $callback_method], $args);
        }
    }

    /**
     * Aggiorna il modello con i campi passati.
     *
     * @param mixed $model
     * @return mixed Restituisce il modello con i nuovi attributi.
     */
    protected function updateModel($model, array $fields)
    {
        foreach ($fields as $field_object) {
            $field_object->setAttributeModel();
        }

        return $model;
    }

    /**
     * Salva il modello.
     *
     * @param mixed $model
     * @return boolean
     */
    protected function saveModel($model)
    {
        return $model->save();
    }

    /**
     * Indica se è stato eseguito l’invio del modulo.
     *
     * @return boolean
     */
    protected function isSubmit()
    {
        return $this->getRequest()->getMethod() === 'POST';
    }

    /**
     * Restituisce il modello in base all’azione.
     *
     * @return Base
     * @throws \Pongho\Http\Exception\HttpNotFoundException
     */
    protected function getModel()
    {
        $model_class = $this->getModelClass();

        if ($this->action == 'add') {
            return new $model_class();
        } else {
            if (($model = $model_class::find($this->getParameter('id', null, true), ['cachable' => false])) === null) {
                throw new HttpNotFoundException();
            }

            if ($this->action == 'duplicate') {
                return clone $model;
            } else {
                return $model;
            }
        }
    }

    /**
     * Restituisce i campi del modulo.
     *
     * @param mixed $model
     * @return array
     */
    private function getFieldsObjects($model)
    {
        $fields = [];

        foreach ($this->getFields($model) as $field_options) {
            $field_name = array_shift($field_options);

            $field_options['subject'] = $this;

            if (array_key_exists('class', $field_options)) {
                $field_class = $field_options['class'];
                unset($field_options['class']);
            } else {
                $field_class = \Application\Admin\Form\TextField::class;
            }

            $fields[$field_name] = new $field_class($model, $field_name, $field_options);
        }

        return $fields;
    }

    /**
     * Restituisce la risposta dopo il salvatggio del modello.
     */
    protected function getAddEditResponse($model)
    {
        return new RedirectResponse($this->url(sprintf('/%s/edit/%d/', $this->getParameter('path'), $model->id)));
    }

    /**
     * Visualizza le azioni di massa.
     */
    protected function displayMassActions()
    {
        $this->getHelper()->getBodyView()
            ->assignVars([
                'mass_action_url'     => $this->getMassActionUrl(),
                'mass_action_buttons' => $this->getMassActionButtons(),
            ]);
    }

    /**
     * Restituisce la action per le azioni di massa.
     *
     * @return string
     */
    protected function getMassActionUrl()
    {
        return $this->url('/' . $this->getParameter('path') . '/mass/');
    }

    /**
     * Restituisce il link di redirect per le azioni di massa.
     *
     * @return string
     */
    protected function getMassActionRedirectUrl()
    {
        return $this->url('/' . $this->getParameter('path') . '/');
    }

    /**
     * Restituisce l’elenco delle azioni di massa.
     *
     * @return array
     */
    protected function getMassActions()
    {
        return ['delete'];
    }

    /**
     * Restituisce il codice per visualizzare le azioni di massa.
     *
     * @return string
     */
    protected function getMassActionButtons()
    {
        $translator = $this->getHelper()->getTranslator();

        $mass_actions = $this->getMassActions();

        switch (count($mass_actions)) {
            case 0:
                return '';

            case 1:
                $action = reset($mass_actions);

                return '<input type="hidden" name="mass_action" value="' . $action . '" class="hide" /><input type="submit" name="submit" value="' . $this->getHelper()->getLocalization()->get($action) . '" />';

            default:
                $options = '<option>' . $translator->trans('Mass actions') . '</option>';

                foreach ($mass_actions as $action) {
                    $options .= '<option value="' . $action . '">' . $this->getHelper()->getLocalization()->get($action) . '</option>';
                }

                return '<select name="mass_action">' . $options . '</select> <input type="submit" value="' . $translator->trans('Submit') . '" name="submit" />';
        }
    }

    /**
     * Visualizza la paginazione
     *
     * @param integer $page Il numero della pagina corrente.
     * @param integer $total_records Numero totale dei records.
     * @param integer $rows_per_page Numero di righe per pagina da visualizzare.
     * @param array $query Elenco dei parametri da aggiungere in query string nei collegamenti.
     */
    protected function displayPagination($page, $total_records, $rows_per_page = null, array $query = [])
    {
        if ($rows_per_page === null) {
            $rows_per_page = $this->records_per_page;
        }

        $url = preg_replace('#(page\/(\d+)\/)$#', '', $this->getRequest()->getPathInfo());

        $this->getHelper()->getBodyView()
            ->assignVar('pagination', new Pagination($url, $total_records, $page, $rows_per_page, $query));
    }

    /**
     * Genera la lista delle azioni. L’array da passare al metodo deve essere di questo tipo:
     * <code>
     * $options = array(
     *     array('Edit', 'href' => $this->_url(""), 'title' => 'Edit_message', 'class' => 'edit'),
     *     array('Delete', 'href' => $this->_url(""), 'class' => 'delete'),
     * );
     * </code>
     *
     * @return string
     */
    protected function parseActions(array $actions)
    {
        /** @var \Pongho\Template\Html $html */
        $html = $this->getContainer()->get('template_html');

        $code = [];
        foreach ($actions as $action) {
            $label = array_shift($action);
            $attributes = $action;

            $code[] = $html->generateTagBlock('a', $this->getHelper()->getLocalization()->get($label), $attributes);
        }

        return implode(' | ', $code);
    }

    /**
     * Abilita l'editor wysiwyg (TinyMCE).
     */
    protected function enableWysiwyg(array $options = [])
    {
        $translator = $this->getHelper()->getTranslator();

        $language = $this->getHelper()->getUser()->getAccount()->language->culture;

        if (!file_exists(__DIR__ . '/../Resources/vendor/tinymce/langs/' . $language . '.js')) {
            if (strlen((string) $language) > 2) {
                $language = substr((string) $language, 0, 2);

                if (!file_exists(__DIR__ . '/../Resources/vendor/tinymce/langs/' . $language . '.js')) {
                    $language = 'it';
                }
            } else {
                $language = 'it';
            }
        }

        $options = array_merge(
            [
                'language'          => $language,

                // HTML5
                'element_format'    => 'html', // sostituisce '<br />' con '<br>'
                'fix_list_elements' => true, // corregge le liste indentate male
                'schema'            => 'html5-strict',

                // CSS
                'content_css'       => pongho_url(
                    '/Application/Admin/Resources/views/css/editor-content.css?v=' . filemtime(
                        __DIR__ . '/../Resources/views/css/editor-content.css'
                    )
                ),

                // Caratteri accentati
                'entity_encoding'   => 'raw',
                'convert_urls'      => false,
            ],
            $options
        );

        $options = $this->getHelper()->filter($this, 'admin.filter_wysiwyg_options', $options);
        if (($module = $this->getHelper()->getModuleSite(false)) !== null && $module->node_type) {
            $options = $this->getHelper()->filter($this, "admin.node_{$module->node_type}.filter_wysiwyg_options", $options);
        }

        // Opzioni Normal
        $options_normal = array_merge(
            $options,
            [
                // Plugins
                'plugins'                   => 'advlist anchor charmap code contextmenu directionality fullscreen hr image link lists media paste print responsivefilemanager searchreplace tabfocus table textcolor visualblocks wordcount',

                // Menu
                'menu'                      => [
                    'edit'   => [
                        'title' => $translator->trans('Edit', [], 'wysiwyg'),
                        'items' => 'undo redo | searchreplace | selectall',
                    ],
                    'format' => [
                        'title' => $translator->trans('Format', [], 'wysiwyg'),
                        'items' => 'bold italic underline strikethrough | subscript superscript | formats | removeformat',
                    ],
                    'insert' => [
                        'title' => $translator->trans('Insert', [], 'wysiwyg'),
                        'items' => 'link anchor | hr | image media | charmap',
                    ],
                    'table'  => [
                        'title' => $translator->trans('Table', [], 'wysiwyg'),
                        'items' => 'inserttable tableprops deletetable cell row column',
                    ],
                    'tools'  => [
                        'title' => $translator->trans('Tools', [], 'wysiwyg'),
                        'items' => 'visualblocks fullscreen | print code',
                    ],
                ],

                // Strumenti
                'toolbar'                   => [
                    'formatselect | bold italic underline forecolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link unlink image media',
                ],

                // Tabelle
                'visual'                    => true,

                // FileManager
                'external_filemanager_path' => pongho_url('/Application/Admin/Resources/vendor/responsivefilemanager/'),
                'filemanager_title'         => $translator->trans('File manager', [], 'wysiwyg'),
                'external_plugins'          => [
                    'filemanager' => pongho_url('/Application/Admin/Resources/vendor/responsivefilemanager/plugin.min.js'),
                ],

                // Allineamento
                'formats'                   => [
                    'alignleft'    => [
                        'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img',
                        'classes'  => 'alignleft',
                    ],
                    'alignright'   => [
                        'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img',
                        'classes'  => 'alignright',
                    ],
                    'alignjustify' => [
                        'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img',
                        'classes'  => 'alignfull',
                    ],
                    'aligncenter'  => [
                        'selector' => 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img',
                        'classes'  => 'aligncenter',
                    ],
                ],
            ]
        );

        // Opzioni Small
        $options_small = array_merge(
            $options,
            [
                // Plugins
                'plugins' => 'link wordcount',

                // Menu
                'menubar' => false,

                // Strumenti
                'toolbar' => [
                    'bold italic underline bullist numlist link unlink',
                ],
            ]
        );

        // Opzioni Full
        $options_full = array_merge(
            $options_normal,
            [
                'plugins' => 'advlist anchor charmap code contextmenu directionality fullpage fullscreen hr image link lists media paste print responsivefilemanager searchreplace tabfocus table textcolor visualblocks wordcount',

                // Strumenti
                'toolbar' => [
                    'formatselect | fontselect | fontsizeselect | forecolor backcolor | removeformat',
                    'bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link unlink | image media',
                ],
            ]
        );

        // Opzioni Newsletter
        $options_newsletter = array_merge(
            $options_full,
            [
                // Schema
                'schema'                       => 'html4',

                // Fullpage
                'fullpage_default_doctype'     => '',
                'fullpage_default_encoding'    => 'UTF-8',
                'fullpage_default_title'       => 'Newsletter',
                'fullpage_default_font_size'   => '12px',
                'fullpage_default_font_family' => "'Lucida Grande', Lucida, Verdana, sans-serif",
                'fullpage_default_text_color'  => '#333',
                'fullpage_default_xml_pi'      => false,
                'fullpage_hide_in_source_view' => false,
            ]
        );

        unset($options_newsletter['formats']);

        // Filtri
        $options_normal = $this->getHelper()->filter($this, 'admin.filter_wysiwyg_options.normal', $options_normal);
        $options_small = $this->getHelper()->filter($this, 'admin.filter_wysiwyg_options.small', $options_small);
        $options_full = $this->getHelper()->filter($this, 'admin.filter_wysiwyg_options.full', $options_full);
        $options_newsletter = $this->getHelper()->filter($this, 'admin.filter_wysiwyg_options.newsletter', $options_newsletter);

        if (($module = $this->getHelper()->getModuleSite(false)) !== null && $module->node_type) {
            $options_normal = $this->getHelper()->filter($this, "admin.node_{$module->node_type}.filter_wysiwyg_options.normal", $options_normal);
            $options_small = $this->getHelper()->filter($this, "admin.node_{$module->node_type}.filter_wysiwyg_options.small", $options_small);
            $options_full = $this->getHelper()->filter($this, "admin.node_{$module->node_type}.filter_wysiwyg_options.full", $options_full);
        }

        // Javascript
        $options = json_encode(
            [
                'normal'     => $options_normal,
                'small'      => $options_small,
                'full'       => $options_full,
                'newsletter' => $options_newsletter,
            ]
        );

        $host_url = $this->getHelper()->getSite()->domain();

        $this->getHelper()
            ->addJavascript(pongho_url(
                '/Application/Admin/Resources/vendor/tinymce/tinymce.min.js?v='
                . filemtime(PONGHO_PATH . '/Application/Admin/Resources/vendor/tinymce/tinymce.min.js')
            ))
            ->addJavascript(pongho_url(
                '/Application/Admin/Resources/vendor/tinymce/jquery.tinymce.min.js?v='
                . filemtime(PONGHO_PATH . '/Application/Admin/Resources/vendor/tinymce/jquery.tinymce.min.js')
            ))
            ->addJavascriptInline(
                <<<JS
    Pongho.wysiwyg = {
        options: $options
    };

    /**
     * Pongho.wysiwyg.cleanUrls()
     *
     * Serve a mantenere inalterati gli shortcode inseriti negli attributi href e src dei tag
     * ATTENZIONE: gli url relativi devono essere inseriti con / iniziale altrimenti non vengono convertiti
     *
     * Dati i link
     * <p><a href="[config key='company_website' /]">url con shortcode [</a></p>
     * <p><a href="{COMPANY_WEBSITE}">url con shortcode { (non viene parsato dai nodi)</a></p>
     * <p><a href="/prova-url-shortcode/">url relativo</a></p>
     * <p><a href="http://www.google.it">url assoluto</a></p>
     *
     * L'output risulta in
     * <p><a href="[config key='company_website' /]">url con shortcode [</a></p>
     * <p><a href="{COMPANY_WEBSITE}">url con shortcode { (non viene parsato dai nodi)</a></p>
     * <p><a href="http://www.domain.it/prova-url-shortcode/">url relativo</a></p>
     * <p><a href="http://www.google.it">url assoluto</a></p>
     */
    Pongho.wysiwyg.cleanUrls = function (editor) {
        // http://www.tinymce.com/wiki.php/api4:method.tinymce.Editor.convertURL
        editor.originalConvertUrl = editor.convertURL;

        editor.convertURL = function (url, name, elm) {
            if (typeof url === 'string') {
                if (url[0] === '/' && url[1] != '/') {
                    url = '{$host_url}' + url;
                }
            }

            return editor.originalConvertUrl(url, name, elm);
        };
    };

    /**
     * Pongho.wysiwyg.fixInitialContent()
     */
    Pongho.wysiwyg.fixInitialContent = function (editor) {
        editor.on('init', function() {
            var doc = $(editor.dom.doc);

            doc.find('table').each(function () {
                var table = $(this);

                if (table.attr('width')) {
                    table.css('width', table.attr('width'));
                }

                if (table.attr('bgcolor')) {
                    table.css('background-color', table.attr('bgcolor'));
                }

                if (table.attr('align') === 'center') {
                    table.css({
                        marginLeft: 'auto',
                        marginRight: 'auto'
                    });
                }
            });

            doc.find('td, th').each(function () {
                var td = $(this);

                if (td.attr('width')) {
                    td.css('width', td.attr('width'));
                }

                if (td.attr('bgcolor')) {
                    td.css('background-color', td.attr('bgcolor'));
                }

                if (td.attr('align')) {
                    td.css('text-align', td.attr('align'));

                    td.children('table').css({
                        marginLeft: 'auto',
                        marginRight: 'auto'
                    });
                }
            });
        });
    };

    /**
     * Pongho.wysiwyg.setup()
     */
    Pongho.wysiwyg.setup = function (editor) {
        Pongho.wysiwyg.cleanUrls(editor);
        Pongho.wysiwyg.fixInitialContent(editor);
    };

    Pongho.wysiwyg.options.normal.setup = Pongho.wysiwyg.setup;
    Pongho.wysiwyg.options.small.setup = Pongho.wysiwyg.setup;
    Pongho.wysiwyg.options.full.setup = Pongho.wysiwyg.setup;
    Pongho.wysiwyg.options.newsletter.setup = Pongho.wysiwyg.setup;

$(document).ready(function () {
	"use strict";
	$('textarea.tinymce').tinymce(Pongho.wysiwyg.options.normal);
	$('textarea.tinymce-small').tinymce(Pongho.wysiwyg.options.small);
	$('textarea.tinymce-full').tinymce(Pongho.wysiwyg.options.full);
	$('textarea.tinymce-newsletter').tinymce(Pongho.wysiwyg.options.newsletter);
});
JS
            );
    }
}
