using System;
using System.Collections.Generic;
using FMOD.Studio;
using FMODUnity;
using NitroxClient.GameLogic.FMOD;
using NitroxClient.Unity.Helper;
using NitroxModel.GameLogic.FMOD;
using UnityEngine;

namespace NitroxClient.MonoBehaviours;

[DisallowMultipleComponent]
public class FMODEmitterController : MonoBehaviour
{
    private readonly Dictionary<string, FMOD_CustomEmitter> customEmitters = new();
    private readonly Dictionary<string, Tuple<FMOD_CustomLoopingEmitter, bool, float>> loopingEmitters = new(); // Tuple<emitter, is3D, radius>
    private readonly Dictionary<string, FMOD_StudioEventEmitter> studioEmitters = new();
    private readonly Dictionary<string, EventInstance> eventInstances = new(); // 2D Sounds

    /// <summary>
    /// When <see cref="GameObject"/>s are copied their Start()/Awake() functions don't get called again.
    /// So the FMOD Start patch that tried to locate a <see cref="NitroxEntity"/> won't find one and will error later.
    /// This function can late register missed FMOD MonoBehaviours
    /// </summary>
    public void LateRegisterEmitter()
    {
        foreach (MonoBehaviour behaviour in gameObject.GetComponentsInChildren<MonoBehaviour>(true))
        {
            switch (behaviour)
            {
                case FMOD_CustomEmitter customEmitter when this.Resolve<FMODWhitelist>().IsWhitelisted(customEmitter.asset.path, out float maxDistance):
                    AddEmitter(customEmitter.asset.path, customEmitter, maxDistance);
                    break;
                case FMOD_StudioEventEmitter studioEmitter when this.Resolve<FMODWhitelist>().IsWhitelisted(studioEmitter.asset.path, out float maxDistance):
                    AddEmitter(studioEmitter.asset.path, studioEmitter, maxDistance);
                    break;
            }
        }
    }

    public void AddEmitter(string path, FMOD_CustomEmitter customEmitter, float maxDistance)
    {
        if (customEmitters.ContainsKey(path))
        {
            return;
        }

        customEmitter.CacheEventInstance();
        EventInstance evt = customEmitter.GetEventInstance();
        evt.getDescription(out EventDescription description);
        description.is3D(out bool is3D);

        if (is3D)
        {
            evt.setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f);
            evt.setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, maxDistance);

            customEmitters.Add(path, customEmitter);

            if (customEmitter is FMOD_CustomLoopingEmitter loopingEmitter)
            {
                if (loopingEmitter.assetStart && this.Resolve<FMODWhitelist>().IsWhitelisted(loopingEmitter.assetStart.path, out float radiusStart))
                {
                    AddEmitter(loopingEmitter.assetStart.path, loopingEmitter, radiusStart);
                }

                if (loopingEmitter.assetStop && this.Resolve<FMODWhitelist>().IsWhitelisted(loopingEmitter.assetStop.path, out float radiusStop))
                {
                    AddEmitter(loopingEmitter.assetStop.path, loopingEmitter, radiusStop);
                }
            }
        }
        else
        {
            AddEventInstance(customEmitter.asset.path, evt);
        }
    }

    private void AddEmitter(string path, FMOD_CustomLoopingEmitter loopingEmitter, float radius)
    {
        if (!loopingEmitters.ContainsKey(path))
        {
            loopingEmitter.CacheEventInstance();

            loopingEmitter.evt.getDescription(out EventDescription description);
            description.is3D(out bool is3D);

            loopingEmitters.Add(path, new Tuple<FMOD_CustomLoopingEmitter, bool, float>(loopingEmitter, is3D, radius));
        }
    }

    public void AddEmitter(string path, FMOD_StudioEventEmitter studioEmitter, float maxDistance)
    {
        if (!customEmitters.ContainsKey(path))
        {
            studioEmitter.CacheEventInstance();

            studioEmitters.Add(path, studioEmitter);
        }
    }

    private void AddEventInstance(string path, EventInstance eventInstance)
    {
        if (!eventInstances.ContainsKey(path))
        {
            eventInstances.Add(path, eventInstance);
        }
    }

    public void PlayCustomEmitter(string path) => customEmitters[path].AliveOrNull()?.Play();
    public void SetParameterCustomEmitter(string path, string paramString, float value) => customEmitters[path].AliveOrNull()?.SetParameterValue(paramString, value);
    public void StopCustomEmitter(string path) => customEmitters[path].AliveOrNull()?.Stop();

    public void PlayStudioEmitter(string path) => studioEmitters[path].AliveOrNull()?.PlayUI();
    public void StopStudioEmitter(string path, bool allowFadeout) => studioEmitters[path].AliveOrNull()?.Stop(allowFadeout);

    public void PlayCustomLoopingEmitter(string path)
    {
        (FMOD_CustomLoopingEmitter loopingEmitter, bool is3D, float radius) = loopingEmitters[path];
        EventInstance eventInstance = FMODUWE.GetEventImpl(path);

        if (is3D)
        {
            eventInstance.set3DAttributes(loopingEmitter.transform.To3DAttributes());
            eventInstance.setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f);
            eventInstance.setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radius);
        }
        else
        {
            eventInstance.setVolume(FMODSystem.CalculateVolume(loopingEmitter.transform.position, Player.main.transform.position, radius, 1f));
        }

        eventInstance.start();
        eventInstance.release();
        loopingEmitter.timeLastStopSound = Time.time;
    }

    public void PlayEventInstance(string path, float volume)
    {
        EventInstance eventInstance = eventInstances[path];
        eventInstance.setVolume(volume);
        eventInstance.start();
    }

    public void StopEventInstance(string path)
    {
        EventInstance eventInstance = eventInstances[path];
        eventInstance.stop(FMOD.Studio.STOP_MODE.IMMEDIATE);
    }

    public static void PlayEventOneShot(FMODAsset asset, float radius, Vector3 origin, float volume = 1f) => PlayEventOneShot(asset.path, radius, origin, volume);

    public static void PlayEventOneShot(string path, float radius, Vector3 origin, float volume = 1f)
    {
        EventInstance evt = FMODUWE.GetEventImpl(path);
        evt.getDescription(out EventDescription description);
        description.is3D(out bool is3D);

        if (is3D)
        {
            evt.setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f);
            evt.setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radius);
            evt.setVolume(volume);
        }
        else
        {
            evt.setVolume(FMODSystem.CalculateVolume(origin, Player.main.transform.position, radius, volume));
        }

        evt.set3DAttributes(origin.To3DAttributes());
        evt.start();
        evt.release();
    }
}
