# GPLv3 or later
# (C) J.Y.Amihud ( blenderdumbass ) 2024

# Multiplayer client functions

import bge
import time
import json
import zlib

import traceback

from Scripts import Vehicle
from Scripts import Character_Controll
from Scripts import Opt
from Scripts import Settings
from Scripts.Multiplayer_Shared import *
from Scripts import Reuse

from Scripts.Common import *


settings = Settings.load_settings()
host = settings.get("mp-host", "")

def DescribeScene():

    scene = bge.logic.getCurrentScene()
    dani = scene.objects["Dani_Box"]
    cam = scene.active_camera

    garage = scene.objects["GarageColider"]
    garageDistance = 31
    
    chunksize = 250

    data = {}
    addr = Opt.Address(dani.position, chunksize)
    if addr not in data:
        data[addr] = []

    danidata = Character_Controll.Encode(dani)
    data[addr].append(danidata)

    userId = bge.logic.globalDict.get("userId")
    
    for car in bge.logic.globalDict["allcars"]:
        if car.get("inview") and car.getDistanceTo(garage) > garageDistance:

            if car.get("ownerId") and car.get("ownerId") != userId:
                continue
            
            addr = Opt.Address(car.position, chunksize)
            cardata = Vehicle.Encode(car)
            if addr not in data:
                data[addr] = []
            data[addr].append(cardata)        
    
    return data
    
def MainLoop():
    
    
    scene = bge.logic.getCurrentScene()
    dani = scene.objects["Dani_Box"]
    cam = scene.active_camera
    chunksize = 250

    bge.logic.globalDict["networkQueue"] = {}

    while True:
        try:

            # A bit of delay, to not owerwhelm the server
            time.sleep(0.1) 
            
            
            

            # Testing if the game is still runing.
            # it will fail when the game engine stops.
            try: bge.logic.getRealTime()
            except: return

            
            if bge.logic.globalDict.get("restore-physics-timer"):
                continue
            if bge.logic.globalDict.get("network-scene"):
                continue
    
            

            ######### SENDING THE DATA TO SERVER ############

            data = {}

            # Login
            LoginEncode(data)

            # Scene
            data["scene"]  = DescribeScene()
            data["vision"] = Opt.Surround(cam.position,
                                          chunksize,
                                          cam.orientation.to_euler())

            # Queue
            networkQueue = bge.logic.globalDict["networkQueue"]
            for key in networkQueue:
                if networkQueue[key]:
                    data[key] = networkQueue[key].pop(0)
            
            
            ######### DEALING WITH RESPONSE ############

            data = Send(host, data)

            for key in data:

                payload = data[key]

                if key == "login":
                    LoginDecode(payload)
                    
                elif key == "scene":                    
                    bge.logic.globalDict["network-scene"] = payload

                elif key == "message":
                    bge.logic.globalDict["print"] = payload

                else:
                    print(key, payload)
                    
        except Exception:
            print("\n\nNETWORK CLIENT ERROR:", clr["bold"]+clr["tdrd"], traceback.format_exc(), clr["bold"])
        
def SecondaryLoop():

    # This loop runs in the game loop, not the separate thread.
    # And it deals with updates sent by the other thread.
    if bge.logic.globalDict.get("network-scene"):
        data = bge.logic.globalDict["network-scene"]
        SceneDecode(data)
        bge.logic.globalDict["network-scene"] = None
    
def LoginEncode(data):

    data["login"] = {}
    data["login"]["userId"]   = bge.logic.globalDict.get("userId")
    data["login"]["username"] = settings.get("mp-name")
    data["login"]["room"]     = settings.get("mp-room")

def LoginDecode(data):
    
    if data.get("userId"):
        bge.logic.globalDict["userId"] = data["userId"]

    if data.get("room"):
        settings["mp-room"] = data["room"]

def SceneDecode(data):

    netObjects = bge.logic.globalDict["netObjects"]
    
    if not data:
        return
    
    for addr in data:
        chunk = data[addr]

        for obj in chunk:

            # update = True
            
            # if obj.get("netId") in netObjects["netId"]:
            #     if obj.get("ownerId"):
            #         netObjects["netId"][obj.get("netId")]["ownerId"] = obj.get("ownerId")

            #     obj["ID"] = id(netObjects["netId"][obj.get("netId")])
                    
            # Sometimes we want to update some things like netId.
            if obj.get("ID") in netObjects["pythonId"]\
               and obj.get("name") == netObjects["pythonId"][obj["ID"]].name:
                netObjects["pythonId"][obj["ID"]]["netId"] = obj.get("netId")
                netObjects["netId"][obj.get("netId")] = netObjects["pythonId"][obj["ID"]]
                
            
            if obj.get("type") == "veh":
                Vehicle.Decode(obj, network=True)
            elif obj.get("type") == "chr":
                Character_Controll.Decode(obj, network=True)
                        
def QueueToSend(key, data):


    networkQueue = bge.logic.globalDict["networkQueue"]

    if key not in networkQueue:
        networkQueue[key] = []

    networkQueue[key].append(data)
                    
def GrabOwnership(obj):

    
        
    # This function grabs ownership of an object.
    # Like when a player steals a car spawned by
    # another player.

    userId  = bge.logic.globalDict["userId"]
    ownerId = obj.get("ownerId")
    netId   = obj.get("netId")

    # First we will check that the ownership isn't ours.
    if userId != ownerId and netId:

        QueueToSend("change-ownership",{
            "netId" :netId,
            "userId":userId
        })

    obj["ownerId"] = userId