<?php

namespace addons\fastim\library\swoole;

use think\Db;
use Swoole\Table;
use addons\fastim\library\CommonCode;
use addons\fastim\library\Common as extCommon;
use addons\fastim\library\pushapi\UniPush;

/**
 * Swoole 通用类
 */
class Common
{
    /**
     * 保存了uid和fd的对应关系
     * uid => fd1,fd2,fd3
     * fd最大长度为 $this->fdlen
     * 高速内存表
     * @var Swoole\Table
     */
    protected $tableUid;

    /**
     * 保存了fd的相关资料
     * 字段列表:fd,uid,tourist=是否是游客,platform=此连接的系统
     * @var Swoole\Table
     */
    protected $tableFd;

    /**
     * Swoole\WebSocket\Server
     * @var obj
     */
    protected $server;

    /**
     * Swoole\WebSocket\Frame
     * @var obj
     */
    protected $frame;

    protected static $fdlen = 32;

    public function __construct($tableSize, $server)
    {
        $this->server = $server;
        $this->table($tableSize);
    }

    public function table($size)
    {
        /**
         * uid => fd1,fd2,fd3
         * fd字段保存的fd总长度不超过 self::$fdlen 个字符
         * fd从范围为从1-1600万,一个连接占一个fd
         * 若有单用户同时打开大量窗口的需求,可以调大此值
         */
        $this->tableUid = new Table($size);
        $this->tableUid->column('fd', Table::TYPE_STRING, self::$fdlen);
        $uidCreate = $this->tableUid->create();

        /**
         * 保存了fd的相关资料
         * fd => uid,tourist=是否是游客:1=是,2=不是,platform=此连接的系统:0=web,1=安卓,2=IOS
         * @var Table
         */
        $this->tableFd = new Table($size);
        $this->tableFd->column('uid', Table::TYPE_INT, 11);
        $this->tableFd->column('tourist', Table::TYPE_INT, 1);
        $this->tableFd->column('platform', Table::TYPE_INT, 1);
        $fdCreate = $this->tableFd->create();

        if (!$uidCreate || !$fdCreate) {
            throw new Exception('create table fail! Out of memory!');
        }
    }

    public function setFrame($frame)
    {
        $this->frame = $frame;
    }

    /**
     * 检查fd字符串的长度
     */
    public static function checkFdlen($fdStr)
    {
        if (strlen($fdStr) > self::$fdlen) {
            // 删除第一个fd
            $fds = explode('|', $fdStr);
            array_shift($fds);
            $fdStr = implode('|', $fds);
            return self::checkFdlen($fdStr);
        } else {
            return $fdStr;
        }
    }

    /**
     * 绑定uid到fd
     * @param int $fd swooleFd
     * @param int $uid imUserID
     * @param int $tourist 是否是游客
     * @param int $platform 此连接的系统
     * @return bool
     */
    public function bindFd($fd, $uid, $tourist, $platform)
    {
        $platformArr = [
            'web'     => 0,
            'android' => 1,
            'ios'     => 2
        ];
        $this->tableFd->set($fd, [
            'uid'      => $uid,
            'tourist'  => $tourist ? 1 : 2,
            'platform' => $platformArr[$platform] ?? 0
        ]);
    }

    /**
     * 通过fd获得用户uid
     * @param  [type] $fd [description]
     * @return [type]     [description]
     */
    public function getUidByFd($fd)
    {
        $uid = $this->tableFd->get($fd, 'uid');
        return $uid ?? false;
    }

    /**
     * 将uid与fd绑定
     * 高性能内存table,实现单uid对多fd的功能
     * @param int $fd swooleFd
     * @param int $uid imUserID
     */
    public function bindUid($fd, $uid)
    {
        $fds = $this->tableUid->get($uid);
        if ($fds) {
            $fds['fd'] .= '|' . $fd;
            $this->tableUid->set($uid, [
                'fd' => self::checkFdlen($fds['fd'])
            ]);
        } else {
            $this->tableUid->set($uid, [
                'fd' => $fd
            ]);
        }
    }

    /**
     * 获取一个fd是否是游客登录的
     * @param int $fd swooleFd
     * @return int 1=是游客,2=不是游客
     */
    public function getUserIsTourist($fd)
    {
        $tourist = $this->tableFd->get($fd, 'tourist');
        return $tourist ? $tourist : '1';
    }

    /**
     * 获取一个fd的连接系统
     * @param int $fd swooleFd
     * @return int 0=web,1=安卓,2=IOS
     */
    public function getUserPlatform($fd)
    {
        $platform = $this->tableFd->get($fd, 'platform');
        return $platform ?? 0;
    }

    /**
     * 获取一个uid下绑定的所有fd
     * @param int $uid imUserID
     * @return array swooleFd列表
     */
    public function getFdByUid($uid)
    {
        $fds = $this->tableUid->get($uid);
        if ($fds) {
            $fds = explode('|', $fds['fd']);
            return $fds;
        } else {
            return false;
        }
    }

    /**
     * 解除uid与fd的绑定
     * @param int $fd swooleFd
     * @param int $uid imUserID
     * @return bool 是否还有fd与uid绑定
     */
    public function unbindUid($fd, $uid)
    {
        $fds = $this->tableUid->get($uid);
        if ($fds) {
            $fds   = explode('|', $fds['fd']);
            $fdKey = array_search($fd, $fds);
            if ($fdKey !== false) {
                unset($fds[$fdKey]);
            }
            if ($fds) {
                $fds = implode('|', $fds);
                $this->tableUid->set($uid, [
                    'fd' => $fds
                ]);
                return true;
            } else {
                $this->tableUid->del($uid);
                return false;
            }
        }
        return false;
    }

    /**
     * 推送消息给用户
     * @param int $uid 用户ID
     * @param string||arrar $message 消息内容
     */
    public function pushMessageToUser($uid, $message)
    {
        $recipientIdFds = $this->getFdByUid($uid);
        if (!$recipientIdFds) {
            return;
        }

        foreach ($recipientIdFds as $key => $value) {
            if ($this->server->isEstablished($value)) {
                $this->server->push($value, json_encode($message));
            }
        }
    }

    /**
     * 必填参数检查器
     * @param array $param 要检查的参数列表
     * @param array $data 数据
     * @param boolean $close 检查失败是否关闭链接
     * @param array $tip 检查失败的提示
     * @return boolean
     */
    public function requiredParamCheck($param, $data, $close = false, $tip = [])
    {
        foreach ($param as $key => $value) {
            if (!isset($data[$value]) || !$data[$value]) {
                $this->server->push($this->frame->fd, json_encode([
                    'event' => 'show_msg',
                    'data'  => isset($tip[$value]) ? $tip[$value] : '重要参数丢失,请重试~',
                    'close' => $close
                ]));
                if ($close) {
                    $this->server->close($this->frame->fd);
                }
                return false;
            }
        }
        return true;
    }

    /**
     * 发送状态消息
     * @param int $uid 用户
     * @param int $status 新状态:0=离线,1=上线,2=忙碌
     */
    public function radioStatusMessage($uid, $status)
    {
        // 从会话列表读取要通知的好友
        $dbprefix    = config('database.prefix');
        $sessionList = Db::name('fastim_session')
            ->alias('s')
            ->field('s.*, (SELECT createtime FROM `' . $dbprefix . 'fastim_message` `m` WHERE m.session_id=s.id ORDER BY m.createtime DESC LIMIT 1) as m_createtime')
            ->where('s.type', 'single')
            ->where('s.user_one|s.user_two', $uid)
            ->where('s.deleteuser', '<>', $uid)
            ->order('m_createtime desc, s.createtime desc')
            ->limit(40)
            ->select();
        foreach ($sessionList as $key => $value) {
            $friendID = ($value['user_one'] == $uid) ? $value['user_two'] : $value['user_one'];
            $this->pushMessageToUser($friendID, [
                'event' => 'update_status',
                'data'  => [
                    'user_id'    => $uid,
                    'session_id' => $value['id'],
                    'status'     => $status
                ]
            ]);
        }
    }

    /**
     * 阅读群消息
     * @param array $sessionInfo 会话资料
     */
    public function readGroupMessage($sessionInfo)
    {
        $lastReadId = Db::name('fastim_group_user')
            ->where('group_id', $sessionInfo['chat_id'])
            ->where('user_id', $sessionInfo['user']['id'])
            ->value('last_read_id');

        // 当前最大消息ID
        $maxMessageId = Db::name('fastim_message')
            ->where('group_id', $sessionInfo['chat_id'])
            ->where('status', '<=', 4)
            ->max('id');

        Db::name('fastim_group_user')
            ->where('group_id', $sessionInfo['chat_id'])
            ->where('user_id', $sessionInfo['user']['id'])
            ->update([
                'last_read_id' => $maxMessageId
            ]);

        Db::name('fastim_message')
            ->where('group_id', $sessionInfo['chat_id'])
            ->whereIn('type', implode(',', CommonCode::nonPrivilegedMessage(false)))
            ->where('status', '<', 4)
            ->where('id', '>', $lastReadId)
            ->where('sender_id', '<>', $sessionInfo['user']['id'])
            ->setInc('read_number');

        $message = Db::name('fastim_message')
            ->field('id,sender_id,read_number,message,createtime')
            ->where('group_id', $sessionInfo['chat_id'])
            ->whereIn('type', implode(',', CommonCode::nonPrivilegedMessage(false)))
            ->where('status', '<', 4)
            ->where('id', '>', $lastReadId)
            ->where('sender_id', '<>', $sessionInfo['user']['id'])
            ->select();
        foreach ($message as $key => $value) {
            $this->pushMessageToUser($value['sender_id'], [
                'event' => 'read_message',
                'data'  => [
                    'sessionType' => 'group',
                    'message_id'  => $value['id'],
                    'status'      => ($value['read_number'] >= ($sessionInfo['sessionUser']['user_count'] - 1)) ? 6 : 5,
                    'read_number' => $value['read_number']
                ]
            ]);
        }

        // 阅读无弹窗的公告
        extCommon::readGroupChatNotice($sessionInfo['chat_id'], $sessionInfo['user']['id'], '', 'all');

        // 阅读所有@消息
        Db::name('fastim_reading_log')
            ->where('type', 2)
            ->where('group_id', $sessionInfo['chat_id'])
            ->where('user_id', $sessionInfo['user']['id'])
            ->where('receipt', 0)
            ->update([
                'receipt'     => 1,
                'confirmtime' => time()
            ]);
    }

    /**
     * 新消息推送
     */
    public function newMessagePush($messageData, $sessionInfo, $recipientId)
    {
        // 会话置顶状态
        $sessionInfo['top'] = Db::name('fastim_session_top')
            ->where('user_id', $recipientId)
            ->where('session_id', $sessionInfo['id'])
            ->value('top');

        // 接受人和发送人
        if (!isset($sessionInfo['pushUser'])) {
            if ($recipientId == $sessionInfo['sessionUser']['id']) {
                $sessionInfo['pushUser']      = $sessionInfo['user'];
                $sessionInfo['recipientUser'] = $sessionInfo['sessionUser'];
            } else {
                $sessionInfo['pushUser']      = $sessionInfo['sessionUser'];
                $sessionInfo['recipientUser'] = $sessionInfo['user'];
            }
        }
        unset($sessionInfo['sessionUser'], $sessionInfo['user']);

        // 会话删除状态检查
        Db::name('fastim_session')->where('id', $sessionInfo['id'])->update([
            'deleteuser' => 0
        ]);

        // 最后消息和推送消息资料
        $lastMessage = extCommon::getLastMessage($recipientId, $sessionInfo);

        $pushTitle = isset($sessionInfo['pushUser']) ? $sessionInfo['pushUser']['nickname'] : '收到了一条新消息~';
        if ($sessionInfo['type'] == 'single') {
            self::offlineMessagePush($recipientId, $sessionInfo['id'], $pushTitle, $lastMessage['last_message']);
        } elseif ($sessionInfo['type'] == 'service') {
            self::offlineMessagePush($recipientId, 0, $pushTitle, $lastMessage['last_message']);
        }

        $this->pushMessageToUser($recipientId, [
            'event' => 'new_message',
            'data'  => [
                'messageData'          => $messageData,
                'sessionInfo'          => $sessionInfo,
                'lastMessage'          => $lastMessage,
                'unreadMessagesNumber' => extCommon::getUnreadMessagesNumber($recipientId, $sessionInfo),
                'shield'               => false
            ]
        ]);
    }

    /**
     * 离线消息推送-uniPush
     */
    public function offlineMessagePush($recipientId, $sessionId, $title, $content)
    {
        $sysUniPushSwitch = Db::name('fastim_config')->where('name', 'uni_push_switch')->value('value');
        if ($sysUniPushSwitch == 0) {
            return false;
        }
        $userNewMessagePushNotice = extCommon::imUserConfig($recipientId, 'new_message_push_notice');
        if ($userNewMessagePushNotice == 0) {
            return false;
        }

        $appConnection = false;
        $fds           = self::getFdByUid($recipientId);
        if ($fds) {
            foreach ($fds as $key => $value) {
                $platform = self::getUserPlatform($value);
                if ($platform == 1 || $platform == 2) {
                    $appConnection = true;
                    break;
                }
            }
        } else {
            $appConnection = false;
        }

        if ($appConnection) {
            return false;
        }

        $userCid = Db::name('fastim_user_push_clientid')
            ->field('clientid, platform')
            ->where('user_id', $recipientId)
            ->order('updatetime desc')
            ->select();
        if (!$userCid) {
            return false;
        }

        foreach ($userCid as $key => $value) {
            // 发送通知
            UniPush::instance()->single($value['clientid'], $title, $content, $sessionId, $value['platform']);
        }
    }

    /**
     * 发送消息到群聊
     * @param  [type]  $type         消息类型
     * @param  [type]  $message      要入库的消息数据
     * @param boolean $noPushUser 不推送给此用户
     * @param integer $oldMessageId 前台生成的临时消息ID
     * @param array $atUser at的群成员数据
     */
    public function pushMessageToGroup($type, $messageData, $noPushUser = false, $oldMessageId = 0, $atUser = [])
    {
        if (!isset($messageData['group_id']) || !$messageData['group_id']) {
            return false;
        }

        $messageData['type'] = $type; // 消息 type 通过外部传递,方便查找、识别代码

        // 取得群聊中所有的用户
        $groupUser = Db::name('fastim_group_user')->where('group_id', $messageData['group_id'])->select();

        $senderUserInfo  = extCommon::getImUserInfo($messageData['sender_id']);
        $imGroupChatInfo = extCommon::getImGroupChat($messageData['group_id']);

        $speak = ($imGroupChatInfo['speak'] == 1 && $messageData['sender_id'] != $imGroupChatInfo['leader']) ? true : false;

        $messageData['status']     = $speak ? 4 : 0;
        $messageData['createtime'] = time();
        $messageData               = extCommon::insertMessage($messageData);

        if ($messageData) {
            // 只入库不推送
            if ($speak) {
                return false;
            }

            // 此时推送发送成功的消息(以免成功通知在群消息阅读通知之后)
            if ($oldMessageId) {
                $this->server->push($this->frame->fd, json_encode([
                    'event' => 'send_message',
                    'data'  => [
                        'message_id'     => $oldMessageId,
                        'new_message_id' => $messageData['id'],
                        'status'         => $messageData['status']
                    ]
                ]));
            }

            $pushMessage['messageData'] = $messageData;
            $pushMessage['sessionInfo'] = [
                'type'        => 'group',
                'chat_id'     => $messageData['group_id'],
                'sessionUser' => $imGroupChatInfo,
                'pushUser'    => $senderUserInfo
            ];

            // 发信人群聊昵称
            $groupUserNickname = Db::name('fastim_group_user')
                ->where('group_id', $messageData['group_id'])
                ->where('user_id', $messageData['sender_id'])
                ->value('nickname');

            if ($type == 'group_chat_notice') {
                $tempNoticeMessage = $pushMessage['messageData']['message'];
            }

            // @群成员处理
            if ($atUser) {
                // 去除消息内容中不存在的被@用户-去除重复@的用户
                foreach ($atUser as $key => $value) {
                    $atNickname = '@' . trim($value['nickname'], ' ');
                    if (mb_strpos($messageData['message'], $atNickname) !== false) {
                        $insertAt[$value['id']] = [
                            'type'       => 2,
                            'group_id'   => $messageData['group_id'],
                            'user_id'    => $value['id'],
                            'data_id'    => $messageData['id'],
                            'createtime' => time()
                        ];
                    }
                }
                if ($insertAt) {
                    Db::name('fastim_reading_log')->insertAll($insertAt);
                }
            }

            foreach ($groupUser as $key => $value) {
                if ($noPushUser && $value['user_id'] == $noPushUser) {
                    continue;
                }

                if ($type == 'group_chat_notice') {
                    $pushMessage['messageData']['message'] = extCommon::formatGroupChatNotice($tempNoticeMessage, $value['user_id'], false);
                }

                $pushMessage['lastMessage'] = extCommon::getLastMessage($value['user_id'], $pushMessage['sessionInfo']);

                // 获取发信人给群聊中的收信人设置的备注
                // 不在大循环中使用 extCommon::getImUserInfo
                $friend                                             = Db::name('fastim_friendship')
                    ->field('user_id,remark')
                    ->where('user_id', $value['user_id'])
                    ->where('friend_id', $messageData['sender_id'])
                    ->find();
                $pushMessage['sessionInfo']['pushUser']['friend']   = $friend ? true : false;
                $pushMessage['sessionInfo']['pushUser']['avatar']   = $senderUserInfo['avatar'];
                $pushMessage['sessionInfo']['pushUser']['nickname'] = $senderUserInfo['nickname'];
                $newNickname                                        = extCommon::trueAttr(extCommon::nicknameSort([
                    $groupUserNickname,
                    $friend['remark'] ?? '',// 加入用户群昵称
                    $pushMessage['sessionInfo']['pushUser']['nickname']
                ], $pushMessage['sessionInfo']['pushUser']['id']));

                // 必要时重新生成收信人头像
                if ($pushMessage['sessionInfo']['pushUser']['nickname'] != $newNickname) {
                    $pushMessage['sessionInfo']['pushUser']['nickname'] = $newNickname;
                    $defaultAvatar                                      = preg_match('/^data:image/', $pushMessage['sessionInfo']['pushUser']['avatar']);
                    $pushMessage['sessionInfo']['pushUser']['avatar']   = extCommon::avatarSrc([
                        $defaultAvatar ? '' : $pushMessage['sessionInfo']['pushUser']['avatar']
                    ], $newNickname);
                }

                $pushMessage['sessionInfo']['id']    = $value['session_id'];
                $pushMessage['shield']               = $value['block_messages'] ? true : false;
                $pushMessage['unreadMessagesNumber'] = extCommon::getUnreadMessagesNumber($value['user_id'], $pushMessage['sessionInfo']);

                $pushMessage['sessionInfo']['top'] = Db::name('fastim_session_top')
                    ->where('user_id', $value['user_id'])
                    ->where('session_id', $pushMessage['sessionInfo']['id'])
                    ->value('top');

                if (!$value['block_messages']) {
                    self::offlineMessagePush($value['user_id'], $value['session_id'], $imGroupChatInfo['nickname'], $pushMessage['lastMessage']['last_message']);
                }
                self::pushMessageToUser($value['user_id'], [
                    'event' => 'new_message',
                    'data'  => $pushMessage
                ]);

                // 会话删除状态检查
                Db::name('fastim_session')->where('id', $value['session_id'])->update([
                    'deleteuser' => 0
                ]);
            }
            return $messageData;
        } else {
            return false;
        }
    }
}