<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 3/13/17
 * Time: 9:29 PM
 */

namespace IMATHUZH\Qfq\Core\Form;

use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Evaluate;
use IMATHUZH\Qfq\Core\Store\Store;


/**
 * Class DragAndDrop
 * @package qfq
 */
class DragAndDrop {

    /**
     * @var Database
     */
    private $db = null;

    /**
     * @var Store
     */
    private $store = null;

    /**
     * @var Evaluate instantiated class
     */
    protected $evaluate = null;  // copy of the loaded form

    /**
     * @var array
     */
    private $formSpec = null;

    /**
     * @param array $formSpec F_TABLE_NAME, F_DRAG_AND_DROP_ORDER_SQL, F_DRAG_AND_DROP_INTERVAL
     * @param bool|false $phpUnit
     *
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function __construct(array $formSpec = array(), $phpUnit = false) {

        #TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}"
        $this->formSpec = $formSpec;

        $dbIndex = $formSpec[F_DB_INDEX];  //Hier muss noch die aktuelle DB ermittelt werden (kann im Form angegeben sein) - Gerade im Formular FORM Editor genau testen!
        $this->db = new Database($dbIndex);

        $this->store = Store::getInstance('', $phpUnit);
//        $this->evaluate = new Evaluate($this->store, $this->db);
    }

    /**
     * Reorder the elements according formSpec[F_DRAG_AND_DROP_ORDER_SQL]
     *
     * @return array|int
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     */
    public function process() {

        if (!is_array($this->formSpec[F_DRAG_AND_DROP_ORDER_SQL]) || count($this->formSpec[F_DRAG_AND_DROP_ORDER_SQL]) == 0) {
            throw new \UserFormException('Reorder SQL failed - expect at least one record, but got nothing. Check: Form.parameter.' . F_DRAG_AND_DROP_ORDER_SQL, ERROR_DND_EMPTY_REORDER_SQL);
        }

        $dragId = $this->store->getVar(DND_DRAG_ID, STORE_CLIENT, SANITIZE_ALLOW_ALNUMX);
        $setTo = $this->store->getVar(DND_SET_TO, STORE_CLIENT, SANITIZE_ALLOW_ALNUMX);
        $hoverId = $this->store->getVar(DND_HOVER_ID, STORE_CLIENT, SANITIZE_ALLOW_ALNUMX);

        $orderInterval = empty($this->formSpec[F_ORDER_INTERVAL]) ? 1 : $this->formSpec[F_ORDER_INTERVAL];
        $orderColumn = empty($this->formSpec[F_ORDER_COLUMN]) ? F_ORDER_COLUMN_NAME : $this->formSpec[F_ORDER_COLUMN];

//        if (!is_array($this->formSpec[F_DRAG_AND_DROP_ORDER_SQL])) {
//            return [];
//        }

        $data = $this->reorder($this->formSpec[F_DRAG_AND_DROP_ORDER_SQL], $dragId, $setTo, $hoverId, $orderColumn,
            $orderInterval, $this->formSpec[F_TABLE_NAME]);

        return $data;
    }

    /**
     * Calculate new ord values. The array rows$ contains the old order with
     * [
     *   [
     *     [DND_COLUMN_ID] => 1,
     *     [DND_COLUMN_ORD] => 10
     *   ], [
     *     [DND_COLUMN_ID] => 2,
     *     [DND_COLUMN_ORD] => 20
     *   ],
     *   ...
     * ]
     *
     * @param array $rows Array with id/ord in the old order.
     * @param int $dragId Id of the element which has been drag'ed
     * @param string $setTo DND_SET_TO_BEFORE|DND_SET_TO_AFTER   Indicates if the drop zone is before or after the $hoverId
     * @param int $hoverId Id of element where the drag'ed element has been dropped on.
     * @param string $orderColumn Table column where to save the new calculated order.
     * @param int $orderInterval Order increment.
     * @param string $tableName Table name where to update the order records.
     * @return array          Array with html-id references to update order values in the browser. Check PROTOCOL.md for 'element-update'.
     *
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function reorder(array $rows, $dragId, $setTo, $hoverId, $orderColumn, $orderInterval, $tableName) {
        $ord = $orderInterval;
        $ordDragOld = -1;
        $data = array();
        $nameId = false;
        $nameOrd = false;

        if (FEATURE_FORM_FILE_SYNC) {
            // Import Form from file if loaded record is Form/FormElement (If form file was changed, throw exception)
            // Note: This is here since this code is called outside QuickFormQuery->doForm
            $dbQfq = new Database($this->store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM));
            $formFileName = FormAsFile::importFormRecordId($dragId, $tableName, $dbQfq);
        }

        // Reorder. Get index for 'drag' and 'hover'
        foreach ($rows as $key => $row) {

            if ($nameId === false) {
                $nameId = $this->getFinalColumnName(DND_COLUMN_ID, $row);
                $nameOrd = $this->getFinalColumnName(DND_COLUMN_ORD, $row);
            }

            // The dragged element: skip old position.
            if ($row[$nameId] == $dragId) {
                $ordDragOld = $row[$nameOrd];
                continue;
            }

            // The dragged element: new position.
            if ($row[$nameId] == $hoverId) {

                switch ($setTo) {
                    case DND_SET_TO_BEFORE:
                        $data = $this->setNewOrder($tableName, $orderColumn, $dragId, $ordDragOld, $ord, $data);
                        $ord += $orderInterval;
                        $data = $this->setNewOrder($tableName, $orderColumn, $row[$nameId], $row[$nameOrd], $ord, $data);
                        break;

                    case DND_SET_TO_AFTER:
                        $data = $this->setNewOrder($tableName, $orderColumn, $row[$nameId], $row[$nameOrd], $ord, $data);
                        $ord += $orderInterval;
                        $data = $this->setNewOrder($tableName, $orderColumn, $dragId, $ordDragOld, $ord, $data);
                        break;

                    default:
                        throw new \CodeException(json_encode([ERROR_MESSAGE_TO_USER => 'Unknown "setTo" string', ERROR_MESSAGE_TO_DEVELOPER => "Token found: " . $setTo]), ERROR_UNKNOWN_TOKEN);
                }
            } else {
                $data = $this->setNewOrder($tableName, $orderColumn, $row[$nameId], $row[$nameOrd], $ord, $data);
            }
            $ord += $orderInterval;
        }

        if (FEATURE_FORM_FILE_SYNC) {
            // Export Form file
            if ($formFileName !== null) {
                $dbQfq = new Database($this->store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM));
                FormAsFile::exportForm($formFileName, $dbQfq);
            }
        }

        return $data;
    }

    /**
     * Check if there is a column called '_' . $name. If yes, return that name, else return $name.
     * If none is found, throw an exception.
     *
     * @param $name
     * @param $row
     * @return string
     * @throws \UserFormException
     */
    private function getFinalColumnName($name, $row) {

        if (!isset($row[$name]) && !isset($row['_' . $name])) {
            throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Missing column '$name' or '_$name'", ERROR_MESSAGE_TO_DEVELOPER => "Check your DND SQL statement"]),
                ERROR_MISSING_REQUIRED_PARAMETER);
        }

        return isset($row['_' . $name]) ? '_' . $name : $name;
    }

    /**
     * Helper function to update order column of single element.
     *
     * WARNING: This does not update Form file if table is FormElement!
     *
     * Form file is updated in DragAndDrop->reorder()
     *
     * @param string $tableName
     * @param string $orderColumn
     * @param int $id
     * @param int $ordOld
     * @param int $ordNew
     * @param array $data
     * @return array
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     */
    private function setNewOrder($tableName, $orderColumn, $id, $ordOld, $ordNew, array $data) {

        if ($ordNew == $ordOld) {
            return $data;
        }

        $this->db->sql("UPDATE `$tableName` SET `$orderColumn`=? WHERE `id`=?", ROW_REGULAR, [$ordNew, $id]);

        // Converting to string is necessary: JSON detects int else.
        $data[API_ELEMENT_UPDATE][DND_ORD_HTML_ID_PREFIX . $id][API_ELEMENT_CONTENT] = (string)$ordNew;

        return $data;
    }
}