<?php
/**
 *  ╔═══════════════════════════════════════════════════╗
 *  ║                                                   ║
 *  ║     ██╗  ██╗   █████╗    ██████╗                  ║
 *  ║     ██║  ██║  ██╔══██╗  ██╔═══██╗                 ║
 *  ║     ███████║  ███████║  ██║   ██║                 ║
 *  ║     ██╔══██║  ██╔══██║  ██║   ██║                 ║
 *  ║     ██║  ██║  ██║  ██║  ╚██████╔╝   SNS           ║
 *  ║                                                   ║    
 *  ║                                                   ║    
 *  ║     © 2023 HaoSNS™ All Rights Reserved            ║
 *  ║     官方网站: https://www.haosns.com                *
 *  ║     本代码由赣州乐易网络科技有限公司®提供             *
 *  ║                                                    *
 *  ║   未经授权禁止复制、传播或用于其他商业目的            *
 *  ║                                                   ║
 *  ╚═══════════════════════════════════════════════════╝
 */


namespace app\common\websocket;

use Swoole\Server;
use think\App;
use think\facade\Log;
use think\swoole\websocket\Room;
use app\common\cache\ChatCache;
use app\common\enum\ChatEnum;
use app\common\enum\ChatMsgEnum;
use think\swoole\Websocket;



use Swoole\WebSocket\Frame;
use think\Event;
use think\Request;
use think\swoole\contract\websocket\HandlerInterface;
use think\swoole\websocket\Event as WsEvent;

// class Handler implements HandlerInterface
// class Handler extends \think\swoole\websocket\Handler
class Handler
{
    public $event;

    public $ws;

    public function __construct(Event $event,App $app,  Room $room, Parser $parser, ChatCache $redis,\think\swoole\Websocket $ws)
    {
        $this->event = $event;

        $this->ws =$ws;
        
        try {
            // ,Server $server
            // $this->server = $server;
            $this->room = $room;
            $this->parser = $parser;
            $this->cache = $redis;
            $this->prefix = config('project.websocket_prefix');
            // parent::__construct($event);
        } catch (\Throwable $e) {
            Log::write('建立连接错误--'. $e->getMessage());
        }
    }

    /**
     * "onOpen" listener.
     *
     * @param Request $request
     */
    public function onOpen(Request $request)
    {
        // echo 'onOpen========';
        // $this->event->trigger('swoole.websocket.Open', $request);
        // echo '<pre>';
        // print_r($request);


        $type = $request->get('type/s'); //登录类型,user 
        $token = $request->get('token/s');
        $terminal = $request->get('terminal/d');

        
        echo "\n===========onOpen================";
        echo "\nfd:".$this->ws->getSender();
        echo "\n连接类型：".$type;
        echo "\ntoken：".$token;
        echo "\nterminal:".$terminal;
        echo "\n===========end===onOpen================\n";


        

        $user = $this->triggerEvent('login', []);
        
        try {
            $user = $this->triggerEvent('login', [
                'type' => $type,
                'terminal' => $terminal,
                'token' => $token
            ]);

            if ($user['code'] == 20001 || empty($user['data']['id'])) {
                throw new \Exception(empty($user['msg']) ? "未知错误" : $user['msg']);
            }
        } catch (\Throwable $e) {
            echo 'onOpen失败--'.$e->getMessage();
            Log::write('onOpen失败--'. $e->getMessage());
            // return $this->server->close($fd);
        }

        // 登录者绑定fd
        // $this->bindFd($type, $user['data'], $fd);

        // $this->ping($fd);

        // return $this->pushData($fd, 'login', [
        //     'msg' => '连接成功',
        //     'msg_type' => ChatMsgEnum::TYPE_TEXT
        // ]);
    }

    /**
     * "onMessage" listener.
     *
     * @param Frame $frame
     */
    public function onMessage(Frame $frame)
    {
        
        // try{
        //     
        // }catch(\Exception $e){
        //     echo 'onMassege报错了：'.$e->getMessage();
        // }
        
        echo '接收到消息了fd:'.$frame->fd;
        $this->event->trigger('swoole.websocket.Message', $frame);

        $event = $this->decode($frame->data);
        if ($event) {
            $this->event->trigger('swoole.websocket.Event', $event);
        }
    }

    /**
     * "onClose" listener.
     */
    public function onClose()
    {;
        $this->event->trigger('swoole.websocket.Close');
    }

    protected function decode($payload)
    {
        $data = json_decode($payload, true);
        if (!empty($data['type'])) {
            return new WsEvent($data['type'], $data['data'] ?? null);
        }
        return null;
    }

    public function encodeMessage($message)
    {
        if ($message instanceof WsEvent) {
            return json_encode([
                'type' => $message->type,
                'data' => $message->data,
            ]);
        }
        return $message;
    }


        /**
     * @notes 触发事件
     * @param string $event
     * @param array $data
     * @return mixed
     */
    public function triggerEvent(string $event, array $data)
    {
        return $this->event->until('swoole.websocket.' . $event, $data);
    }


    /**
     * @notes 登录者的id绑定fd
     * @param $type
     * @param $user
     * @param $fd
     */
    public function bindFd($type, $user, $fd)
    {
        $uid = $user['id'];

        // socket_fd_{fd} => ['uid' => {uid}, 'type' => {type}]
        // 以fd为键缓存当前fd的信息
        $fdKey = $this->prefix . 'fd_' . $fd;
        $fdData = [
            'uid' => $uid,
            'type' => $type,
            'nickname' => $user['nickname'],
            'avatar' => $user['avatar'],
            'terminal' => $user['terminal'],
            'token' => $user['token'],
        ];
        $this->cache->set($fdKey, json_encode($fdData, true));

        $uidKey = $this->prefix . $type . '_' . $uid;
        $this->cache->sadd($uidKey, $fd);

        // socket_user => {fd} 在线用户的所有fd
        if ($type == 'receive') {
            $groupKey = $this->prefix . 'receive';
        } else {
            $groupKey = $this->prefix . 'user';
        }
        $this->cache->sadd($groupKey, $uid);
    }


    /**
     * @notes 移除绑定
     * @param $fd
     */
    public function removeBind($fd)
    {
        $data = $this->getDataByFd($fd);
        if ($data) {
            $key = $this->prefix . 'user';
            if ($data['type'] == ChatEnum::TYPE_RECEIVE) {
                $key = $this->prefix . 'receive';
            }
            $this->cache->srem($key, $data['uid']); // socket_user => 11
            $this->cache->srem($key . '_' . $data['uid'], $fd); // socket_user_uid => fd
        }
        $this->cache->del($this->prefix . 'fd_' . $fd);
    }


    /**
     * @notes 通过登录id和登录类型获取对应的fd
     * @param $uid
     * @param $type
     * @return bool
     */
    public function getFdByUid($uid, $type)
    {
        $key = $this->prefix . $type . '_' . $uid;
        return $this->cache->sMembers($key);
    }


    /**
     * @notes 根据fd获取登录的id和登录类型
     * @param $fd
     * @return mixed|string
     */
    public function getDataByFd($fd)
    {
        $key = $this->prefix . 'fd_' . $fd;
        $result = $this->cache->get($key);
        if (!empty($result)) {
            $result = json_decode($result, true);
        }
        return $result;
    }


    /**
     * @notes ping
     * @param $fd
     * @return bool
     */
    public function ping($fd)
    {
        $data = $this->getDataByFd($fd);
        if (!empty($data)) {
            return $this->pushData($fd, 'ping', ['terminal_time' => time()]);
        }
        return true;
    }


    /**
     * @notes 推送数据
     * @param $fd
     * @param $event
     * @param $data
     * @return bool
     */
    public function pushData($fd, $event, $data)
    {
        $data = $this->parser->encode($event, $data);

        // fd非数组时转为数组
        if (!is_array($fd)) {
            $fd = [$fd];
        }

        // 向fd发送消息
        foreach ($fd as $item) {
            if ($this->server->exist($item)) {
                $this->server->push($item, $data);
            }
        }
        return true;
    }


    /**
     * @notes 在线fd
     * @param $fd
     * @return array
     */
    public function onlineFd($fd)
    {
        $result = [];

        if (empty($fd)) {
            return $result;
        }

        if (!is_array($fd)) {
            $fd = [$fd];
        }

        foreach ($fd as $item) {
            if ($this->server->exist($item)) {
                $result[] = $item;
            }
        }

        return $result;
    }
}
