<?php

namespace Velis\Bpm\Dashboard;

use ArrayObject;
use Company\Company;
use Exception;
use Laminas\View\Model\ViewModel;
use UnexpectedValueException;
use User\User;
use Velis\Acl\Role;
use Velis\App;
use Velis\Arrays;
use Velis\Bpm\Dashboard;
use Velis\Db\Exception as DbException;
use Velis\Lang;
use Velis\Model\Cacheable;
use Velis\Model\DataObject;
use Velis\Output;

/**
 * Widget prototype i core functions
 *
 * @author Michał Nosek <michal.nosek@velis.pl>
 * @author Olek Procki <olo@velis.pl>
 */
class Widget extends DataObject implements Cacheable
{
    /**
     * Widget params
     * @var array
     */
    protected $_params;


    protected static $_customers = [];
    protected static $_periodAssets = null;

    /**
     * Widget default sort mode
     * @var array
     */
    protected static $_sortMode = self::SORT_STRING;


    /**
     * Returns related SQL table name
     * @return string
     */
    protected function _getTableName()
    {
        return 'app.widget_tab';
    }


    /**
     * @return string
     */
    protected function _getListDatasource()
    {
        return 'app.widget w';
    }


    /**
     * Returns widget grid
     * @returns int
     */
    public function getGrid()
    {
        return $this['grid_no'];
    }


    /**
     * Returns widget colum number
     * @return string
     */
    public function getColumn()
    {
        return $this['column_no'];
    }


    /**
     * Returns widget row number
     * @return string
     */
    public function getRow()
    {
        return $this['row_no'];
    }


    /**
     * Returns widget colum number
     *
     * @param bool $rawJson
     * @return string
     */
    public function getOptions($rawJson = false)
    {
        $options = $this['params'] ?: $this['params_default'];

        if ($rawJson) {
            return $options;
        } else {
            return Output::jsonDecode($options);
        }
    }


    /**
     * Returns widget default options
     *
     * @param bool $rawJson
     * @return string
     */
    public function getDefaultOptions($rawJson = false)
    {
        $options = $this->params_default;

        if ($rawJson) {
            return $options;
        } else {
            return Output::jsonDecode($options);
        }
    }


    /**
     * Set new widget options
     *
     * @param array $options
     * @param int $widgetToUserId
     * @throws Exception
     */
    public function setOptions($options, $widgetToUserId): ViewModel
    {
        self::$_db->startTrans();

        try {
            $defaultOptions = (Output::jsonEncode($options) == $this->getDefaultOptions(true));
            $params = !$defaultOptions ? Output::jsonEncode($options) : null;
            $params = [
                'user_id' => App::$user->id(),
                'widget_id' => $this->id(),
                'widget_to_user_id' => $widgetToUserId,
                'params' => $params,
            ];

            if (Widget::checkDuplications($params)) {
                throw new Exception(Lang::get('GENERAL_DASHBOARD_WIDGET_IDENTICAL_PARAMETERS'));
            }

            self::$_db->execDML(
                '
                    UPDATE app.widget_to_user_tab
                    SET params = :params
                    WHERE user_id = :user_id
                        AND widget_id = :widget_id
                        AND widget_to_user_id = :widget_to_user_id
                ',
                $params
            );

            $this['params'] = $params['params'];

            $dashboard = new Dashboard();
            $widget = Arrays::getFirst($dashboard->getWidgets(['widget_to_user_id' => $widgetToUserId]));
            $model = $widget->getModel(null, false);

            self::$_db->commit();

            return $model;
        } catch (Exception $e) {
            self::$_db->rollback();
            throw $e;
        }
    }


    /**
     * Returns widget column number
     * @return string
     */
    public function getName()
    {
        return $this->getTranslatedName();
    }


    /**
     * Returns widget identifier (action name)
     *
     * @param bool $camelized
     * @return string
     */
    public function identifier($camelized = false)
    {
        if ($camelized) {
            return ucfirst(Output::toPascalCase($this->action));
        } else {
            return $this->action;
        }
    }


    /**
     * Returns widget url
     * @param bool $options
     * @return string
     */
    public function getUrl($options = false)
    {
        $url = '/dashboard/widget/' . $this['action'];
        if ($this['widget_to_user_id']) {
            $url .= '?widget_to_user_id=' . $this['widget_to_user_id'];
            if ($options) {
                $url .= '&options=1';
            }
        }
        return $url;
    }


    /**
     * Returns roles attached to widget
     * @return array|void
     */
    public function getRoles()
    {
        if ($this->role_ids) {
            return explode(',', $this->role_ids);
        }
    }


    /**
     * Returns all users attached directly to widget
     * @return array
     */
    public static function getUsers()
    {
        $query = "SELECT * FROM app.widget_user_tab";

        return self::$_db->getAll($query);
    }


    /**
     * Assign role to widget
     * @param Role $role
     */
    public function assignRole(Role $role)
    {
        $params = array(
            'widget_id' => $this->id(),
            'role_id'   => $role->id()
        );
        self::$_db->insert('app.widget_role_tab', $params);
    }


    /**
     * Removes role from widget
     * @param Role $role
     */
    public function removeRole(Role $role)
    {
        $params = [
            'widget_id' => $this->id(),
            'role_id' => $role->id(),
        ];

        self::$_db->execDML(
            '
                DELETE FROM app.widget_role_tab
                WHERE widget_id = :widget_id
                    AND role_id = :role_id
            ',
            $params
        );
    }


    /**
     * Assign user to widget
     * @param User $user
     */
    public function assignUser(User $user)
    {
        $params = array(
            'widget_id' => $this->id(),
            'user_id'   => $user->id()
        );

        self::$_db->insert('app.widget_user_tab', $params);
    }


    /**
     * Removes user from widget
     * @param User $user
     */
    public function removeUser(User $user)
    {
        $params = array(
            'widget_id' => $this->id(),
            'user_id'   => $user->id()
        );
        self::$_db->execDML(
            'DELETE FROM app.widget_user_tab
             WHERE widget_id = :widget_id
               AND user_id = :user_id',
            $params
        );
    }


    /**
     * Returns widget list
     *
     * @param int $page
     * @param array|ArrayObject $params
     * @param string $order
     * @param int $limit
     * @param string|array $fields
     * @return static[]
     * @throws \Velis\Exception
     */
    public static function getList($page = 1, $params = null, $order = null, $limit = self::ITEMS_PER_PAGE, $fields = null)
    {
        self::$_listParams = array();
        self::$_listConditions = array();

        if ($params['user_id']) {
            self::$_listDatasource = 'app.widget_user';
        }

        $result = parent::getList($page, $params, $order, $limit, $fields);
        foreach ($result as $widget) {
            $widget['identifier'] = $widget->identifier();
        }

        self::$_listDatasource = null;

        return $result;
    }


    /**
     * Sets params for execution
     *
     * @param array|ArrayObject $params
     * @return Widget
     */
    protected function _setParams($params = null)
    {
        if ($params == null) {
            $this->_params = $this->getOptions();
        } else {
            $this->_params = $params;
        }
        return $this;
    }


    /**
     * Returns widget data
     *
     * @return ViewModel
     */
    protected function _getData()
    {
        return new ViewModel();
    }


    /**
     * Returns options view model for options renderer
     * @return ViewModel
     */
    protected function _getOptionsData()
    {
        return new ViewModel();
    }


    /**
     * Returns widget params
     */
    final public function getOptionsModel(): ViewModel
    {
        $result = $this->_getOptionsData();
        if (!($result instanceof ViewModel)) {
            $result = new ViewModel($result);
        }

        $this->_setParams();
        $result->setTemplate('dashboard/widget/' . $this->identifier());

        $result->options = $this->_params;
        $result->widget  = $this;

        return $result;
    }


    /**
     * Reads required data & returns valid view model
     *
     * @param array|ArrayObject $params
     * @param bool $muteExceptions
     * @throws UnexpectedValueException
     */
    final public function getModel($params = null, $muteExceptions = true): ViewModel
    {
        $this->_setParams($params);

        try {
            $result = $this->_getData();
        } catch (DbException $e) {
            if ($muteExceptions) {
                $result = new ViewModel();
            } else {
                throw $e;
            }
        }

        if (!($result instanceof ViewModel)) {
            throw new UnexpectedValueException('Widget must return ViewModel as _getData result');
        }
        $result->setTemplate('dashboard/widget/' . $this->identifier());

        $result->options = $this->_params;
        $result->widget  = $this;

        return $result;
    }

    /**
     * Widget execution method
     *
     * @throws UnexpectedValueException
     */
    final public function __invoke(): ViewModel
    {
        return $this->getModel();
    }


    public static function checkDuplications($widget)
    {
        $query = 'SELECT COUNT(*) FROM app.widget_to_user_tab
                    WHERE user_id = :user_id
                        AND widget_id = :widget_id
                        AND widget_to_user_id != :widget_to_user_id';

        $params = [
            'user_id' => $widget['user_id'],
            'widget_id' => $widget['widget_id'],
            'widget_to_user_id' => $widget['widget_to_user_id'],
        ];

        if ($widget['params'] != null && $widget['params'] != 'null') {
            $query .= ' AND params = :params';
            $params['params'] = $widget['params'];
        } else {
            $query .= ' AND params IS NULL';
        }

        return self::$_db->getOne($query, $params);
    }


    /**
     * Gets company data for widget
     * @param int[]|int $customerIds
     * @return Company[]
     */
    public static function getCustomers($customerIds)
    {
        if (!is_array($customerIds)) {
            $customerIds = [$customerIds];
        }

        if (reset($customerIds) == 'yes') {
            $customerIds = array_keys($customerIds);
        }

        $result = [];
        $toGet  = [];

        foreach ($customerIds as $customerId) {
            if (isset(self::$_customers[$customerId]) && self::$_customers[$customerId] instanceof Company) {
                $result[$customerId] = self::$_customers[$customerId];
            } else {
                $toGet[] = $customerId;
            }
        }

        if (!empty($toGet)) {
            $newCustomers = Company::listById($toGet);
            self::$_customers = Arrays::merge(self::$_customers, $newCustomers);
            $result = Arrays::merge($result, $newCustomers);
        }

        return $result;
    }
}
