---
--- Created by jinjing.
--- DateTime: 2020/7/23 17:41
---

---@class LNetWorking
LNetWorking = LNetWorking or class("LNetWorking");

LNetWorking.SERVER_AUTH = 0;
LNetWorking.SERVER_GS = 1;

LNetWorking.REMOTE_ENTITY_ID_START = 10000;

LNetWorking.ENTITY_TYPE_LOCALPLAYER = 1;
LNetWorking.ENTITY_TYPE_SKELETAL_ACTOR = 2;
LNetWorking.ENTITY_TYPE_VEHICLE = 3;
LNetWorking.ENTITY_TYPE_FLIGHT = 4;


function LNetWorking:ctor()
    self:init();
    self:initEvents();
end

function LNetWorking:dtor()
    self:uninit();
    self:uninitEvents();
end

function LNetWorking:init()
    self.m_tabConnection = {};       --网络连接socket表,包含认证服务器和GS
    self.m_mapEntityToActorID = {};  --{uuid, actorId}

    self.m_playerEntityID = nil;      --主角的entityID
    self.m_vehicleEntityID = nil;     --车子的entityID
    self.m_flightEntityID = nil;      --飞机的entityID

    self.m_vehicleActorID = -1;
    self.m_flightActorID = -1;

    self.m_nMapID = -1;              --当前mapid
    self.m_modelRes = "";
    self.m_account = "";            
    self.m_pwd = "";
    self.m_lastPos = {x = 1, y = 1, z = 1, w = 1};
end

function LNetWorking:uninit()
    for id, conn in pairs (self.m_tabConnection) do
        KLNet.DestroyNetClient(conn);
        self.m_tabConnection[id] = nil;
    end

    self.m_idgenerater = 0;

    self.m_playerEntityID = nil;
    self.m_vehicleEntityID = nil;
    self.m_flightEntityID = nil;

    self.m_vehicleActorID = -1;
    self.m_flightActorID = -1;

    self.m_nMapID = -1;
    self.m_account = "";
    self.m_pwd = "";
    self.m_lastPos = {x = 1, y = 1, z = 1, w = 1};
end

function LNetWorking:initEvents()

    g_HBUIEventDispatcher:AddEventListener(HBUIEvent.UI2S_ConnectAuth, self.onHBUI_ConnectAuth, self);
    g_HBUIEventDispatcher:AddEventListener(HBUIEvent.UI2S_LoginAuth, self.onHBUI_LoginAuth, self);
    g_HBUIEventDispatcher:AddEventListener(HBUIEvent.UI2S_SendChatMsg, self.onHBUI_SendChatMsg, self);

    g_EventDispatcherManager:AddEventListener("localplayer", "AnimStateChanged", self.onEvent_LocalPlayerStateChanged, self);
end

function LNetWorking:uninitEvents()

    g_HBUIEventDispatcher:RemoveEventListener(HBUIEvent.UI2S_ConnectAuth, self.onHBUI_ConnectAuth, self);
    g_HBUIEventDispatcher:RemoveEventListener(HBUIEvent.UI2S_LoginAuth, self.onHBUI_LoginAuth, self);
    g_HBUIEventDispatcher:RemoveEventListener(HBUIEvent.UI2S_SendChatMsg, self.onHBUI_SendChatMsg, self);

    g_EventDispatcherManager:RemoveEventListener("localplayer", "AnimStateChanged", self.onEvent_LocalPlayerStateChanged, self);
end

function LNetWorking:GameTick()
    for _, conn in pairs (self.m_tabConnection) do
        if conn ~= nil and conn:isConnected() then
            conn:tick();
        end
    end

    self:SyncPlayerPos();
    self:SyncEntityPos(self.m_vehicleActorID, self.m_vehicleEntityID);       
    self:SyncEntityPos(self.m_flightActorID, self.m_flightEntityID);   
         
end

--同步主角位置
function LNetWorking:SyncPlayerPos()
    local conn = self.m_tabConnection[LNetWorking.SERVER_GS];
    if not conn or not conn:isConnected() then
        return false;
    end

    local player = g_ActorManager:GetLocalPlayer();
    if nil == player then
        return false;
    end
    
    local _,x,y,z = player:GetWorldPosition();
    local _,p,yaw,r = player:GetWorldRotation();

    if not x then
        return;
    end

    if  math.abs(self.m_lastPos.x - x) < 1.0 and
        math.abs(self.m_lastPos.y - y) < 1.0 and
        math.abs(self.m_lastPos.z - z) < 1.0 and
        math.abs(self.m_lastPos.w - yaw) < 1.0 then
        return false;
    end

    local data = {
        entityId = self.m_playerEntityID;
        pos = {x = x, y = y, z = z, w = yaw};
    }

    --util.printTB(data, "LNetWorking:SyncPlayerPos ")

    local data = proto.encode("protocol.Gs2C_SyncEntityPos", data);
    conn:sendPacketEx(protocol.MT.CLIENT_SYNC_ENTITY_POS, data);

    self.m_lastPos = {x = x, y = y, z = z, w = yaw};

    return true;
end

function LNetWorking:SyncEntityPos(actorId, entityId)
    local conn = self.m_tabConnection[LNetWorking.SERVER_GS];
    if not conn or not conn:isConnected() then
        return false;
    end

    local pActor = g_ActorManager:GetActorById(actorId);
    if nil == pActor then
        return false;
    end
    
    local _,x,y,z = pActor:GetWorldPosition();
    local _,p,yaw,r = pActor:GetWorldRotation();

    if not x then
        return;
    end

    if  math.abs(self.m_lastPos.x - x) < 1.0 and
        math.abs(self.m_lastPos.y - y) < 1.0 and
        math.abs(self.m_lastPos.z - z) < 1.0 and
        math.abs(self.m_lastPos.w - yaw) < 1.0 then
        return false;
    end

    local data = {
        entityId = entityId;
        pos = {x = x, y = y, z = z, w = yaw};
    }

    --util.printTB(data, "LNetWorking:SyncEntityPos ")

    local data = proto.encode("protocol.Gs2C_SyncEntityPos", data);
    conn:sendPacketEx(protocol.MT.CLIENT_SYNC_ENTITY_POS, data);
end

function LNetWorking:EntityCreated(entityType, actorId, modelRes)

    LOG_I("LNetWorking:EntityCreated", entityType, actorId, modelRes);

    local conn = self.m_tabConnection[LNetWorking.SERVER_GS];
    if not conn then
        return
    end

    local pActor = g_ActorManager:GetActorById(actorId);
    if not pActor then
        LOG_E("LNetWorking:EntityCreated not exists ", actorId);
        return;
    end

    --local _,x,y,z = pActor:GetWorldPosition();
    --local _,p,yaw,r = pActor:GetWorldRotation();

    local info = {
        name = self.m_playerEntityID;
        modelRes = modelRes;
        pos = {x = self.m_lastPos.x, y = self.m_lastPos.y, z = self.m_lastPos.z, w = self.m_lastPos.w};
        clientEntityId = actorId;
        entityType = entityType;
    }

    util.printTB(info, "send packet C2Gs_CreateEntity ")

    local data = proto.encode("protocol.C2Gs_CreateEntity", info);
    conn:sendPacketEx(protocol.MT.CLIENT_SYNC_CREATE_ENTITY, data);

    if self.ENTITY_TYPE_VEHICLE == entityType then
        self.m_vehicleActorID = actorId;
    elseif self.ENTITY_TYPE_FLIGHT == entityType then
        self.m_flightActorID = actorId;
    end
end

function LNetWorking:EntityDeleted(actorId)
    LOG_I("LNetWorking:EntityDeleted", actorId);

    local conn = self.m_tabConnection[LNetWorking.SERVER_GS];
    if not conn then
        return
    end

    local entityId = nil;
    local player = g_ActorManager:GetLocalPlayer();
    if self.m_vehicleActorID == actorId then
        entityId = self.m_vehicleEntityID;
    elseif self.m_flightActorID == actorId then
        entityId = self.m_flightEntityID;
    elseif player:GetId() == actorId then
        entityId = self.m_playerEntityID;
    end

    if not entityId then
        return;
    end

    local data = {
        entityId = entityId;
    }

    util.printTB(data, "LNetWorking:EntityDeleted ")

    local data = proto.encode("protocol.C2Gs_DeleteEntity", data);
    conn:sendPacketEx(protocol.MT.CLIENT_SYNC_DELETE_ENTITY, data);

    if self.m_vehicleActorID == actorId then
        self.m_vehicleEntityID = nil;
        self.m_vehicleActorID = -1;
    elseif self.m_flightActorID == actorId then
        self.m_flightEntityID = nil;
        self.m_flightActorID = -1;
    elseif player:GetId() == actorId then
        self.m_playerEntityID = nil;
    end
end

--HBUI事件-申请登陆认证服务器
function LNetWorking:onHBUI_ConnectAuth(data)
    util.printTB("LNetWorking:onHBUI_ConnectAuth >>>>>>>> ", data);
    local conn = self.m_tabConnection[LNetWorking.SERVER_AUTH];
    if conn and conn:isConnected() then
        self:DisConnect(LNetWorking.SERVER_AUTH);       
    end

    self.m_account = data.account;
    self.m_pwd = data.pwd;

    local ret = self:Connect(self.SERVER_AUTH, define.SERVER_ADDR, define.SERVER_PORT);

    local hbuiEvtData = {
        ret = ret
    };

    LOG_I("HBUIEvent.S2UI_ConnectAuthRet ", ret)
    KLHbui.TriggerEvent(HBUIEvent.S2UI_ConnectAuthRet, cjson.encode(hbuiEvtData));
end

--HBUI事件-申请登陆认证服务器
function LNetWorking:onHBUI_LoginAuth(data)
    util.printTB(data, "LNetWorking:onHBUI_LoginAuth >>>>>>>> ");

    local ret = false;

    -- 若已断连，先重连一下认证服务器
    local conn = self.m_tabConnection[LNetWorking.SERVER_AUTH];
    if not conn or not conn:isConnected() then
        ret = self:Connect(self.SERVER_AUTH, define.SERVER_ADDR, define.SERVER_PORT);
        if not ret then
            return;
        end
    end

    self:LoginAuth(self.m_account, self.pwd);
    self.m_nMapID = data.mapId;
    self.m_modelRes = data.modelRes;
end

--HBUI事件-发送聊天信息
function LNetWorking:onHBUI_SendChatMsg(data)
    util.printTB("LNetWorking:onHBUI_SendChatMsg >>>>>>>> ", data);

    local conn = self.m_tabConnection[LNetWorking.SERVER_GS];
    if not conn then
        return
    end

    local info = {
        chatType = 0;
        chatContent = data.msg;
    }

    local protoData = proto.encode("protocol.C2GS_SendChatMsg", info);
    conn:sendPacketEx(protocol.MT.CLIENT_SEND_CHAT_MSG, protoData);
end


--主角状态切换
function LNetWorking:onEvent_LocalPlayerStateChanged(state)

    local nState = tonumber(state);
    LOG_I("LNetWorking:onEvent_LocalPlayerStateChanged new state ", nState)

    local conn = self.m_tabConnection[LNetWorking.SERVER_GS];
    if not conn then
        return
    end

    local info = {
        entityId = self.m_playerEntityID,
        state = nState,
        enable = true
    }

    util.printTB(info, "send packet C2Gs_SyncEntityInputState ")

    local data = proto.encode("protocol.C2Gs_SyncEntityInputState", info);
    conn:sendPacketEx(protocol.MT.CLIENT_SYNC_INPUT_STATE, data);
end

--连接服务器
function LNetWorking:Connect(serverType, ip, port)
    LOG_I("LNetWorking:Connect = ", serverType , ip , port)

    -- 若已存在连接，先清掉
    self:DisConnect(serverType);

    --创建新的连接
    local conn = KLNet.CreateNetClient();
    if conn:connect(ip, port) ~= 1 then
        LOG_E("LNetWorking:Connect error: conn failed");
        return false;
    end

    self.m_tabConnection[serverType] = conn;
    return true;
end

--断连服务器
function LNetWorking:DisConnect(serverType)

    local conn = self.m_tabConnection[serverType]
    if not conn then
        return;
    end

    if not conn:isConnected() then
        self.m_tabConnection[serverType] = nil;
        return
    end

    LOG_I("LNetWorking:DisConnect", serverType)

    KLNet.DestroyNetClient(conn);
    self.m_tabConnection[serverType] = nil;
end

--登陆认证服务器
function LNetWorking:LoginAuth(szAccount, szPwd)
    local conn = self.m_tabConnection[LNetWorking.SERVER_AUTH];
    if not conn then
        return
    end

    local auth = {
        account = szAccount,
        password = szPwd,
    }

    local data = proto.encode("protocol.C2A_LoginAuthReq", auth);
    conn:sendPacketEx(protocol.MT.CLIENT_REQUEST_LOGIN_AUTH, data);
end

--认证服务器登陆成功
function LNetWorking:cb_LoginAuthSuccess()
    LOG_I("LNetWorking:LoginAuthSuccess ")
    
end

--登陆GS
function LNetWorking:LoginGS(accKey, secKey)
    LOG_I("LNetWorking:LoginGS ", accKey, secKey)
    local conn = self.m_tabConnection[LNetWorking.SERVER_GS];
    if not conn then
        return
    end

    local loginInfo = {
        accessKey = accKey;
        secretKey = secKey;
        mapId = self.m_nMapID;
        mapWidth = 1024;
        mapLength = 1024;
        pos = {x = self.m_lastPos.x, y = self.m_lastPos.y, z = self.m_lastPos.z, w = self.m_lastPos.w};
        username = self.m_account or "";
        modelRes = self.m_modelRes or "DefaultModel.actor";
    }

    util.printTB(loginInfo, "send packet ClientPlayerLogin ")

    local data = proto.encode("protocol.ClientPlayerLogin", loginInfo);
    conn:sendPacketEx(protocol.MT.CLIENT_PLAYER_LOGIN, data);
end

--GS登陆成功
function LNetWorking:cb_LoginGSSuccess(mapID, entityID)
    LOG_I("LNetWorking:LoginGSSuccess ", mapID, entityID)
    self.m_nMapID = mapID;
    self.m_playerEntityID = entityID;

    local hbuiEvtData = {
        ret = true
    };

    KLHbui.TriggerEvent(HBUIEvent.S2UI_ConnectGSRet, cjson.encode(hbuiEvtData));
end

--接收聊天信息
function LNetWorking:cb_onRecvChatMsg(chatType, sender, msg)
    LOG_I("LNetWorking:onRecvChatMsg ", chatType, sender, msg)
    local hbuiEvtData = {
        type = chatType,
        sender = sender,
        msg = msg
    };

    KLHbui.TriggerEvent(HBUIEvent.S2UI_RecvChatMsg, cjson.encode(hbuiEvtData));
end

--创建新entity
function LNetWorking:cb_CreateNewEntity(data)
    util.printTB(data, "LNetWorking:cb_CreateNewEntity >>>>>> ")

    -- ownerid无效，没时间改服务器，暂时用名字替代owner的entityID
    if data.name == self.m_playerEntityID then
        LOG_I("LNetWorking:cb_CreateNewEntity : my own entity")
        return;
    end

    local strActorPath;
    local pActor;
    if data.entityType == self.ENTITY_TYPE_VEHICLE then
        --strActorPath = data.modelRes;
        strActorPath = "UGCSource\\car\\DefaultCar.assemble"                -- 目前只有一个车子
        pActor = g_ActorManager:CreateWheeledVehicleActor(strActorPath);
    elseif data.entityType == self.ENTITY_TYPE_FLIGHT then
        --strActorPath = data.modelRes;
        strActorPath = "UGCSource\\flight\\assembles\\parts\\kite\\kite.Mesh"  -- 目前只有一个飞行器
        pActor = g_ActorManager:CreateSkeletalMeshActor(strActorPath);
        pActor:SetCollisionType(Core.QueryAndPhysics);
    else
        strActorPath = "UGCSource\\player\\" .. data.modelRes;
        pActor = g_ActorManager:CreateSkeletalMeshActor(strActorPath);
        pActor:SetCollisionType(Core.QueryAndPhysics);
    end


    pActor:SetWorldPosition(data.pos.x, data.pos.y, data.pos.z);
    pActor:SetWorldRotation(0, data.pos.w, 0);

    self.m_mapEntityToActorID[data.entityId] = pActor:GetId();
end

--删除entity
function LNetWorking:cb_DeleteEntity(entityId)
    if self:IsSelf(entityId) then
        return;
    end

    local nPlayerId = self.m_mapEntityToActorID[entityId]
    if not nPlayerId then
        LOG_E("LNetWorking:DeleteEntity playerid not exists ", nPlayerId);
        return;
    end

    local pActor = g_ActorManager:GetActorById(nPlayerId);
    if not pActor then
        LOG_E("LNetWorking:DeleteEntity player not exists ", nPlayerId);
        return;
    end

    g_ActorManager:DestroyActor(pActor);
    self.m_mapEntityToActorID[entityId] = nil;
end

--同步entity位置
function LNetWorking:cb_SyncEntityPos(entityId, pos)

    if self:IsSelf(entityId) then
        --LOG_E("LNetWorking:IsSelf ", entityId, self.m_playerEntityID, self.m_vehicleEntityID);
        return;
    end

    local nActorId = self.m_mapEntityToActorID[entityId]
    if not nActorId then
        LOG_E("LNetWorking:SyncEntityPos playerid not exists ", nActorId);
        return;
    end

    local pActor = g_ActorManager:GetActorById(nActorId);
    if not pActor then
        LOG_E("LNetWorking:SyncEntityPos player not exists ", nActorId);
        return;
    end

    --util.printTB(pos, "LNetWorking:SyncEntityPos " .. entityId)

    pActor:SetWorldPosition(pos.x, pos.y, pos.z);
    pActor:SetWorldRotation(0, pos.w, 0);
end

--entity状态切换
function LNetWorking:cb_EntityChangeState(entityId, state)
    LOG_I("LNetWorking:EntityChangeState ", entityId, state)

    if self:IsSelf(entityId) then
        return;
    end

    local nPlayerId = self.m_mapEntityToActorID[entityId]
    if not nPlayerId then
        --LOG_E("LNetWorking:SyncEntityPos playerid not exists ", nPlayerId);
        return;
    end

    local pActor = g_ActorManager:GetActorById(nPlayerId);
    if not pActor then
        LOG_E("LNetWorking:SyncEntityPos player not exists ", nPlayerId);
        return;
    end

    pActor:ChangeAnimState(state);
end

--创建entity回包
function LNetWorking:cb_CreateEntityResp(entityId, actorId)
    LOG_I("LNetWorking:cb_CreateEntityResp ", entityId, actorId)

    -- 没有类型返回先这样很挫地写……
    if actorId == self.m_vehicleActorID then
        self.m_vehicleEntityID = entityId;
    elseif actorId == self.m_flightActorID then
        self.m_flightEntityID = entityId;
    end

end

function LNetWorking:IsSelf(entityId)
    if self.m_playerEntityID == entityId or self.m_vehicleEntityID == entityId or self.m_flightEntityID == entityId then
        return true;
    end

    return false;
end

function LNetWorking:onSceneDestroy()
    LOG_I("LNetWorking:onSceneDestroy ")
    if self.m_playerEntityID then
        self:EntityDeleted(1);
    end

    if self.m_flightEntityID then
        self:EntityDeleted(self.m_flightActorID);
    end

    if self.m_vehicleEntityID then
        self:EntityDeleted(self.m_vehicleActorID);
    end

    self:uninit();
end


_G.g_NetWorking = LNetWorking:new();