<?php

/**
 * FastIm v1.0.0
 * FastAdmin企业IM客服系统
 * https://www.fastadmin.net/store/fastim.html
 *
 * Copyright 2021 FastAdmin:thinkphp
 *
 * FastAdmin企业IM客服系统不是开源产品，所有文字、图片、样式、风格等版权归企业IM客服系统作者所有，如有复制、仿冒、抄袭、盗用，FastAdmin和企业IM客服系统作者将追究法律责任
 *
 * Released on: November 8, 2021
 */

namespace addons\fastim\library\swoole;

use think\Db;
use Swoole\Server\Helper;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server as WebSocketServer;
use Exception;
use Swoole\Timer;

/**
 * Swoole WebSocket Server 命令行服务类
 * 此文件的修改不支持热更新，请于更新后重启swoole-websocket服务
 */
class WebSocket
{
    protected $monitor;
    protected $lastMtime;
    protected $config;

    /**
     * Swoole对象
     * @var object
     */
    protected $swoole;

    /**
     * Socket的类型
     * @var int
     */
    protected $sockType = SWOOLE_SOCK_TCP;

    /**
     * 运行模式
     * @var int
     */
    protected $mode = SWOOLE_PROCESS;

    /**
     * swooleCommon 类实例
     */
    protected $swooleCommon;

    /**
     * 支持的响应事件
     * @var array
     */
    protected $event = [
        'Start',
        'Shutdown',
        'WorkerStart',
        'WorkerStop',
        'WorkerExit',
        'Close',
        'Task',
        'Finish',
        'PipeMessage',
        'WorkerError',
        'ManagerStart',
        'ManagerStop',
        'Open',
        'Message',
        'HandShake',
        'Request'
    ];

    function __construct()
    {
        $this->config = get_addon_config('fastim');

        if ($this->config['wss_switch']) {
            if (file_exists($this->config['ssl_cert_file']) && file_exists($this->config['ssl_key_file'])) {
                $this->sockType = SWOOLE_SOCK_TCP | SWOOLE_SSL;
            } else {
                throw new Exception('SSL certificate file does not exist!');
            }
        }

        $this->swoole = new WebSocketServer('0.0.0.0', $this->config['websocket_port'], $this->mode, $this->sockType);
    }

    /**
     * Worker 进程启动
     * @param  $server
     * @param  $worker_id
     */
    public function onWorkerStart($server, $worker_id)
    {
        $this->lastMtime = time();

        if (0 == $worker_id && $this->monitor) {
            $this->monitor($server);
        }

        // print_r(get_included_files());// 查看不支持热更新的文件列表

        // 保持mysql链接可用性
        $server->tick(27000, function () {
            Db::execute("SELECT 1");
        });
    }

    /**
     * 链接握手成功
     * @param  $server
     * @param  $frame
     */
    public function onOpen($ws, $request)
    {
        $ws->push($request->fd, json_encode([
            'event' => 'open'
        ]));
    }

    /**
     * 收到数据帧
     * @param  $server
     * @param  $frame
     */
    public function onMessage($server, $frame)
    {
        $data = json_decode($frame->data, true);
        // 安全检查过滤
        array_walk_recursive($data, ['addons\fastim\library\Common', 'checkVariable']);

        if (!is_array($data) || !isset($data['c']) || !isset($data['a'])) {
            $server->push($frame->fd, json_encode([
                'event' => 'show_msg',
                'data'  => '错误的请求！',
                'close' => true
            ]));
            $server->close($frame->fd);
            return;
        }

        // 载入文件类似：根目录/addons/fastim/library/controller/index.php
        $filename = __DIR__ . '/../controller/' . $data['c'] . '.php';
        if (file_exists($filename)) {
            require_once $filename;

            // 检查要访问的类是否存在
            if (!class_exists($data['c'], false)) {
                $server->push($frame->fd, json_encode([
                    'event' => 'show_msg',
                    'data'  => '访问的控制器不存在！',
                    'close' => true
                ]));
                $server->close($frame->fd);
                return;
            }
        } else {
            $server->push($frame->fd, json_encode([
                'event' => 'show_msg',
                'data'  => '错误的请求！',
                'close' => true
            ]));
            $server->close($frame->fd);
            return;
        }

        $o = new $data['c']([$server, $frame, $this->swooleCommon]); // 新建对象
        if (!method_exists($o, $data['a'])) {
            $server->push($frame->fd, json_encode([
                'event' => 'show_msg',
                'data'  => '访问的方法不存在！'
            ]));
            return;
        }
        if ($o->authCheck($data['a'])) {
            $data['data'] = $data['data'] ?? [];
            call_user_func_array([$o, $data['a']], [$data['data']]); //调用对象$o($c)里的方法$a
        }
    }

    /**
     * 链接关闭
     */
    public function onClose($server, $fd, $reactorId)
    {
        // 解除fd绑定并修改用户状态
        $uid = $this->swooleCommon->getUidByFd($fd);
        if (!$this->swooleCommon->unbindUid($fd, $uid)) {

            /**
             * 用户断开1分钟后才设置为离线状态
             * Timer::after 不同于sleep，不会阻塞
             */
            Timer::after(60000, function () use ($uid) {
                if (!$this->swooleCommon->getFdByUid($uid)) {
                    Db::name('fastim_user')->where('id', $uid)->update([
                        'status' => 0
                    ]);
                    $this->swooleCommon->radioStatusMessage($uid, 0);
                }
            });
        }
    }

    public function option(array $option)
    {
        if (!empty($option)) {
            $this->swoole->set($this->checkOptions($option));
        }

        // 注册回调
        foreach ($this->event as $event) {
            if (method_exists($this, 'on' . $event)) {
                $this->swoole->on($event, [$this, 'on' . $event]);
            }
        }

        // 实例化swooleCommon类
        $this->swooleCommon = new \addons\fastim\library\swoole\Common($option['max_connections'], $this->swoole);
    }

    protected function checkOptions(array $options)
    {
        if (class_exists(Helper::class)) {
            $constOptions = Helper::GLOBAL_OPTIONS + Helper::SERVER_OPTIONS + Helper::PORT_OPTIONS + Helper::HELPER_OPTIONS;
            foreach ($options as $k => $v) {
                if (!array_key_exists(strtolower($k), $constOptions)) {
                    unset($options[$k]);
                }
            }
        }
        return $options;
    }

    public function setMonitor($interval = 2, $path = [])
    {
        $this->monitor['interval'] = $interval;
        $this->monitor['path']     = (array)$path;
    }

    /**
     * 文件监控
     *
     * @param $server
     */
    public function monitor($server)
    {
        if ($this->monitor['path']) {
            $server->tick($this->monitor['interval'], function () use ($server) {
                foreach ($this->monitor['path'] as $path) {
                    $dir      = new \RecursiveDirectoryIterator($path);
                    $iterator = new \RecursiveIteratorIterator($dir);

                    foreach ($iterator as $file) {
                        if (pathinfo($file, PATHINFO_EXTENSION) != 'php') {
                            continue;
                        }

                        if ($this->lastMtime < $file->getMTime()) {
                            $this->lastMtime = $file->getMTime();
                            echo '[update]' . $file . " reload...\n";
                            $server->reload();
                            return;
                        }
                    }
                }
            });
        }
    }

    /**
     * 魔术方法 有不存在的操作的时候执行
     * @access public
     * @param string $method 方法名
     * @param array $args 参数
     * @return mixed
     */
    public function __call($method, $args)
    {
        call_user_func_array([$this->swoole, $method], $args);
    }
}
