using System;
using System.Collections.Generic;
using System.Numerics;
using Godot;
using Sfs3.Client;
using Sfs3.Client.Core;
using Sfs3.Client.Entities;
using Sfs3.Client.Entities.Audio;
using Sfs3.Client.Entities.Data;
using Sfs3.Client.Entities.Variables;
using Sfs3.Client.Logging;
using Sfs3.Client.Requests;
using Sfs3.Client.Requests.Audio;
using Sfs3.Client.Util;

public partial class World : Node3D
{
    private const int WORLD_SIZE = 50;

    PackedScene avatarScene;

    private Hud hud;
    private GlobalManager global;
	private SmartFox sfs;
    private Player player;
    private Dictionary<int, Avatar> avatars = [];

    //----------------------------------------------------------
    // Callback Methods
    //----------------------------------------------------------
    #region

	public override void _Ready()
	{
        // NOTE
        // As this class is not wxtending BaseController, we have to replicate
        // some of its features, like accessing the GlobalManager instance

        global = (GlobalManager)GetNode("/root/GlobalManager");
        sfs = global.GetSfsClient();

        // Add SFS event listeners
        AddSmartFoxListeners();

        // Load avatar nested scene
        avatarScene = GD.Load<PackedScene>("scenes/sub/Avatar.tscn");

        // Get reference to HUD and set user name
        hud = GetNode<Hud>("HUD");
        hud.SetUserName(sfs.MySelf.Name);

        // Set random player coordinates
        var rnd = new Random();
        float x = (float)(rnd.NextDouble() * WORLD_SIZE) - WORLD_SIZE / 2;
        float y = 1;
        float z = (float)(rnd.NextDouble() * WORLD_SIZE) - WORLD_SIZE / 2;
        int deg = rnd.Next(0, 360);

        // Set player instance position
        player = GetNode<Player>("Player");
        player.Translate(new Godot.Vector3(x, y, z));
        player.RotationDegrees = new Godot.Vector3(0, deg, 0);
		
		// Init audio
		sfs.Send(new InitAudioRequest());
	}

	// Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(double delta)
	{
        if (sfs != null)
        {
            // Update player position in User Variables
            SetPlayerPosition();

            // Process SFS events
            sfs.ProcessEvents();
        }
	}

    public override void _ExitTree()
    {
        RemoveSmartFoxListeners();
    }

    #endregion

    //----------------------------------------------------------
    // UI event listeners
    //----------------------------------------------------------
    #region
    
    public void OnLogoutButtonClick()
    {
        sfs.Disconnect();
    }

    private void OnMuteButtonToggle(bool isPressed)
    {
        sfs.Send(new ToggleMuteAudioStreamRequest());
    }

    #endregion

    //----------------------------------------------------------
    // Helper methods
    //----------------------------------------------------------
    #region

    private void AddSmartFoxListeners()
    {
        sfs.AddEventListener(SFSEvent.USER_ENTER_ROOM, OnUserEnterRoom);
        sfs.AddEventListener(SFSEvent.USER_EXIT_ROOM, OnUserExitRoom);
        sfs.AddEventListener(SFSEvent.USER_VARIABLES_UPDATE, OnUserVarsUpdate);
        sfs.AddEventListener(SFSAudioEvent.INIT, OnAudioInit);
        sfs.AddEventListener(SFSAudioEvent.STREAM_START, OnAudioStreamStart);
        sfs.AddEventListener(SFSAudioEvent.STREAM_STOP, OnAudioStreamStop);
        sfs.AddEventListener(SFSAudioEvent.STREAM_ADD, OnAudioStreamAdd);
        sfs.AddEventListener(SFSAudioEvent.STREAM_REMOVE, OnAudioStreamRemove);
        sfs.AddEventListener(SFSAudioEvent.TOGGLE_MUTE, OnToggleMute);
    }

    private void RemoveSmartFoxListeners()
    {
        if (sfs != null)
        {
            sfs.RemoveEventListener(SFSEvent.USER_ENTER_ROOM, OnUserEnterRoom);
            sfs.RemoveEventListener(SFSEvent.USER_EXIT_ROOM, OnUserExitRoom);
            sfs.RemoveEventListener(SFSEvent.USER_VARIABLES_UPDATE, OnUserVarsUpdate);
            sfs.RemoveEventListener(SFSAudioEvent.INIT, OnAudioInit);
            sfs.RemoveEventListener(SFSAudioEvent.STREAM_START, OnAudioStreamStart);
            sfs.RemoveEventListener(SFSAudioEvent.STREAM_STOP, OnAudioStreamStop);
            sfs.RemoveEventListener(SFSAudioEvent.STREAM_ADD, OnAudioStreamAdd);
            sfs.RemoveEventListener(SFSAudioEvent.STREAM_REMOVE, OnAudioStreamRemove);
            sfs.RemoveEventListener(SFSAudioEvent.TOGGLE_MUTE, OnToggleMute);
        }
    }

    private void AddAudioStreamPlayer(int id, AudioStreamPlayer3D asp, bool isMuted)
    {
        if (avatars.TryGetValue(id, out Avatar avatar))
            avatar.AddAudioStreamPlayer(asp, isMuted);
    }

    private void RemoveAudioStreamPlayer(int id)
    {
        if (avatars.TryGetValue(id, out Avatar avatar))
            avatar.RemoveAudioStreamPlayer();
    }

    private void SetPlayerPosition()
    {
        var p = new SFSUserVariable("p", ToSfsVector3(player.Position));
        var r = new SFSUserVariable("r", player.RotationDegrees.Y);

        sfs.Send(new SetUserVariablesRequest([p,r]));
    }

    private SFSVector3 ToSfsVector3(Godot.Vector3 v)
    {
        return new SFSVector3(v.X, v.Y, v.Z);
    }

    private Godot.Vector3 ToGodotVector3(SFSVector3 v)
    {
        return new Godot.Vector3(v.X, v.Y, v.Z);
    }
    #endregion

    //----------------------------------------------------------
    // SmartFoxServer event listeners
    //----------------------------------------------------------
    #region

    private void OnUserEnterRoom(ApiEvent evt)
    {
        Room room = (Room)evt.GetParam(EventParam.Room);
        User user = (User)evt.GetParam(EventParam.User);

        if (user == sfs.MySelf)
            global.Trace(String.Format("I entered Room {0}", room));
        else
            global.Trace(String.Format("User {0} entered Room {1}", user, room));
    }

    private void OnUserExitRoom(ApiEvent evt)
    {
        Room room = (Room)evt.GetParam(EventParam.Room);
        User user = (User)evt.GetParam(EventParam.User);

        if (user == sfs.MySelf)
            global.Trace(String.Format("I left Room {0}", room));
        else
        {
            global.Trace(String.Format("User {0} left Room {1}", user, room));

            if (avatars.Remove(user.Id, out Avatar avatar))
                RemoveChild(avatar);
        }
    }

    private void OnUserVarsUpdate(ApiEvent evt)
    {
        User user = (User)evt.GetParam(EventParam.User);
        List<string> changedVars = (List<string>)evt.GetParam(EventParam.ChangedVars);

        if (user.IsItMe)
            return;

        Avatar avatar;

        if (!avatars.ContainsKey(user.Id))
        {
            // Add user avatar to scene
            avatar = (Avatar)avatarScene.Instantiate();
            avatar.Name = "Avatar_" + user.Id;
            AddChild(avatar);
            
            avatar.SetLabel(user.Name);

            avatars.Add(user.Id, avatar);
        }
        else
            avatars.TryGetValue(user.Id, out avatar);

        if (avatar != null)
        {
            if (changedVars.Contains("p"))
                avatar.Position = ToGodotVector3(user.GetVariable("p").Vector3Value);

            if (changedVars.Contains("r"))
                avatar.RotationDegrees = new Godot.Vector3(0, (float)user.GetVariable("r").FloatValue, 0);
        }   
    }

    private void OnAudioInit(ApiEvent evt)
    {
        global.Trace("Audio Streaming system initialized");

        // Add utility scene node to SFS API
        sfs.AudioManager.SetSceneObject(GetNode("SfsNode"));

		// Start stream
		sfs.Send(new StartAudioStreamRequest());
    }

    private void OnAudioStreamStart(ApiEvent evt)
    {
        global.Trace("My audio stream started");
    }

    private void OnAudioStreamStop(ApiEvent evt)
    {
        hud.ResetMuteButton();

        global.Trace("My audio stream stopped");
    }

    private void OnAudioStreamAdd(ApiEvent evt)
    {
        AudioStreamInfo asi = (AudioStreamInfo)evt.GetParam(EventParam.AudioStream)!;
        AudioStreamPlayer3D asp = (AudioStreamPlayer3D)evt.GetParam(EventParam.AudioAsset)!;

        AddAudioStreamPlayer(asi.Id, asp, asi.IsMuted);

        global.Trace("Audio stream added; user: " + asi.User);
    }

    private void OnAudioStreamRemove(ApiEvent evt)
    {
        AudioStreamInfo asi = (AudioStreamInfo)evt.GetParam(EventParam.AudioStream)!;

        RemoveAudioStreamPlayer(asi.Id);

        global.Trace("Audio stream removed; user: " + asi.User);
    }

    private void OnToggleMute(ApiEvent evt)
    {
        AudioStreamInfo asi = (AudioStreamInfo)evt.GetParam(EventParam.AudioStream)!;
        bool isMuted = (bool)evt.GetParam(EventParam.IsMuted)!;

        if (avatars.TryGetValue(asi.Id, out Avatar avatar))
            avatar.SetMuted(isMuted);

        if (isMuted)
            global.Trace("Audio stream muted; user: " + asi.User);
        else
            global.Trace("Audio stream unmuted; user: " + asi.User);
    }

    #endregion
}
