%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 11. 1月 2021 上午12:17
%%%-------------------------------------------------------------------
-module(ws_channel).
-author("licheng5").
-include("iot.hrl").

%% API
-export([init/2]).
-export([websocket_init/1, websocket_handle/2, websocket_info/2, terminate/3]).
-export([publish/3, stop/2, send/2]).

-record(state, {
    uuid :: undefined | binary(),
    %% 用户进程id
    host_pid = undefined,
    %% 发送消息对应的id
    packet_id = 1 :: integer(),

    %% 请求响应的对应关系
    inflight = #{}
}).

%% 向通道中写入消息
-spec publish(Pid :: pid(), ReceiverPid :: pid(), Msg :: binary()) -> Ref :: reference().
publish(Pid, ReceiverPid, Msg) when is_pid(Pid), is_binary(Msg) ->
    Ref = make_ref(),
    Pid ! {publish, ReceiverPid, Ref, Msg},
    Ref.

%% 向通道中写入消息
-spec send(Pid :: pid(), Msg :: binary()) -> no_return().
send(Pid, Msg) when is_pid(Pid), is_binary(Msg) ->
    Pid ! {send, Msg}.

%% 关闭方法
-spec stop(Pid :: pid(), Reason :: any()) -> no_return().
stop(undefined, _Reason) ->
    ok;
stop(Pid, Reason) when is_pid(Pid) ->
    Pid ! {stop, Reason}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 逻辑处理方法
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

init(Req, Opts) ->
    {cowboy_websocket, Req, Opts}.

websocket_init(_State) ->
    lager:debug("[ws_channel] get a new connection"),
    %% 初始状态为true
    {ok, #state{packet_id = 1}}.

websocket_handle({binary, <<?PACKET_REQUEST, PacketId:32, ?METHOD_AUTH:8, Data/binary>>}, State) ->
    #{<<"uuid">> := UUID, <<"timestamp">> := Timestamp, <<"salt">> := Salt, <<"username">> := Username, <<"token">> := Token} = jiffy:decode(Data, [return_maps]),
    lager:debug("[ws_channel] auth uuid: ~p, request message: ~p", [UUID, Data]),
    case iot_auth:check(Username, Token, UUID, Salt, Timestamp) of
        true ->
            case host_bo:get_host_by_uuid(UUID) of
                undefined ->
                    lager:warning("[ws_channel] uuid: ~p, user: ~p, host not found", [UUID, Username]),
                    {stop, State};
                {ok, _} ->
                    %% 尝试启动主机的服务进程
                    {ok, HostPid} = iot_host_sup:ensured_host_started(UUID),
                    case iot_host:attach_channel(HostPid, self()) of
                        ok ->
                            %% 建立到host的monitor
                            erlang:monitor(process, HostPid),
                            Reply = jiffy:encode(#{<<"code">> => 1, <<"message">> => <<"ok">>}, [force_utf8]),

                            {reply, {binary, <<?PACKET_RESPONSE, PacketId:32, 0:8, Reply/binary>>}, State#state{uuid = UUID, host_pid = HostPid}};
                        {error, Reason} ->
                            lager:debug("[ws_channel] uuid: ~p, attach channel get error: ~p", [UUID, Reason]),
                            {stop, State}
                    end
            end;
        false ->
            lager:warning("[ws_channel] uuid: ~p, user: ~p, auth failed", [UUID, Username]),
            {stop, State}
    end;

websocket_handle({binary, <<?PACKET_REQUEST, PacketId:32, ?METHOD_CREATE_SESSION:8, PubKey/binary>>}, State = #state{host_pid = HostPid}) when is_pid(HostPid) ->
    {ok, Reply} = iot_host:create_session(HostPid, PubKey),
    {reply, {binary, <<?PACKET_RESPONSE, PacketId:32, Reply/binary>>}, State};

websocket_handle({binary, <<?PACKET_REQUEST, _PacketId:32, ?METHOD_DATA:8, Data/binary>>}, State = #state{host_pid = HostPid}) when is_pid(HostPid) ->
    iot_host:handle(HostPid, {data, Data}),
    {ok, State};

websocket_handle({binary, <<?PACKET_REQUEST, _PacketId:32, ?METHOD_PING:8, CipherMetric/binary>>}, State = #state{host_pid = HostPid}) when is_pid(HostPid) ->
    iot_host:handle(HostPid, {ping, CipherMetric}),
    {ok, State};

websocket_handle({binary, <<?PACKET_REQUEST, _PacketId:32, ?METHOD_INFORM:8, CipherInfo/binary>>}, State = #state{host_pid = HostPid}) when is_pid(HostPid) ->
    iot_host:handle(HostPid, {inform, CipherInfo}),
    {ok, State};

websocket_handle({binary, <<?PACKET_REQUEST, _PacketId:32, ?METHOD_FEEDBACK_STEP:8, CipherInfo/binary>>}, State = #state{host_pid = HostPid}) when is_pid(HostPid) ->
    iot_host:handle(HostPid, {feedback_step, CipherInfo}),
    {ok, State};

websocket_handle({binary, <<?PACKET_REQUEST, _PacketId:32, ?METHOD_FEEDBACK_RESULT:8, CipherInfo/binary>>}, State = #state{host_pid = HostPid}) when is_pid(HostPid) ->
    iot_host:handle(HostPid, {feedback_result, CipherInfo}),
    {ok, State};

websocket_handle({binary, <<?PACKET_REQUEST, _PacketId:32, ?METHOD_EVENT:8, CipherEvent/binary>>}, State = #state{host_pid = HostPid}) when is_pid(HostPid) ->
    iot_host:handle(HostPid, {event, CipherEvent}),
    {ok, State};

websocket_handle({binary, <<?PACKET_REQUEST, _PacketId:32, ?METHOD_AI_EVENT:8, CipherEvent/binary>>}, State = #state{host_pid = HostPid}) when is_pid(HostPid) ->
    iot_host:handle(HostPid, {ai_event, CipherEvent}),
    {ok, State};

%% 主机端的消息响应
websocket_handle({binary, <<?PACKET_PUBLISH_RESPONSE, 0:32, Body/binary>>}, State = #state{uuid = UUID}) ->
    lager:debug("[ws_channel] uuid: ~p, get send response message: ~p", [UUID, Body]),
    {ok, State};
websocket_handle({binary, <<?PACKET_PUBLISH_RESPONSE, PacketId:32, Body/binary>>}, State = #state{uuid = UUID, inflight = Inflight}) when PacketId > 0 ->
    lager:debug("[ws_channel] uuid: ~p, get publish response message: ~p, packet_id: ~p", [UUID, Body, PacketId]),
    case maps:take(PacketId, Inflight) of
        error ->
            lager:warning("[ws_channel] get unknown publish response message: ~p, packet_id: ~p", [Body, PacketId]),
            {ok, State};
        {{ReceiverPid, Ref}, NInflight} ->
            case is_pid(ReceiverPid) andalso is_process_alive(ReceiverPid) of
                true when Body == <<>> ->
                    ReceiverPid ! {ws_response, Ref};
                true ->
                    ReceiverPid ! {ws_response, Ref, Body};
                false ->
                    lager:warning("[ws_channel] get publish response message: ~p, packet_id: ~p, but receiver_pid is deaded", [Body, PacketId])
            end,
            {ok, State#state{inflight = NInflight}}
    end;

websocket_handle(Info, State) ->
    lager:error("[ws_channel] get a unknown message: ~p, channel will closed", [Info]),
    {stop, State}.

%% 处理关闭信号
websocket_info({stop, Reason}, State) ->
    lager:debug("[ws_channel] the channel will be closed with reason: ~p", [Reason]),
    {stop, State};

%% 发送消息
websocket_info({publish, ReceiverPid, Ref, Msg}, State = #state{packet_id = PacketId, inflight = Inflight}) when is_binary(Msg) ->
    NInflight = maps:put(PacketId, {ReceiverPid, Ref}, Inflight),
    {reply, {binary, <<?PACKET_PUBLISH, PacketId:32, Msg/binary>>}, State#state{packet_id = PacketId + 1, inflight = NInflight}};

%% 发送消息, 不需要等待回复
websocket_info({send, Msg}, State) when is_binary(Msg) ->
    {reply, {binary, <<?PACKET_PUBLISH, 0:32, Msg/binary>>}, State};

%% 用户进程关闭，则关闭通道
websocket_info({'DOWN', _, process, HostPid, Reason}, State = #state{uuid = UUID, host_pid = HostPid}) ->
    lager:debug("[ws_channel] uuid: ~p, channel will close because host exited with reason: ~p", [UUID, Reason]),
    {stop, State};

%% 处理其他未知消息
websocket_info(Info, State = #state{uuid = UUID}) ->
    lager:debug("[ws_channel] channel get unknown info: ~p, uuid: ~p", [Info, UUID]),
    {ok, State}.

%% 进程关闭事件
terminate(Reason, _Req, State) ->
    lager:debug("[ws_channel] channel close with reason: ~p, state is: ~p", [Reason, State]),
    ok.