package net.halbear.Terrain4J.EngineCore.Audio;

import net.halbear.Terrain4J.EngineCore.Main.Scene.Camera;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.ALC;
import org.lwjgl.openal.ALCCapabilities;
import org.tinylog.Logger;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;

import static java.sql.Types.NULL;
import static org.lwjgl.openal.AL10.alDistanceModel;
import static org.lwjgl.openal.ALC10.*;

public class AudioManager {
    private final long Context;
    private final long Device;
    private final Map<String, SoundBuffer> SoundBufferMap;
    private final Map<String, AudioSource> SoundSourceMap;
    private SoundListener listener;

    public AudioManager(){
        SoundBufferMap = new HashMap<>();
        SoundSourceMap = new HashMap<>();
        Device = alcOpenDevice((ByteBuffer)null);
        if(Device == NULL){
            throw new IllegalStateException("Failed to open default OpenAL device");
        }
        ALCCapabilities deviceCapabilies = ALC.createCapabilities(Device);
        this.Context = alcCreateContext(Device,(IntBuffer) null);
        if(Context == NULL){
            throw new IllegalStateException("Failed to create OpenAL context.");
        }
        alcMakeContextCurrent(Context);
        AL.createCapabilities(deviceCapabilies);
    }

    public void AddSoundBuffer(String name, SoundBuffer soundBuffer){
        this.SoundBufferMap.put(name,soundBuffer);
    }
    public void AddSoundSource(String name, AudioSource source){
        this.SoundSourceMap.put(name,source);
    }
    public void CleanUp(){
        SoundSourceMap.values().forEach(AudioSource::CleanUp);
        SoundSourceMap.clear();
        SoundBufferMap.values().forEach(SoundBuffer::CleanUp);
        SoundBufferMap.clear();
        if(Context != NULL){
            alcDestroyContext(Context);
        }
        if(Device != NULL){
            alcCloseDevice(Device);
        }
    }

    public AudioSource GetAudioSource(String Name){
        return this.SoundSourceMap.get(Name);
    }

    public void Pause(String SourceID){
        AudioSource source = SoundSourceMap.get(SourceID);
        if (source == null) {
            Logger.warn("Unknown audio source [{}]", SourceID);
            return;
        }
        source.Pause();
    }

    public void Play(String SourceID, String BufferID){
        AudioSource audioSource = SoundSourceMap.get(SourceID);
        if (audioSource == null) {
            Logger.warn("Unknown audio source [{}]", SourceID);
            return;
        }
        SoundBuffer soundBuffer = SoundBufferMap.get(BufferID);
        if (soundBuffer == null) {
            Logger.warn("Unknown audio buffer [{}]", BufferID);
            return;
        }
        audioSource.SetBuffer(soundBuffer.GetBufferID());
        audioSource.Play();
    }

    public void PlayAudioSource(String name){
        AudioSource audioSource = this.SoundSourceMap.get(name);
        if(audioSource != null && !audioSource.isPlaying()){
            audioSource.Play();
        }
    }

    public void RemoveAudioSource(String name){
        this.SoundSourceMap.remove(name);
    }

    public void SetAttenuationModel(int model){
        alDistanceModel(model);
    }
    public void SetListener(SoundListener listener){
        this.listener = listener;
    }
    public void Stop(String SourceID){
        AudioSource source = SoundSourceMap.get(SourceID);
        if (source == null) {
            Logger.warn("Unknown audio source [{}]", SourceID);
            return;
        }
        source.Stop();
    }
    public void UpdateListenerPosition(Camera camera){
        Matrix4f viewMatrix = camera.GetViewMatrix();
        listener.SetPosition(camera.GetPosition());
        Vector3f at = new Vector3f();
        viewMatrix.positiveZ(at).negate();
        Vector3f up = new Vector3f();
        viewMatrix.positiveY(up);
        listener.SetOrientation(at,up);
    }
}

