<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/25/16
 * Time: 10:00 PM
 */

namespace IMATHUZH\Qfq\Core;

use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Form\Form;
use IMATHUZH\Qfq\Core\Form\FormAsFile;
use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Renderer\BaseRenderer;
use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Store\Store;

/**
 * Class BuildFormBootstrap
 * @package qfq
 */
class FormBuilder {

    /**
     * @var array
     */
    protected $formSpec = array();  // copy of the loaded form
    /**
     * @var array
     */
    protected $feSpecAction = array(); // copy of all formElement.class='action' of the loaded form
    /**
     * @var array
     */
    protected $feSpecNative = array(); // copy of all formElement.class='native' of the loaded form
    /**
     * @var Store
     */
    protected $store = null;
    /**
     * @var Link
     */
    protected $link = null;
    /**
     * @var Database[] - Array of Database instantiated class
     */
    protected $dbArray = array();
    /**
     * @var bool|mixed
     */
    protected $dbIndexData = false;
    /**
     * @var bool|string
     */
    protected $dbIndexQfq = false;
    /**
     * @var int
     */
    protected $sip = null;
    /**
     * @var array
     */
    protected $buildRowName = null;
    /**
     * @var int
     */
    protected $ttContentUid = null;
    /**
     * @var int
     */
    protected $formId = null;
    /**
     * @var int
     */
    protected $recordId = null;


    /**
     * @param array $formSpec
     * @param array $feSpecAction
     * @param array $feSpecNative
     * @param array $db Array of 'Database' instances
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function __construct(array $formSpec, array $feSpecAction, array $feSpecNative, array $db) {
        $this->formSpec = $formSpec;
        $this->feSpecAction = $feSpecAction;
        $this->feSpecNative = $feSpecNative;
        $this->store = Store::getInstance();
//        $this->dbIndexData = $this->store->getVar(SYSTEM_DB_INDEX_DATA, STORE_SYSTEM);
        $this->dbIndexData = $formSpec[F_DB_INDEX];
        $this->dbIndexQfq = $this->store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM);

        $this->dbArray = $db;
        //$this->evaluate = new Evaluate($this->store, $this->dbArray[$this->dbIndexData]);
        // $this->showDebugInfoFlag = Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_YES, $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM));

        $this->sip = $this->store->getSipInstance();

        $this->link = new Link($this->sip, $this->dbIndexData, '', $formSpec);


        $this->buildRowName = [
            FE_TYPE_CHECKBOX => 'Native',
            FE_TYPE_DATE => 'Native',
            FE_TYPE_DATETIME => 'Native',
            'email' => 'Native',
            FE_TYPE_EXTRA => 'Native',
            FE_TYPE_TEXT => 'Native',
            FE_TYPE_EDITOR => 'Native',
            FE_TYPE_TIME => 'Native',
            FE_TYPE_NOTE => 'Native',
            FE_TYPE_PASSWORD => 'Native',
            FE_TYPE_RADIO => 'Native',
            FE_TYPE_SELECT => 'Native',
            FE_TYPE_SUBRECORD => 'Subrecord',
            FE_TYPE_UPLOAD => 'Native',
            FE_TYPE_ANNOTATE => 'Native',
            FE_TYPE_IMAGE_CUT => 'Native',
            FE_TYPE_CHAT => 'Native',
            'fieldset' => 'Fieldset',
            'pill' => 'Pill',
            'templateGroup' => 'TemplateGroup',
        ];

        //$this->symbol[SYMBOL_DELETE] = "<span class='glyphicon " . GLYPH_ICON_DELETE . "'></span>";

        $this->ttContentUid = $this->store->getVar(TYPO3_TT_CONTENT_UID, STORE_TYPO3);
        $this->formId = $this->formSpec[F_ID] ?? 0;
        $this->recordId = $this->store->getVar(CLIENT_RECORD_ID, STORE_SIP . STORE_RECORD . STORE_TYPO3 . STORE_ZERO);
    }


    /**
     * Builds complete 'form'.
     *
     * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE
     *
     * @param bool $htmlElementNameIdZero
     * @param array $latestFeSpecNative
     * @return string|array   $mode=LOAD_FORM: The whole form as HTML, $mode=FORM_UPDATE /FORM_SAVE: array of all
     *                        formElement.dynamicUpdate-yes  values/states
     * @throws \CodeException
     * @throws \DbException
     * @throws \DownloadException
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function process($mode, $htmlElementNameIdZero = false, $latestFeSpecNative = array()) {

        $this->formSpec[FORM_ELEMENTS_NATIVE] = empty($latestFeSpecNative) ? $this->feSpecNative: $latestFeSpecNative;
        $this->formSpec[FORM_ELEMENTS_ACTION] = $this->feSpecAction;

        // FE Types that must not be processed
        $skipFeTypes = [];
        if ($mode == FORM_UPDATE) {
            $skipFeTypes = [FE_TYPE_ANNOTATE];
        } else if ($mode == FORM_SAVE) {
            $skipFeTypes = [FE_TYPE_ANNOTATE];
        }

        // Remove elements that should be skipped
        $formSpec = $this->formSpec;
        for ($i = count($formSpec[FORM_ELEMENTS_NATIVE]) - 1; $i >= 0; $i--) {
            if (in_array($formSpec[FORM_ELEMENTS_NATIVE][$i][FE_TYPE], $skipFeTypes)) {
                unset($formSpec[FORM_ELEMENTS_NATIVE][$i]);
            } else {
                switch ($mode) {
                    case FORM_UPDATE:
                        // Render only Pills or FEs that are set to 'dynamic update'
                        if ((!isset($formSpec[FORM_ELEMENTS_NATIVE][$i][FE_DYNAMIC_UPDATE]) || $formSpec[FORM_ELEMENTS_NATIVE][$i][FE_DYNAMIC_UPDATE] != 'yes') && $formSpec[FORM_ELEMENTS_NATIVE][$i][FE_TYPE] != FE_TYPE_PILL) {
                            unset($formSpec[FORM_ELEMENTS_NATIVE][$i]);
                        }
                        break;
                    case FORM_SAVE:
                        // Remove FEs that are part of a template group
                        // TODO: Find better solution for str_contains
                        if ($formSpec[FORM_ELEMENTS_NATIVE][$i][FE_TG_INDEX] != 0 || str_contains($formSpec[FORM_ELEMENTS_NATIVE][$i][FE_NAME], '%')) {
                            unset($formSpec[FORM_ELEMENTS_NATIVE][$i]);
                        }
                        break;
                    default:
                        break;
                }
            }
        }
        $formSpec[FORM_ELEMENTS_NATIVE] = array_values($formSpec[FORM_ELEMENTS_NATIVE]); // re-index the array

        // Process depending on Mode
        switch ($mode) {
            case FORM_LOAD:
                $form = new Form($this->formSpec);
                $renderer = BaseRenderer::getInstance();
                return $renderer->renderFormHtml($form, RENDER_MODE_HTML);

            case FORM_UPDATE:
                $formSpec[FORM_ELEMENTS_NATIVE] = array_values($formSpec[FORM_ELEMENTS_NATIVE]); // re-index the array
                $form = new Form($formSpec);
                $renderer = BaseRenderer::getInstance();
                return $renderer->renderFormJson($form);

            case FORM_SAVE:
                // The form has already been saved at this point by QuickFormQuery->saveForm!
                // If the record was updated (r!=0), then the form is updated via json - like in FORM_UPDATE!
                // If the record was inserted (r=0), then a FORM_LOAD is performed
                $tmpRecordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_ZERO);
                $intValue = ctype_digit($tmpRecordId) ? intval($tmpRecordId) : null;
                $this->recordId = $intValue === null ? 0 : $tmpRecordId;
                if ($mode === FORM_SAVE && $this->recordId != 0) {
                    $form = new Form($formSpec);
                    $renderer = BaseRenderer::getInstance();
                    $arrayForJson = $renderer->renderFormJson($form);
                    // element-update: with 'value'
                    $md5 = $this->buildRecordHashMd5($this->formSpec[F_TABLE_NAME], $this->recordId, $formSpec[F_PRIMARY_KEY]);
                    $arrayForJson[][API_ELEMENT_UPDATE][DIRTY_RECORD_HASH_MD5][API_ELEMENT_ATTRIBUTE]['value'] = $md5;
                    return $arrayForJson;
                }
                return array(); // Save with id=0 results in FORM_LOAD - no output needed here
            default:
                break;
        }
    }

    /**
     * @param $tableName
     * @param $recordId
     * @param string $primaryKey
     *
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     */
    public function buildRecordHashMd5($tableName, $recordId, $primaryKey = F_PRIMARY_KEY_DEFAULT) {
        $record = array();

        if ($recordId != 0) {
            $record = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM `$tableName` WHERE `$primaryKey`=?", ROW_EXPECT_1, [$recordId], "Record to load not found. "
                . (FEATURE_FORM_FILE_SYNC ? FormAsFile::errorHintFormImport($tableName) : ''));
        }

        if (isset($record[F_FILE_STATS])) {
            // why: The column "fileStats" in the Form table is modified when a form is exported to a file but nothing else changes.
            unset($record[F_FILE_STATS]);
        }

        return OnArray::getMd5($record);
    }

    // TODO: Check if this is rly needed

    /**
     * @return string
     */
    private function getTabId() {
        return 'qfqTabs-' . $this->store->getVar(TYPO3_TT_CONTENT_UID, STORE_TYPO3) . '-'
            . $this->formSpec[F_ID] . '-' . $this->store->getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_SIP . STORE_RECORD . STORE_ZERO);
    }

    /** Build a new link with given string
     * Used in QuickFormQuery for multiForm delete buttons - buildChangedElements()
     *
     * @param $strDefault
     * @return string
     */
    public function getNewLink($strDefault): string {
        return $this->link->renderLink($strDefault);
    }
}