<?php

namespace IMATHUZH\Qfq\Core\Renderer;

use IMATHUZH\Qfq\Core\Form\Form;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Helper\Token;
use IMATHUZH\Qfq\Core\Renderer\Element\Bootstrap3\Bootstrap3ButtonRenderer;
use IMATHUZH\Qfq\Core\Renderer\Element\Bootstrap3\Bootstrap3SpinnerRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3CheckboxRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3ColorPickerRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3DatetimeRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3EditorRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3FieldsetRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3ImageCutRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3InputRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3NoteRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3PillRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3RadioButtonRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3SelectRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3SubrecordRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3TemplateGroupRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3\Bootstrap3UploadRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\NativeRenderer;
use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Store\Store;

class Bootstrap3Renderer extends BaseRenderer {
    public function __construct() {
        // If Bootstrap3Renderer does not specify a Renderer for a specific element, then the base version of that renderer will be used!
        parent::__construct();

        //Bootstrap3-specific Renderers
        $this->buttonRenderer = new Bootstrap3ButtonRenderer();
        $this->spinnerRenderer = new Bootstrap3SpinnerRenderer();

        $this->pillRenderer = new Bootstrap3PillRenderer();
        $this->fieldsetRenderer = new Bootstrap3FieldsetRenderer();
        $this->templateGroupRenderer = new Bootstrap3TemplateGroupRenderer();

        $this->noteRenderer = new Bootstrap3NoteRenderer();
        $this->inputRenderer = new Bootstrap3InputRenderer();
        $this->checkboxRenderer = new Bootstrap3CheckboxRenderer();
        $this->radioRenderer = new Bootstrap3RadioButtonRenderer();
        $this->selectRenderer = new Bootstrap3SelectRenderer();
        $this->subrecordRenderer = new Bootstrap3SubrecordRenderer();
        $this->editorRenderer = new Bootstrap3EditorRenderer();
        $this->datetimeRenderer = new Bootstrap3DatetimeRenderer();
        $this->uploadRenderer = new Bootstrap3UploadRenderer();
        $this->colorPickerRenderer = new Bootstrap3ColorPickerRenderer();

        $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] = 'clearfix form-group';

    }

    /**
     * @param array $formSpec Associative array containing all form specifications
     * @param string $renderMode HTML/JSON
     * @return string Rendered Form Head
     */
    protected function renderFormHead(Form $form, string $renderMode = RENDER_MODE_HTML): string {
        $formSpec = $form->specFinal;
        $output = '';

        switch ($renderMode) {
            case RENDER_MODE_HTML:
                $containerClassAttribute = Support::doAttribute('class', $formSpec[F_CLASS], true);

                // Show title / frame only if there is a title given.
                $titleHtml = "";
                if (trim($form->specFinal[F_TITLE]) != '') {
                    $classTitle = isset($form->specFinal[F_CLASS_TITLE]) ? $form->specFinal[F_CLASS_TITLE] : "qfq-form-title";

                    $btnPreviousNextSql = (!empty($form->specFinal[F_BTN_PREVIOUS_NEXT_SQL])) ? $this->renderPreviousNextButtons($form) : '';
                    $titleHtml = Support::wrapTag('<div class="row"><div class="col-md-12">', Support::wrapTag('<div class="' . $classTitle . '" name="qfq-form-title">', $form->specFinal[F_TITLE] . $btnPreviousNextSql));
                }

                // Prepare Form Tag Attributes
                $formTagAttributes = $form->formTagAttributes;
                $formTagAttributes['class'] = 'form-horizontal qfq-form';
                $formTagAttributes['data-toggle'] = 'validator';
                $formTagAttributes = OnArray::toString($formTagAttributes, '=', ' ', "'");

                // Prepare Pills (if any)
                $pillHtml = $this->renderPillNavigation($form);

                //Prepare Content Div Classes
                $contentDivClassesArray = ['tab-content', $form->specFinal[F_CLASS_BODY]];
                if ($pillHtml == '') {
                    $contentDivClassesArray[] = 'col-md-12';
                    $contentDivClassesArray[] = 'qfq-form-body'; // Make an outline on form body

                    if ($titleHtml == '') {
                        $contentDivClassesArray[] = 'qfq-form-no-title';
                    }
                }
                $contentDivClassAttribute = Support::doAttribute('class', $contentDivClassesArray);

                // Save button at bottom of form if submitButtonText is given
                $submitButtonCheck = ($form->specFinal[F_SUBMIT_BUTTON_TEXT] !== '');

                // Check if default configuration of control buttons is used
                if (!isset($form->specFinal[F_BTN_TOP_LEFT])
                    && !isset($form->specFinal[F_BTN_TOP_LEFT_ORDER])
                    && !isset($form->specFinal[F_BTN_TOP_RIGHT])
                    && !isset($form->specFinal[F_BTN_TOP_RIGHT_ORDER])
                    && !isset($form->specFinal[F_BTN_FOOTER_LEFT])
                    && !isset($form->specFinal[F_BTN_FOOTER_LEFT_ORDER])
                    && !isset($form->specFinal[F_BTN_FOOTER_RIGHT])
                    && !isset($form->specFinal[F_BTN_FOOTER_RIGHT_ORDER])) {

                    // Check Y-Store for button configuration
                    if ($this->store->getVar(F_BTN_TOP_RIGHT_ORDER, STORE_SYSTEM) === '') {
                        $buttons = $this->store->getVar(F_BTN_TOP_RIGHT_ORDER, STORE_DEFAULT);

                        // Get button configuration from D-Store
                    } else {
                        $buttons = $this->store->getVar(F_BTN_TOP_RIGHT_ORDER, STORE_SYSTEM);
                    }
                    $controlButtonsHtml = $this->renderControlButtonsHtml($form, $submitButtonCheck, $buttons);
                    $controlButtonsHtml = $this->wrapControlButtons($form, $controlButtonsHtml, F_BTN_TOP_RIGHT);

                    // Control buttons are customized
                } else {
                    $controlButtonsLeftHtml = $this->customizeControlButtons($form, $submitButtonCheck, F_BTN_TOP_LEFT);
                    $controlButtonsRightHtml = $this->customizeControlButtons($form, $submitButtonCheck, F_BTN_TOP_RIGHT);
                    $controlButtonsHtml = $this->wrapControlButtons($form, $controlButtonsLeftHtml . $controlButtonsRightHtml, F_BTN_TOP_WRAP);
                }

                $honeypotHtml = $this->renderHoneypotElements();
                $hiddenSip = $this->renderHiddenSip();
                $hiddenT3Sip = $this->renderHiddenT3Sip();
                $recordHashMd5InputHtml = $this->renderRecordHashMd5Input($form);

                $output .= <<<EOL
                                <div $containerClassAttribute>
                                    <div class="row">
                                        <div class="col-md-12">
                                            $controlButtonsHtml
                                        </div>
                                    </div>
                                    $titleHtml
                                    <div class="row">
                                        $pillHtml
                                    </div>

                                    <form $formTagAttributes>
                                        $honeypotHtml
                                        $hiddenSip
                                        $hiddenT3Sip
                                        $recordHashMd5InputHtml
                                        <div $contentDivClassAttribute>
                    EOL;
            case RENDER_MODE_JSON:
                break;
        }

        return $output;
    }

    protected function renderFormTail(Form $form, string $renderMode = RENDER_MODE_HTML): string {

        $output = ''; // close main content div of form
        $formSpec = $form->specFinal;

        switch ($renderMode) {
            case RENDER_MODE_HTML:
                //finish elements and close tag-form before wrapping save button
                $labelClass = $formSpec[FE_BS_LABEL_COLUMNS];
                $inputClass = $formSpec[FE_BS_INPUT_COLUMNS];
                $noteClass = $formSpec[FE_BS_NOTE_COLUMNS];

                // Save button at bottom of form if submitButtonText is given
                $submitButtonCheck = ($form->specFinal[F_SUBMIT_BUTTON_TEXT] !== '');
                if ($submitButtonCheck) {
                    $submitButton = $this->renderSaveButton($form, $submitButtonCheck);
                    $output .= <<<EOF
                                <div class="clearfix form-group"></div>
                                <div class="row">
                                    <div class="col-md-12" style="height: 45px; padding-top: 8px;">
                                        <div class="$labelClass qfq-label"></div>
                                        <div class="$inputClass">
                                            $submitButton
                                        </div>
                                        <div class="$noteClass qfq-note"></div>
                                    </div>
                                </div>
                                <div class="clearfix"></div>
                    EOF;
                }

                // Get customized control buttons
                $controlButtonsLeftHtml = $this->customizeControlButtons($form, $submitButtonCheck, F_BTN_FOOTER_LEFT);
                $controlButtonsRightHtml = $this->customizeControlButtons($form, $submitButtonCheck, F_BTN_FOOTER_RIGHT);

                // Check if customized control buttons are used
                if ($controlButtonsLeftHtml . $controlButtonsRightHtml !== '') {
                    $controlButtonsHtml = $this->wrapControlButtons($form, $controlButtonsLeftHtml . $controlButtonsRightHtml, F_BTN_TOP_WRAP);
                    $output .= <<<EOF
                            <div class="clearfix form-group"></div>
                            <div class="row">
                                <div class="col-md-12">
                                    $controlButtonsHtml
                                </div>
                            </div>
                EOF;
                }

            case RENDER_MODE_JSON:
                break;

        }
        $output .= '</form></div>';  //<div class="container-fluid"> === main <div class=...> around everything
        # Add $this->clientJsAll
        if ($this->clientJsAll != '') {
            $output .= '<script>' . $this->clientJsAll . '</script>';
        }

        return $output;
    }

    /**
     * Loops through all formElements of the given form and render the navigation for all pills found.
     * @param Form $form
     * @return string
     */
    private function renderPillNavigation(Form $form): string {

        // MultiForm has no pills.
        if (isset($form->specFinal[F_MULTI_SQL]) && $form->specFinal[F_MULTI_SQL] != '') return "";

        $pillButton = '';
        $pillDropdown = '';
        $htmlDropdown = '';
        $ii = 0;
        $isFirstPill = true;

        for ($i = 0; $i < count($form->formElements); $i++) {
            if ($form->formElements[$i]->attributes[FE_TYPE] !== FE_TYPE_PILL) continue;

            $htmlIdLi = $form->formElements[$i]->attributes[FE_HTML_ID] . HTML_ID_EXTENSION_PILL_LI;
            $htmlIdLiA = $form->formElements[$i]->attributes[FE_HTML_ID] . HTML_ID_EXTENSION_PILL_LI_A;
            $pillHref = '';

            $ii++;

            $attributeLiA = 'data-toggle="tab" ';

            switch ($form->formElements[$i]->attributes[FE_MODE]) {
                case FE_MODE_SHOW:
                case FE_MODE_REQUIRED:
                    $attributeLi = '';
                    $pillHref = $form->formElements[$i]->htmlAttributes["href"];
                    break;

                case FE_MODE_READONLY:
                    $pillHref = '#';
                    $attributeLi = Support::doAttribute('class', 'disabled');
                    $attributeLiA .= Support::doAttribute('class', 'noclick');
                    break;

                case FE_MODE_HIDDEN:
                    $attributeLi = Support::doAttribute('class', 'hidden');
                    break;

                default:
                    throw new \UserFormException("Unknown Mode: " . $form->formElements[$i]->attributes[FE_MODE], ERROR_UNKNOWN_MODE);
            }

            $attributeLi .= Support::doAttribute(HTML_ATTR_ID, $htmlIdLi);
            $attributeLi .= Support::doAttribute('title', $form->formElements[$i]->attributes[FE_TOOLTIP]);
            $attributeLiA .= Support::doAttribute(HTML_ATTR_ID, $htmlIdLiA);
            $a = Support::wrapTag("<a $attributeLiA " . Support::doAttribute('href', $pillHref) . ">", $form->formElements[$i]->attributes[FE_LABEL]);

            if ($isFirstPill && $form->formElements[$i]->attributes[FE_MODE] != FE_MODE_HIDDEN) {
                $attributeLi .= 'class="active" ';
                $isFirstPill = false;
                $form->formElements[$i]->isFirstPill = true;
            }

            if (!isset($form->specFinal[F_MAX_VISIBLE_PILL]) || $ii <= $form->specFinal[F_MAX_VISIBLE_PILL]) {
                $pillButton .= '<li role="presentation"' . $attributeLi . ">" . $a . "</li>";
            } else {
                $pillDropdown .= '<li ' . $attributeLi . '>' . $a . "</li>";
            }
        }

        // If there are no pills -> No HTML
        if ($isFirstPill) return "";

        // Pill Dropdown necessary?
        if (isset($form->specFinal[F_MAX_VISIBLE_PILL]) && $ii > $form->specFinal[F_MAX_VISIBLE_PILL]) {
            $htmlDropdown = Support::wrapTag('<ul class="dropdown-menu qfq-form-pill ' . $form->specFinal[F_CLASS_PILL] . '">', $pillDropdown, true);
            $htmlDropdown = '<a class="dropdown-toggle" data-toggle="dropdown" href="$pillHref" role="button">more <span class="caret"></span></a>' . $htmlDropdown;
            $htmlDropdown = Support::wrapTag('<li role="presentation" class="dropdown">', $htmlDropdown, false);
        }

        // Check for active remember last pill
        $flagRememberLastPill = ($form->specFinal[F_REMEMBER_LAST_PILL] == '1') ? 'true' : 'false';

        $tabId = 'qfqTabs-' . $this->store->getVar(TYPO3_TT_CONTENT_UID, STORE_TYPO3) . '-'
            . $form->specFinal[F_ID] . '-' . $this->store->getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_SIP . STORE_RECORD . STORE_ZERO);
        $htmlDropdown = Support::wrapTag('<ul id="' . $tabId . '" data-active-last-pill="' . $flagRememberLastPill . '" class="nav nav-pills qfq-form-pill ' . $form->specFinal[F_CLASS_PILL] . '" role="tablist">', $pillButton . $htmlDropdown);
        $htmlDropdown = Support::wrapTag('<div class="col-md-12">', $htmlDropdown);

        return $htmlDropdown;
    }


    /**
     * Build Buttons panel on top right corner of form.
     *
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function renderControlButtonsHtml(Form $form, $submitButtonCheck = false, $btnOrder = '', $debugButtonsCheck = true): string {
        // Debug Buttons (only if logged into T3 Backend)
        $debugButtonsHtml = '';
        $html = '';

        if ($form->showDebugInfoFlag && $debugButtonsCheck) {
            $debugButtons = $this->renderViewFormButton($form)
                . $this->renderEditFormElementButton()
                . $this->renderCopyFormButton($form)
                . $this->renderEditFormButton($form);
            $debugButtonsHtml = Support::wrapTag('<div class="btn-group" role="group">', $debugButtons, true);
        }

        $html .= $debugButtonsHtml;

        foreach (explode(',', $btnOrder) as $btnGroup) {
            $buttons = '';
            foreach (explode('+', $btnGroup) as $btn) {
                $btnHtml = '';
                switch ($btn) {
                    case FORM_BUTTON_SAVE:
                        $btnHtml = (!$submitButtonCheck && Support::findInSet(FORM_BUTTON_SAVE, $form->specFinal[F_SHOW_BUTTON])) ? $this->renderSaveButton($form) : '';
                        break;
                    case FORM_BUTTON_CLOSE:
                        $btnHtml = $this->renderCloseButton($form);
                        break;
                    case FORM_BUTTON_DELETE:
                        $btnHtml = $this->renderDeleteButton($form);
                        break;
                    case FORM_BUTTON_NEW:
                        $btnHtml = $this->renderNewButton($form);
                        break;
                    case FORM_BUTTON_FORM_EDIT:
                        if ($debugButtonsHtml === '' && $form->showDebugInfoFlag) $btnHtml = $this->renderEditFormButton($form);
                        break;
                    case FORM_BUTTON_FORM_ELEMENT_EDIT:
                        if ($debugButtonsHtml === '' && $form->showDebugInfoFlag) $btnHtml = $this->renderEditFormElementButton();
                        break;
                    case FORM_BUTTON_ISSUE:
                        if (Support::findInSet(FORM_BUTTON_ISSUE, $form->specFinal[F_SHOW_BUTTON])) {
                            $btnHtml = $this->renderIssueButton($form);
                        }
                        break;
                    case FORM_BUTTON_HISTORY:
                        $btnHtml = $this->renderHistoryButton($form);
                        break;
                    default:
                        $btnHtml = $this->renderCustomButton($form, $btn);
                        break;

                }
                $buttons .= $btnHtml;
            }
            $html .= Support::wrapTag('<div class="btn-group" role="group">', $buttons, true);
        }

        return $html;
    }

    /**
     * Renders the Form Save button for given form
     */
    private function renderSaveButton(Form $form, $submitButtonCheck = false): string {

        $customBtn = $this->renderCustomButton($form, FORM_BUTTON_SAVE);
        $btnCustom = 'btnCustom[' . FORM_BUTTON_SAVE . ']';

        // If parameter btnCustom[save] is used but an empty string is returned
        // (e.g. by using r:5), the empty string should be returned
        if ($customBtn !== '' || isset($form->specFinal[$btnCustom])) {
            return $customBtn;

            // Check for deprecated customized save button
        } else {
            $toolTip = ($submitButtonCheck) ? $form->specFinal[F_SUBMIT_BUTTON_TOOLTIP] : $form->specFinal[F_SAVE_BUTTON_TOOLTIP];

            if ($toolTip == 'Save') {
                if ($form->recordId == 0) {
                    $toolTip .= PHP_EOL . 'Create new record';
                } else {
                    $toolTip .= PHP_EOL . 'Record id: ' . $form->recordId;
                    $toolTip .= PHP_EOL . 'Created: ' . $this->store->getVar(HTML_ATTR_CREATED, STORE_RECORD . STORE_EMPTY);;
                    $toolTip .= PHP_EOL . 'Modified: ' . $this->store->getVar(HTML_ATTR_MODIFIED, STORE_RECORD . STORE_EMPTY);;
                }
            }
            // In debugMode every button link should show the information behind the SIP.
            if ($form->showDebugInfoFlag) {
                $toolTip .= PHP_EOL . "Table: " . $form->specFinal[F_TABLE_NAME];
            }

            $buttonSpec = array();
            $buttonSpec[BTN_HTML_ID] = 'save-button-' . $form->formTagAttributes[F_ID];
            $buttonSpec[BTN_TOOLTIP] = htmlspecialchars($toolTip);
            $buttonSpec[BTN_TEXT] = ($submitButtonCheck) ? $form->specFinal[F_SUBMIT_BUTTON_TEXT] : $form->specFinal[F_SAVE_BUTTON_TEXT];
            $buttonSpec[BTN_ICON] = ($submitButtonCheck) ? $form->specFinal[F_SUBMIT_BUTTON_GLYPH_ICON] : $form->specFinal[F_SAVE_BUTTON_GLYPH_ICON];
            $buttonSpec[BTN_CLASS] = ($submitButtonCheck) ? $form->specFinal[F_SUBMIT_BUTTON_CLASS] : $form->specFinal[F_SAVE_BUTTON_CLASS];
            $buttonSpec[BTN_ONCHANGE_CLASS] = $form->specFinal[F_BUTTON_ON_CHANGE_CLASS];
            return $this->buttonRenderer->render($buttonSpec);
        }

        return "";
    }

    /**
     * Renders the Form Close button for given form
     */
    private function renderCloseButton(Form $form): string {
        if (Support::findInSet(FORM_BUTTON_CLOSE, $form->specFinal[F_SHOW_BUTTON])) {

            $customBtn = $this->renderCustomButton($form, FORM_BUTTON_CLOSE);
            $btnCustom = 'btnCustom[' . FORM_BUTTON_CLOSE . ']';

            // If parameter btnCustom[close] is used but an empty string is returned
            // (e.g. by using r:5), the empty string should be returned
            if ($customBtn !== '' || isset($form->specFinal[$btnCustom])) {
                return $customBtn;

                // Check for deprecated customized close button
            } else {
                $buttonSpec = array();
                $buttonSpec[BTN_HTML_ID] = 'close-button-' . $form->formTagAttributes[F_ID];
                $buttonSpec[BTN_TEXT] = $form->specFinal[F_CLOSE_BUTTON_TEXT];
                $buttonSpec[BTN_TOOLTIP] = htmlspecialchars($form->specFinal[F_CLOSE_BUTTON_TOOLTIP]);
                $buttonSpec[BTN_ICON] = $form->specFinal[F_CLOSE_BUTTON_GLYPH_ICON];
                $buttonSpec[BTN_CLASS] = $form->specFinal[F_CLOSE_BUTTON_CLASS];
                return $this->buttonRenderer->render($buttonSpec);
            }
        }
        return "";
    }

    /**
     * Renders the Form Delete button for given form
     */
    private function renderDeleteButton(Form $form): string {
        // Button: Delete
        if (Support::findInSet(FORM_BUTTON_DELETE, $form->specFinal[F_SHOW_BUTTON])) {

            $customBtn = $this->renderCustomButton($form, FORM_BUTTON_DELETE);
            $btnCustom = 'btnCustom[' . FORM_BUTTON_DELETE . ']';

            // If parameter btnCustom[delete] is used but an empty string is returned
            // (e.g. by using r:5), the empty string should be returned
            if ($customBtn !== '' || isset($form->specFinal[$btnCustom])) {
                return $customBtn;

                // Check for deprecated customized delete button
            } else {
                $toolTip = $form->specFinal[F_DELETE_BUTTON_TOOLTIP];

                if ($form->showDebugInfoFlag && $form->recordId > 0) {
                    $toolTip .= PHP_EOL . "form = '" . $form->specFinal[F_FINAL_DELETE_FORM] . "'" . PHP_EOL . "r = '" . $form->recordId . "'";
                }

                $buttonSpec = array();
                $buttonSpec[BTN_HTML_ID] = 'delete-button-' . $form->formTagAttributes[F_ID];
                $buttonSpec[BTN_TEXT] = $form->specFinal[F_DELETE_BUTTON_TEXT];
                $buttonSpec[BTN_TOOLTIP] = htmlspecialchars($toolTip);
                $buttonSpec[BTN_ICON] = $form->specFinal[F_DELETE_BUTTON_GLYPH_ICON];
                $buttonSpec[BTN_CLASS] = $form->specFinal[F_DELETE_BUTTON_CLASS];
                if ($form->recordId == 0) {
                    $buttonSpec[BTN_DISABLED] = BTN_DISABLED;
                }

                return $this->buttonRenderer->render($buttonSpec);
            }
        }
        return "";
    }

    /**
     * Renders the form new button for given form
     */
    private function renderNewButton(Form $form): string {
        // Button: New
        if (Support::findInSet(FORM_BUTTON_NEW, $form->specFinal[F_SHOW_BUTTON])) {

            $customBtn = $this->renderCustomButton($form, FORM_BUTTON_NEW);
            $btnCustom = 'btnCustom[' . FORM_BUTTON_NEW . ']';

            // If parameter btnCustom[new] is used but an empty string is returned
            // (e.g. by using r:5), the empty string should be returned
            if ($customBtn !== '' || isset($form->specFinal[$btnCustom])) {
                return $customBtn;

                // Check for deprecated customized new button
            } else {
                $url = $form->getNewUrl($toolTip);

                $buttonSpec = array();
                $buttonSpec[BTN_URL] = $url;
                $buttonSpec[BTN_HTML_ID] = 'form-new-button';
                $buttonSpec[BTN_TEXT] = $form->specFinal[F_NEW_BUTTON_TEXT];
                $buttonSpec[BTN_TOOLTIP] = htmlspecialchars($toolTip);
                $buttonSpec[BTN_ICON] = $form->specFinal[F_NEW_BUTTON_GLYPH_ICON];
                $buttonSpec[BTN_CLASS] = $form->specFinal[F_NEW_BUTTON_CLASS];
                $buttonSpec[CLASS_IGNORE_HISTORY] = 1;
                return $this->buttonRenderer->render($buttonSpec);
            }
        }
        return "";
    }

    /**
     * Renders the 'Edit Form' button for given form
     * @param Form $form
     * @return void
     */
    private function renderEditFormButton(Form $form): string {
        $buttonSpec = array();
        $buttonSpec[BTN_URL] = $form->getFormEditorUrl();
        $buttonSpec[BTN_HTML_ID] = 'form-edit-button';
        $buttonSpec[BTN_TOOLTIP] = htmlspecialchars("Edit form" . PHP_EOL . PHP_EOL . OnArray::toString($this->store->getStore(STORE_SIP), ' = ', PHP_EOL, "'"));
        $buttonSpec[BTN_ICON] = GLYPH_ICON_TOOL;
        $buttonSpec[BTN_CLASS] = 'btn btn-default navbar-btn ' . CLASS_TRIGGER_STICKY_TOOLTIP;
        return $this->buttonRenderer->render($buttonSpec);
    }

    /**
     * Creates a link to open current form loaded in FormEditor
     *
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     */
    private function renderViewFormButton(Form $form): string {
        $buttonSpec = array();


        // START EXISTING CODE
        $formName = false;
        $buttonSpec[BTN_URL] = '';
        $requiredNew = '';

        switch ($form->specFinal[F_NAME]) {
            case 'form':
                $formName = $this->store->getVar(F_NAME, STORE_RECORD);
                break;
            case 'formElement':
                if (false !== ($formId = $this->store->getVar(FE_FORM_ID, STORE_SIP . STORE_RECORD))) {
                    $row = $this->databaseManager->getQfqDb()->sql("SELECT `f`.`name` FROM `Form` AS f WHERE `id`=" . $formId, ROW_EXPECT_1);
                    $rowRequiredNew = $this->databaseManager->getQfqDb()->sql("SELECT `f`.`requiredParameterNew` FROM `Form` AS f WHERE `id`=" . $formId, ROW_EXPECT_1);
                    $requiredNew = current($rowRequiredNew);
                    $formName = current($row);
                }
                break;
            default:
                return '';
        }

        if ($formName === false) {
            $buttonSpec[BTN_TOOLTIP] = "Form not 'form' or 'formElement'";
            $buttonSpec[BTN_DISABLED] = 'true';
        } else {
            if ($requiredNew === '') {
                $requiredNew = $this->store->getVar(F_REQUIRED_PARAMETER_NEW, STORE_RECORD . STORE_EMPTY);
            }
            if (trim($requiredNew) !== '') {
                $buttonSpec[BTN_TOOLTIP] = "Form has 'required new' parameters and therefore cannot be previewed.";
                $buttonSpec[BTN_DISABLED] = 'true';
            } else {
                $queryStringArray = [
                    F_ID => $this->store->getVar(SYSTEM_EDIT_FORM_PAGE, STORE_SYSTEM),
                    FORM_NAME_FORM => $formName,
                    CLIENT_RECORD_ID => 0,
                ];
                $queryString = Support::arrayToQueryString($queryStringArray);
                $sip = $this->store->getSipInstance();
                $buttonSpec[BTN_URL] = $sip->queryStringToSip($queryString);

                $buttonSpec[BTN_TOOLTIP] = htmlspecialchars("View current form with r=0" . PHP_EOL . PHP_EOL . OnArray::toString($queryStringArray, ' = ', PHP_EOL, "'"));
            }
        }

        $buttonSpec[BTN_HTML_ID] = 'form-view-' . $form->specFinal[F_ID];
        $buttonSpec[BTN_ICON] = GLYPH_ICON_VIEW;
        $buttonSpec[BTN_CLASS] = "btn btn-default navbar-btn";
        return $this->buttonRenderer->render($buttonSpec);
    }

    /**
     * Creates a Checkbox, which toggles 'hide'/'unhide' via JS, on all elements with class= CLASS_FORM_ELEMENT_EDIT.
     *
     * @return string - the rendered Checkbox
     */
    private function renderEditFormElementButton() {
        // EditFormElement Icons
        // TODO remove jquery dependency?
        $js = '$(".' . CLASS_FORM_ELEMENT_EDIT . '").toggleClass("hidden")';
        $element = "<input type='checkbox' onchange='" . $js . "'>"
            . Support::wrapTag("<span title='Toggle: Edit form element icons' class='"
                . GLYPH_ICON . ' ' . GLYPH_ICON_TASKS . "'>", '');
        $element = Support::wrapTag('<label class="btn btn-default navbar-btn">', $element);

        return Support::wrapTag('<div class="btn-group" data-toggle="buttons">', $element);
    }

    /**
     * Creates a button to open 'CopyForm' with the current form as source.
     *
     * @return string - the rendered button
     * @throws \CodeException
     * @throws \UserFormException
     */
    private function renderCopyFormButton(Form $form) {
        $buttonSpec = array();

        // Show copy icon only on form 'form' and only if there is a form loaded (id>0)
        if ($form->recordId == 0 || $form->specFinal[F_NAME] != FORM_NAME_FORM) {
            return '';
        }
        // current loaded form.
        $formId = $this->store->getVar(COLUMN_ID, STORE_RECORD . STORE_ZERO);

        $queryStringArray = [
            'id' => $this->store->getVar(SYSTEM_EDIT_FORM_PAGE, STORE_SYSTEM),
            'form' => 'copyForm',
            'r' => 0,
            'idSrc' => $formId,
        ];
        $queryString = Support::arrayToQueryString($queryStringArray);
        $sip = $this->store->getSipInstance();
        $buttonSpec[BTN_URL] = $sip->queryStringToSip($queryString);
        $buttonSpec[BTN_ICON] = GLYPH_ICON_DUPLICATE;
        $buttonSpec[BTN_TOOLTIP] = htmlspecialchars("Duplicate form" . PHP_EOL . PHP_EOL . OnArray::toString($queryStringArray, ' = ', PHP_EOL, "'"));
        $buttonSpec[BTN_CLASS] = 'btn btn-default navbar-btn';
        $buttonSpec[BTN_HTML_ID] = 'form-view-' . $form->specFinal[F_ID];

        if ($formId == 0) $buttonSpec[BTN_DISABLED] = true;

        return $this->buttonRenderer->render($buttonSpec);
    }

    /**
     * Checks for customized control buttons in given position.
     * Debug buttons will always be rendered on to top right.
     * Returns wrapped control buttons.
     *
     * @param Form $form
     * @param $submitButtonCheck
     * @param $btnPosition
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function customizeControlButtons(Form $form, $submitButtonCheck = false, $btnPosition): string {
        $html = '';
        $buttons = '';
        $btnPositionOrder = $btnPosition . 'Order';
        $debugButtonsCheck = ($btnPosition === F_BTN_TOP_RIGHT);

        if (isset($form->specFinal[$btnPositionOrder])) {
            $buttons = $form->specFinal[$btnPositionOrder];
        } elseif (isset($form->specFinal[$btnPosition])) {
            if ($this->store->getVar($btnPositionOrder, STORE_SYSTEM) === '') {
                $buttons = $this->store->getVar($btnPositionOrder, STORE_DEFAULT);
            } else {
                $buttons = $this->store->getVar($btnPositionOrder, STORE_SYSTEM);
            }
        }

        if ($buttons !== '' || $debugButtonsCheck) {
            $html = $this->renderControlButtonsHtml($form, $submitButtonCheck, $buttons, $debugButtonsCheck);
            $html = $this->wrapControlButtons($form, $html, $btnPosition);
        }

        return $html;
    }

    /**
     * Checks for customized wraps in given position.
     * Returns wrapped control buttons.
     *
     * @param Form $form
     * @param $buttons
     * @param $btnPositionWrap
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function wrapControlButtons(Form $form, $buttons, $btnPosition): string {
        $btnPositionWrap = $btnPosition . 'Wrap';

        if (isset($form->specFinal[$btnPositionWrap])) {
            $wrap = $form->specFinal[$btnPositionWrap];
        } else {
            if ($this->store->getVar($btnPositionWrap, STORE_SYSTEM) === '') {
                $wrap = $this->store->getVar($btnPositionWrap, STORE_DEFAULT);
            } else {
                $wrap = $this->store->getVar($btnPositionWrap, STORE_SYSTEM);
            }
        }

        $html = Support::wrapTag($wrap, $buttons, true);

        return $html;
    }

    /**
     * Check for btnCustom[...] and return html link if found.
     * Adds the class 'navbar-btn' to the link.
     *
     * @return array Array of custom buttons' HTML strings.
     */
    private function renderCustomButton(Form $form, $btn = ''): string {

        $btnCustom = "btnCustom[$btn]";
        // Check for customized new button
        if (isset($form->specFinal[$btnCustom])) {

            // Check if link is already rendered (deprecated)
            if (preg_match("/<[^<]+>/", $form->specFinal[$btnCustom])) {
                return $form->specFinal[$btnCustom];

                // Link needs to be rendered
            } else {
                $kvArr = KeyValueStringParser::explodeKvpSimple($form->specFinal[$btnCustom], ':', '|');

                // Check if class 'navbar-btn' is already set and add it if needed
                if (isset($kvArr['c']) && !str_contains($kvArr['c'], 'navbar-btn')) {
                    $kvArr['c'] .= ' navbar-btn';
                } elseif (!isset($kvArr['c'])) {
                    $kvArr['c'] = 'navbar-btn';
                }

                $kvString = KeyValueStringParser::unparse($kvArr, ':', '|');
                $link = new Link(Store::getSipInstance(), null, null, $form->specFinal);
                return $link->renderLink($kvString);
            }
        }

        return '';
    }

    /**
     * Builds buttons for btnPreviousNextSql and wraps them.
     *
     * @param Form $form
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function renderPreviousNextButtons(Form $form): string {
        $btnPreviousNextWrapHtml = $this->formSpec[F_BTN_PREVIOUS_NEXT_WRAP] ?? '<span class="pull-right"><div class="btn-group" role="group" style="display: flex;">';
        $link = new Link(Store::getSipInstance());
        $btnPrevious = $link->renderLink($form->specFinal[F_BTN_PREVIOUS_NEXT_SQL][F_BTN_PREVIOUS_NEXT_SQL_BTN_PREVIOUS]);
        $btnCurrent = $link->renderLink($form->specFinal[F_BTN_PREVIOUS_NEXT_SQL][F_BTN_PREVIOUS_NEXT_SQL_BTN_CURRENT]);
        $btnNext = $link->renderLink($form->specFinal[F_BTN_PREVIOUS_NEXT_SQL][F_BTN_PREVIOUS_NEXT_SQL_BTN_NEXT]);
        return Support::wrapTag($btnPreviousNextWrapHtml, $btnPrevious . $btnCurrent . $btnNext);
    }

    /**
     * Creates button that links to the page /issues
     * /issues contains a list of all issues related to the current record
     *
     * @param Form $form
     * @return string
     */
    private function renderIssueButton(Form $form): string {

        // Used in strings
        $issueTable = ISSUE_TABLE;
        $phpEol = PHP_EOL;

        // Some variables
        $formId = $form->specFinal[F_ID];
        $formName = $form->specFinal[F_NAME];
        $recordId = $form->recordId;
        $pIdUser = $this->store->getVar(SYSTEM_PID_USER, STORE_SYSTEM . STORE_ZERO);
        $emailUser = $this->store->getVar(SYSTEM_EMAIL_USER, STORE_SYSTEM . STORE_EMPTY);

        // Count all issues the user has access to:
        // User has write, read or admin access on project the issue belongs to
        // User is contact person or watcher
        // User is creator of issue
        // User is assignee of issue
        $sql = <<<EOD
            SELECT COUNT(*) AS cnt
            FROM $issueTable AS i
            LEFT JOIN Project AS pr
              ON pr.id = i.prId
            LEFT JOIN (
                SELECT IFNULL(gl.id, 0) AS hasAccess
                    , gl.xId AS prId
                FROM Glue AS gl, Grp AS gr
                WHERE gl.grId = gr.id
                  AND gl.pId = $pIdUser
                  AND FIND_IN_SET(gr.reference, 'qfq_project_access_write,qfq_project_access_read,qfq_project_access_admin')
                GROUP BY gl.xId
              ) AS userAccess
              ON userAccess.prId = i.prId
            WHERE i.xId = $recordId
              AND i.formId = $formId
              AND i.deleted = 'no'
              AND i.xId != 0
              AND CASE
                WHEN userAccess.hasAccess THEN 1
                WHEN i.accessContact = 'yes' AND ((i.pIdContact = $pIdUser AND $pIdUser != 0) OR ((i.mailContact = '$emailUser' OR FIND_IN_SET('$emailUser', i.mailWatcherSet)) AND '$emailUser' != '')) THEN 1
                WHEN i.pIdCreator = $pIdUser AND $pIdUser != 0 THEN 1
                WHEN i.pIdAssignee = $pIdUser AND $pIdUser != 0 THEN 1
                ELSE 0
              END
            GROUP BY i.xId
        EOD;

        $sql = str_replace(PHP_EOL, ' ', $sql);
        $sqlResult = $this->databaseManager->dbArray[$this->databaseManager->indexData]->sql($sql, ROW_EXPECT_0_1);
        $issuesTotal = $sqlResult[SQL_ALIAS_COUNT] ?? 0;

        // Count all issues the user has access to and are not marked as 'done':
        // User has write, read or admin access on project the issue belongs to
        // User is contact person or watcher
        // User is creator of issue
        // User is assignee of issue
        $sql = <<<EOD
            SELECT COUNT(*) AS cnt
            FROM ($issueTable AS i, Status AS st)
            LEFT JOIN Project AS pr
              ON pr.id = i.prId
            LEFT JOIN (
                SELECT IFNULL(gl.id, 0) AS hasAccess
                    , gl.xId AS prId
                FROM Glue AS gl, Grp AS gr
                WHERE gl.grId = gr.id
                  AND gl.pId = $pIdUser
                  AND FIND_IN_SET(gr.reference, 'qfq_project_access_write,qfq_project_access_read,qfq_project_access_admin')
                GROUP BY gl.xId
              ) AS userAccess
              ON userAccess.prId = i.prId
            WHERE i.xId = $recordId
              AND i.formId = $formId
              AND i.stId = st.id
              AND st.done = 'no'
              AND i.deleted = 'no'
              AND i.xId != 0
              AND CASE
                WHEN userAccess.hasAccess THEN 1
                WHEN i.accessContact = 'yes' AND ((i.pIdContact = $pIdUser AND $pIdUser != 0) OR ((i.mailContact = '$emailUser' OR FIND_IN_SET('$emailUser', i.mailWatcherSet)) AND '$emailUser' != '')) THEN 1
                WHEN i.pIdCreator = $pIdUser AND $pIdUser != 0 THEN 1
                WHEN i.pIdAssignee = $pIdUser AND $pIdUser != 0 THEN 1
                ELSE 0
              END
            GROUP BY i.xId
        EOD;

        $sql = str_replace(PHP_EOL, ' ', $sql);
        $sqlResult = $this->databaseManager->dbArray[$this->databaseManager->indexData]->sql($sql, ROW_EXPECT_0_1);
        $issuesTotalOpen = $sqlResult[SQL_ALIAS_COUNT] ?? 0;

        // Badge with number of issues
        $badgeClass = ($issuesTotalOpen > 0 && $issuesTotal != 0) ? 'qfq-badge qfq-badge-warning' : 'badge';
        $badgeContent = ($issuesTotalOpen > 0) ? $issuesTotalOpen . ' / ' . $issuesTotal : $issuesTotal;
        $badge = "<span class='$badgeClass' style=\"font-size: 11px;\">$badgeContent</span>";

        // Link specs
        $url = "/issue?formId=$formId&xid=$recordId&formName=$formName";
        $tooltip = ($issuesTotalOpen > 0 && $issuesTotal != 0) ? "Issues (open: $issuesTotalOpen, total: $issuesTotal)" : "Issues";
        $text = $form->specFinal[F_NOTE_BUTTON_TEXT] . $badge;
        $icon = GLYPH_ICON_PAPERCLIP;
        $class = 'btn btn-default navbar-btn';
        $buttonHtmlId = ISSUE_BUTTON_HTML_ID;
        $renderMode = ($recordId === 0 || $formName === ISSUE_NAME) ? 3 : 0;

        // Append debug info to tooltip
        if (Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_YES, $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM))) {
            $tooltip .= "$phpEol--{$phpEol}xId: $recordId{$phpEol}Form: $formName";
        }

        $issueLink = "p:$url|s|b:$class|G:$icon|t:$text|o:$tooltip|A:id=$buttonHtmlId|r:$renderMode";
        $link = new Link(Store::getSipInstance(), $this->databaseManager->indexData);

        return $link->renderLink($issueLink);
    }

    /**
     * Creates a button that open a modal dialogue which contains a change history of the current record
     *
     * @param Form $form
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \IMATHUZH\Qfq\Core\Exception\RedirectResponse
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function renderHistoryButton(Form $form): string {

        $buttonSpec = array();
        $phpEol = PHP_EOL;

        // Check form parameter and qfq configuration if the history button should be shown
        $qfqConfigShowHistory = $this->store->getVar(SYSTEM_SHOW_HISTORY, STORE_SYSTEM) === '1';
        $formConfigShowHistory = $form->specFinal[F_SHOW_HISTORY] ?? false;
        $formShowHistoryTitle = $form->specFinal[F_SHOW_HISTORY_TITLE] ?? '';
        $formShowHistoryFeUserSet = $form->specFinal[F_SHOW_HISTORY_FE_USER_SET] ?? '';

        // Prioritize form parameter over qfq configuration
        if ($formConfigShowHistory === '0' || (!$qfqConfigShowHistory && !$formConfigShowHistory)) {
            return '';
        }

        $tooltip = '';
        $recordId = $form->recordId;
        $formId = $form->specFinal[F_ID];
        $table = $form->specFinal[F_TABLE_NAME];

        if ($form->showDebugInfoFlag) {
            $tooltip .= "{$phpEol}recordId: $recordId{$phpEol}formId: $formId{$phpEol}table: $table{$phpEol}{$phpEol}";
        }

        // Check if there exists at least one FormSubmitLog record
        $checkRecords = $this->databaseManager->dbArray[$this->databaseManager->indexData]->checkFormHistoryRecords($formId, $recordId, $formShowHistoryFeUserSet);

        if ($checkRecords) {

            // Get last 11 FormSubmitLog entries for the current form
            $rows = $this->databaseManager->dbArray[$this->databaseManager->indexData]->getAllFormHistoryRecords($table, $recordId, $formShowHistoryFeUserSet, 11);
            $more = count($rows) > 10 ? '...' : '';

            // if there are more than 10 entries remove the last one
            if (count($rows) > 10) {
                array_pop($rows);
            }

            // extract the sql result for tooltip looks like table: created | feUser | formName
            foreach ($rows as $row) {
                $tooltip .= implode(' | ', $row) . PHP_EOL;
            }

            $tooltip .= $more;

        } else {
            $tooltip .= 'No history records for this form.';
        }

        // Build history button
        $mode = RECORD_HISTORY_LOAD;
        $baseUrl = $this->store->getVar(SYSTEM_BASE_URL, STORE_SYSTEM);
        $tablesorterViewData[RECORD_HISTORY_DATA] = $form->evaluate->parse("{{'qfq-history-$formId' AS _tablesorter-view-saver}}");
        $tablesorterViewJson = htmlspecialchars(json_encode($tablesorterViewData, JSON_UNESCAPED_SLASHES));
        $sipText = "r:8|s|U:&formId=$formId&r=$recordId&mode=$mode&table=$table&showHistoryFeUserSet=$formShowHistoryFeUserSet|";
        $link = new Link(Store::getSipInstance());
        $sipData = $link->renderLink($sipText);
        $value = $this->store->getVar(SYSTEM_FORM_HISTORY_BUTTON_DEFAULT, STORE_DEFAULT);
        $value .= "|A:data-sip='$sipData' data-base-url='$baseUrl' data-tablesorter-view-json='$tablesorterViewJson' data-history-title='$formShowHistoryTitle'|t:<i style='font-size:12pt;' class='fas fa-history'></i>|o:$tooltip";

        $text = '<i style="font-size:12pt;" class="fas fa-history"></i>';
        $class = 'btn btn-default navbar-btn qfq-history-btn';

        $buttonSpec['buttonHtmlId'] = 'form-history-' . $form->formTagAttributes[F_ID];
        $buttonSpec['tooltip'] = $tooltip;
        $buttonSpec['class'] = $class;
        $buttonSpec['text'] = $text;
        $buttonSpec['sip'] = $sipData;
        $buttonSpec['baseUrl'] = $baseUrl;
        $buttonSpec['tablesorterView'] = $tablesorterViewJson;
        $buttonSpec['historyTitle'] = $formShowHistoryTitle;
        if (!$checkRecords) $buttonSpec['disabled'] = '';

        return $this->buttonRenderer->render($buttonSpec);
    }
}