<?php

class MessageModel extends Model
{
    // Constants

    const TALK_ID_ALL              = -1;
    const TALK_ID_CANCELLED        = -2;
    const USER_INFO_ALL            = 'all';
    const NEW_TALK_TIME_DELAY      =  900; // how long does it take to group message in a separate talk: 15 minutes (in seconds)
    const LAST_MESSAGES_TIME_DELAY = 3600; // maximum age of previous messages displayed in the current talk: 1 hour (in seconds)

    // Defaults

    public $is_new = 'y';

    // Constructor

    public function initialize()
    {
        parent::initialize();

        $this->talk_id      = self::TALK_ID_ALL;
        $this->to_id        = self::TALK_ID_ALL;
        $this->to_user_info = self::USER_INFO_ALL;
    }

    // Getters & setters

    public function getTableName()
    {
        return 'mirrormx_customer_chat_message';
    }

    public function getColumns()
    {
        return array('from_id', 'to_id', 'body', 'datetime', 'talk_id', 'is_new', 'from_user_info', 'to_user_info');
    }

    // Methods

    public static function repo()
    {
        return new MessageModel;
    }

    public function preSave()
    {
        $result = parent::preSave();

        if(!isset($this->id)) // New message
        {
            $fromUser    = new UserModel((array) $result['from_user_info']);
            $toUser      = new UserModel((array) $result['to_user_info']);
            $isGuest     = !$fromUser->hasRole('OPERATOR');
            $guestInTalk = false;

            $lastMessages = $this->getUserLastMessages($this->from_id, $this->to_id, $isGuest);
            $lastMessage  = null;

            // Group message into a talk

            if(empty($lastMessages)) // Generate a new talk
            {
                $this->talk_id = $this->generateTalkId();
            }
            else
            {
                $lastMessage = end($lastMessages);

                if($isGuest)
                {
                    // Continue the current talk

                    $this->talk_id = $lastMessage->talk_id;
                }
                else
                {
                    if(!$toUser->hasRole('OPERATOR')) // Target user is a guest
                    {
                        // Check if guest is already talking with an operator

                        foreach($lastMessages as $msg)
                        {
                            if($msg->to_id == $this->to_id)
                            {
                                $guestInTalk = true;

                                if($msg->from_id == $this->from_id)
                                {
                                    $lastMessage = $msg;

                                    $this->talk_id = $msg->talk_id;
                                }
                            }
                            else if($msg->to_id == self::TALK_ID_ALL)
                            {
                                $lastMessage = $msg;

                                $this->talk_id = $msg->talk_id;
                            }
                        }
                    }

                    if($this->talk_id == self::TALK_ID_ALL) $this->talk_id = $this->generateTalkId();
                }
            }

            // Assign talk to an operator

            if($this->to_id != self::TALK_ID_CANCELLED && !($fromUser->hasRole('OPERATOR') && $toUser->hasRole('OPERATOR')))
            {
                // Assign message to talk's operator, if any

                if($this->to_id == self::TALK_ID_ALL && $lastMessage && $lastMessage->to_id != self::TALK_ID_ALL) // it's sent as a "broadcast" message, but has a talk already
                {
                    if($lastMessage->from_id == $this->from_id)
                    {
                        $this->to_id        = $lastMessage->to_id;
                        $this->to_user_info = $lastMessage->to_user_info;
                    }
                    else if($lastMessage->to_id == $this->from_id)
                    {
                        $this->to_id        = $lastMessage->from_id;
                        $this->to_user_info = $lastMessage->from_user_info;
                    }
                }
                else if(!$isGuest) // Operator writes to a guest
                {
                    if($lastMessage)
                    {
                        if($lastMessage->to_id == self::TALK_ID_ALL || $lastMessage->talk_id == $this->talk_id) // operator "takes" the talk
                        {
                            $operator = $fromUser;
                            $guest    = $toUser;

                            // Assign operator to messages in the talk and mark the messages as read

                            self::$db->execute(

                                'UPDATE ' . $this->getTableName() . ' SET to_id = ?, is_new = ? WHERE from_id = ? AND (to_id = ? OR to_id = ?) AND talk_id = ?',
                                array($operator->id, 'n', $guest->id, self::TALK_ID_ALL, $operator->id, $this->talk_id)
                            );
                        }
                        else if($guestInTalk) // Operator message is cancelled
                        {
                            $this->to_id = self::TALK_ID_CANCELLED;
                            $this->id    = self::CANCEL_SAVE_ID;
                        }
                    }
                }
            }

            // Encode from/to users data

            $result = $this->getData();

            if($result['from_user_info'] !== self::USER_INFO_ALL) $result['from_user_info'] = json_encode($result['from_user_info']);
            if($result['to_user_info']   !== self::USER_INFO_ALL) $result['to_user_info']   = json_encode($result['to_user_info']);
        }

        // Store current time for new messages

        if(!isset($this->id)) $result['datetime'] = date('Y-m-d H:i:s');

        return $result;
    }

    public function postRead($data)
    {
        $data = parent::postRead($data);

        $data['from_user_info'] = json_decode($data['from_user_info'], true);
        $data['to_user_info']   = json_decode($data['to_user_info'],   true);

        return $data;
    }

    public function queryHistory($query)
    {
        if(empty($query)) $query = array();

        $q = array();

        if(isset($query['datetime'])) $q['datetime'] = $query['datetime'];

        $dbResults = $this->findBy($q, 'AND', array('datetime' => 'DESC'));

        // Group the results into "talk" groups

        $groups = array();

        if(is_array($dbResults))
        {
            foreach($dbResults as $message)
            {
                if(!isset($groups[$message->talk_id])) $groups[$message->talk_id] = array();

                $groups[$message->talk_id][] = $message;
            }
        }

        // Filter by from_user_info and to_user_info fields

        $filteredResults = array();

        if(isset($query['name']) || isset($query['mail']))
        {
            foreach($groups as $group)
            {
                $addEntry = false;

                // Add the group to search results if any entry matches the filter

                foreach($group as $message)
                {
                    $fromUser = $message->from_user_info;
                    $toUser   = $message->to_user_info;

                    if(isset($query['name']))
                    {
                        if(
                            (is_array($fromUser) && stripos($fromUser['name'], $query['name']) !== false) ||
                            (is_array($toUser)   && stripos($toUser['name'],   $query['name']) !== false)
                        )
                        {
                            $addEntry = true;
                        }
                    }

                    if(isset($query['mail']))
                    {
                        if(
                            (is_array($fromUser) && stripos($fromUser['mail'], $query['mail']) !== false) ||
                            (is_array($toUser)   && stripos($toUser['mail'],   $query['mail']) !== false)
                        )
                        {
                            $addEntry = true;
                        }
                    }

                    if($addEntry) break;
                }

                if($addEntry) $filteredResults[] = $group;
            }
        }
        else
        {
            $filteredResults = array_values($groups);
        }

        $results = $filteredResults;

        return $results;
    }

    public function clearHistory()
    {
        // Remove all model entries

        return self::$db->execute('DELETE FROM ' . MessageModel::repo()->getTableName());
    }

    public function generateTalkId()
    {
        // Find the last talk ID

        $result = self::$db->queryOne('SELECT MAX(talk_id) AS talk_id FROM ' . MessageModel::repo()->getTableName());

        return $result['talk_id'] + 1;
    }

    public function getUserLastMessages($userId, $toId, $isGuest = false)
    {
        // Get last messages

        $lastMessages = $this->findBy(array(

            'datetime' => array('>=', date('Y-m-d H:i:s', time() - self::NEW_TALK_TIME_DELAY))
        ));

        $messages = array();

        if(!empty($lastMessages))
        {
            // Filter messages related to this user

            foreach($lastMessages as $message)
            {
                if($isGuest) // Guest to operator
                {
                    if($message->from_id == $userId || $message->to_id == $userId)
                    {
                        $messages[] = $message;
                    }
                }
                else // Operator to operator / operator to guest
                {
                    if(
                         $message->from_id == $userId || $message->to_id == $userId || $message->to_id == $toId ||
                        ($message->from_id == $toId   && $message->to_id == self::TALK_ID_ALL)
                    )
                    {
                        $messages[] = $message;
                    }
                }
            }
        }

        return $messages;
    }

    public function archiveMessagesToGuest($guestId)
    {
        // Mark last messages to the guest as read

        self::$db->query('UPDATE ' . $this->getTableName() . ' SET is_new = "n" WHERE to_id = ? AND is_new = "y"', array($guestId));
    }

    public function archiveMessagesBy($userId, $messageIdFirst, $messageIdLast)
    {
        // Mark given messages to the guest as read

        self::$db->query('UPDATE ' . $this->getTableName() . ' SET is_new = "n" WHERE to_id = ? AND is_new = "y" AND id BETWEEN ? AND ?', array($userId, $messageIdFirst, $messageIdLast));
    }

    public function archiveOutdatedMessages()
    {
        // Mark messages from offline guests as read

        self::$db->query(

            'UPDATE ' . $this->getTableName() . ' m INNER JOIN ' . UserModel::repo()->getTableName() . ' u ON m.from_id = u.id ' .
            'SET m.is_new = "n" WHERE u.last_activity < "' . date('Y-m-d H:i:s', time() - UserModel::GUEST_SESSION_TIME) . '"'
        );
    }
}

?>
