<?php
/** 
 * @package     VikRestaurants
 * @subpackage  core
 * @author      E4J s.r.l.
 * @copyright   Copyright (C) 2023 E4J s.r.l. All Rights Reserved.
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 * @link        https://vikwp.com
 */

// No direct access
defined('ABSPATH') or die('No script kiddies please!');

VRELoader::import('library.operator.user');

/**
 * Helper class used to track the operator logs.
 *
 * Whenever a method asks to specify a group, it is possible to use
 * only one of the following values:
 * - "0" for restaurant reservations;
 * - "1" for take-away orders.
 *
 * @since 1.8
 */
class VREOperatorLogger
{
    /**
     * An array containing the cached reservations.
     * The first-level children can be only the groups:
     * - 0 restaurants;
     * - 1 take-away.
     *
     * @var array
     */
    protected $cache = array();

    /**
     * The singleton instance.
     *
     * @var self
     */
    protected static $instance = null;

    /**
     * Returns an instance of this class, only creating it
     * if it doesn't already exist.
     *
     * @return  self
     */
    public static function getInstance()
    {
        if (static::$instance === null)
        {
            static::$instance = new static();
        }

        return static::$instance;
    }

    /**
     * Caches internally the specified order details.
     *
     * @param   mixed    $order  Either the order ID or the record.
     * @param   integer  $group  The group to which the order belong.
     *
     * @return  self     This object to support chaining.
     *
     * @uses    load()
     */
    public function cache($order, $group)
    {
        if (is_scalar($order))
        {
            // retrieve order details is the ID was passed
            $order = $this->load($order, $group);
        }

        // validate specified group
        $group = $group == 1 ? 1 : 0;

        // make sure the order exists before caching it
        if ($order)
        {
            // make sure the group is set
            if (!isset($this->cache[$group]))
            {
                $this->cache[$group] = array();
            }

            // cache the order
            $this->cache[$group][$order->id] = $order;
        }

        return $this;
    }

    /**
     * Checks if the specified order is already cached and returns it.
     *
     * @param   mixed    $order  Either the order ID or the record.
     * @param   integer  $group  The group to which the order belong.
     *
     * @return  mixed    The  
     */
    public function getCached($order, $group)
    {
        if ($order)
        {
            if (!is_scalar($order))
            {
                $order = $order->id;
            }

            // validate specified group
            $group = $group == 1 ? 1 : 0;

            // check if the order is set
            if (isset($this->cache[$group][$order]))
            {
                // returned cached
                return $this->cache[$group][$order];
            }
        }

        return null;
    }

    /**
     * Loads from the database the specified order/reservations.
     *
     * @param   integer  $id     The order ID.
     * @param   integer  $group  The group to which the order belong.
     *
     * @return  mixed    The order object is exists, null otherwise.
     */
    public function load($id, $group)
    {
        if ((int) $id <= 0)
        {
            // do not need to query in case of invalid ID
            return null;
        }

        // define loading options
        $options = [
            // ignore cache when retrieving the order because the same details might
            // have been already retrieved to keep the previous information
            'ignore_cache' => true,
            // preload all the details, which might be used during the comparison
            'preload' => true,
            // avoid translating the information
            'translate' => false,
        ];

        if ($group == 1)
        {
            // get take-away order
            $order = VREOrderFactory::getOrder($id, null, $options);
        }
        else
        {
            // get restaurant reservation
            $order = VREOrderFactory::getReservation($id, null, $options);
        }

        return $order;
    }

    /**
     * Generates a log based on the specified order details.
     * The fetched log will be automatically stored and assigned
     * to the current operator.
     *
     * @param   mixed    $order  Either the order ID or the record.
     * @param   integer  $group  The group to which the order belong.
     * @param   mixed    $log    The log subject. If not specified, it
     *                           will be automatically fetched.
     *
     * @return  mixed    The log ID on success, false otherwise.
     *
     * @uses    store()
     */
    public function generate($order, $group, $log = null)
    {
        // get cached order, if any
        $prev = $this->getCached($order, $group);

        // load order in case an ID was passed
        if (is_scalar($order))
        {
            // load order
            $order = $this->load($order, $group);
        }

        try
        {
            // retrieve operator data
            $operator = VREOperatorUser::getInstance();
        }
        catch (Exception $e)
        {
            /**
             * Use a mockup to keep tracking even if the user is not an operator.
             * 
             * @since 1.11.1
             */
            $operator = new VREOperatorUserMockup([
                'keep_track' => 1,
            ]);
        }

        if (!$operator->isTrackable())
        {
            // do not proceed in case the operator shouldn't be tracked
            return false;
        }

        $data = array();

        // import order comparator helper class
        VRELoader::import('library.order.comparator');

        // fetch differences
        $diff = VREOrderComparator::diff($order, $prev);

        if ($diff)
        {
            // add differences to JSON data
            $data['diff'] = $diff;
        }

        // find items differences
        $items = VREOrderComparator::diffItems($order, $prev);

        if ($items)
        {
            // add items differences to JSON data
            $data['items'] = $items;
        }

        // fetch log if empty
        if (!$log)
        {
            // try to detect a log subject for the restaurant
            if ($group == 0)
            {
                // if the previous order is null, the order didn't exist
                if ($prev == null)
                {
                    // reservation created
                    $log = 'VROPLOGRESTAURANTINSERT';
                }
                // if the current order is null, the order has probably been deleted
                else if ($order == null)
                {
                    // reservation deleted
                    $log = 'VROPLOGRESTAURANTDELETE';
                }
                // use specific text in case the "diff" attribute contains only the "table" key
                else if (isset($data['diff']) && array_keys($data['diff']) == ['table'])
                {
                    // table changed
                    $log = 'VROPLOGRESTAURANTTABLECHANGED';
                }
                // use specific text in case the "diff" attribute contains only the "status" key (set to CONFIRMED)
                else if (isset($data['diff']) && array_keys($data['diff']) == ['status'] && $order->statusRole === 'APPROVED')
                {
                    // reservation confirmed
                    $log = 'VROPLOGRESTAURANTCONFIRMED';
                }
                // fallback to update
                else
                {
                    // reservation updated
                    $log = 'VROPLOGRESTAURANTUPDATE';
                }
            }
            // try to detect a log subject for the take-away
            else
            {
                // if the previous order is null, the order didn't exist
                if ($prev == null)
                {
                    // order created
                    $log = 'VROPLOGTAKEAWAYINSERT';
                }
                // if the current order is null, the order has probably been deleted
                else if ($order == null)
                {
                    // order deleted
                    $log = 'VROPLOGTAKEAWAYDELETE';
                }
                // use specific text in case the "diff" attribute contains only the "status" key (set to CONFIRMED)
                else if (isset($data['diff']) && array_keys($data['diff']) == ['status'] && $order->statusRole === 'APPROVED')
                {
                    // order confirmed
                    $log = 'VROPLOGTAKEAWAYCONFIRMED';
                }
                // fallback to update
                else
                {
                    // order updated
                    $log = 'VROPLOGTAKEAWAYUPDATE';
                }
            }
        }

        // check if the log exceeds the maximum limit of 256 characters
        if (strlen($log) > 256)
        {
            // include the remaining log subject
            $data['readmore'] = substr($log, 256);
            // keep first 256 characters in subject
            $log = substr($log, 0, 256);
        }

        // update order cache after storing the log
        $this->cache($order, $group);

        return $this->store($operator, $log, (int) $group, (int) $order->id, $data);
    }

    /**
     * Stores the log.
     *
     * @param   VREOperatorUser  $operator  The operator instance (@since 1.11.1 changed from INT).
     * @param   string           $log       The log subject.
     * @param   int              $group     The group of the log.
     * @param   int              $order     The order ID.
     * @param   mixed            $data      The order data.
     *
     * @return  mixed  The log ID on success, false otherwise.
     */
    public function store(VREOperatorUser $operator, string $log, int $group, int $order = 0, $data = null)
    {
        $record = new stdClass;
        // set operator ID
        $record->id_operator = $operator->id;
        // set order ID
        $record->id_reservation = $order;
        // set log subject (MAX 256 characters)
        $record->log = $log;
        // set log to current time
        $record->createdon = VikRestaurants::now();
        // set group
        $record->group = $group == 1 ? 2 : 1;
        
        if ($data)
        {
            // set the order JSON data
            $record->content = is_string($data) ? $data : json_encode($data);
        }

        // register author name as well
        $record->author = trim($operator->firstname . ' ' . $operator->lastname);

        // save log
        $res = JFactory::getDbo()->insertObject('#__vikrestaurants_operator_log', $record, 'id');

        if ($res && !empty($record->id))
        {
            // return log ID on success
            return $record->id;
        }

        // an error occurred
        return false;
    }
}
