package net.minecraft.client.sounds;

import com.google.common.collect.Sets;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.MarkerManager;
import com.mojang.blaze3d.audio.SoundBuffer;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.mojang.math.Vector3f;
import net.minecraft.client.Camera;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import java.util.function.Consumer;
import com.mojang.blaze3d.audio.Channel;
import java.util.Collection;
import java.util.Iterator;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.core.Registry;
import com.google.common.collect.Lists;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import java.util.concurrent.Executor;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.client.resources.sounds.Sound;
import net.minecraft.client.resources.sounds.TickableSoundInstance;
import java.util.List;
import net.minecraft.sounds.SoundSource;
import com.google.common.collect.Multimap;
import net.minecraft.client.resources.sounds.SoundInstance;
import java.util.Map;
import com.mojang.blaze3d.audio.Listener;
import com.mojang.blaze3d.audio.Library;
import net.minecraft.client.Options;
import net.minecraft.resources.ResourceLocation;
import java.util.Set;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;

public class SoundEngine {
    private static final Marker MARKER;
    private static final Logger LOGGER;
    private static final Set<ResourceLocation> ONLY_WARN_ONCE;
    private final SoundManager soundManager;
    private final Options options;
    private boolean loaded;
    private final Library library;
    private final Listener listener;
    private final SoundBufferLibrary soundBuffers;
    private final SoundEngineExecutor executor;
    private final ChannelAccess channelAccess;
    private int tickCount;
    private final Map<SoundInstance, ChannelAccess.ChannelHandle> instanceToChannel;
    private final Multimap<SoundSource, SoundInstance> instanceBySource;
    private final List<TickableSoundInstance> tickingSounds;
    private final Map<SoundInstance, Integer> queuedSounds;
    private final Map<SoundInstance, Integer> soundDeleteTime;
    private final List<SoundEventListener> listeners;
    private final List<TickableSoundInstance> queuedTickableSounds;
    private final List<Sound> preloadQueue;
    
    public SoundEngine(final SoundManager edz, final Options dbr, final ResourceManager zb) {
        this.library = new Library();
        this.listener = this.library.getListener();
        this.executor = new SoundEngineExecutor();
        this.channelAccess = new ChannelAccess(this.library, this.executor);
        this.instanceToChannel = Maps.newHashMap();
        this.instanceBySource = HashMultimap.create();
        this.tickingSounds = Lists.newArrayList();
        this.queuedSounds = Maps.newHashMap();
        this.soundDeleteTime = Maps.newHashMap();
        this.listeners = Lists.newArrayList();
        this.queuedTickableSounds = Lists.newArrayList();
        this.preloadQueue = Lists.newArrayList();
        this.soundManager = edz;
        this.options = dbr;
        this.soundBuffers = new SoundBufferLibrary(zb);
    }
    
    public void reload() {
        SoundEngine.ONLY_WARN_ONCE.clear();
        for (final SoundEvent aah3 : Registry.SOUND_EVENT) {
            final ResourceLocation sm4 = aah3.getLocation();
            if (this.soundManager.getSoundEvent(sm4) == null) {
                SoundEngine.LOGGER.warn("Missing sound for event: {}", Registry.SOUND_EVENT.getKey(aah3));
                SoundEngine.ONLY_WARN_ONCE.add(sm4);
            }
        }
        this.destroy();
        this.loadLibrary();
    }
    
    private synchronized void loadLibrary() {
        if (this.loaded) {
            return;
        }
        try {
            this.library.init();
            this.listener.reset();
            this.listener.setGain(this.options.getSoundSourceVolume(SoundSource.MASTER));
            this.soundBuffers.preload(this.preloadQueue).thenRun(this.preloadQueue::clear);
            this.loaded = true;
            SoundEngine.LOGGER.info(SoundEngine.MARKER, "Sound engine started");
        }
        catch (RuntimeException runtimeException2) {
            SoundEngine.LOGGER.error(SoundEngine.MARKER, "Error starting SoundSystem. Turning off sounds & music", (Throwable)runtimeException2);
        }
    }
    
    private float getVolume(final SoundSource aaj) {
        if (aaj == null || aaj == SoundSource.MASTER) {
            return 1.0f;
        }
        return this.options.getSoundSourceVolume(aaj);
    }
    
    public void updateCategoryVolume(final SoundSource aaj, final float float2) {
        if (!this.loaded) {
            return;
        }
        if (aaj == SoundSource.MASTER) {
            this.listener.setGain(float2);
            return;
        }
        final float float3;
        final float volume;
        this.instanceToChannel.forEach((ecz, a) -> {
            float3 = this.calculateVolume(ecz);
            a.execute(cws -> {
                if (volume <= 0.0f) {
                    cws.stop();
                }
                else {
                    cws.setVolume(volume);
                }
            });
        });
    }
    
    public void destroy() {
        if (this.loaded) {
            this.stopAll();
            this.soundBuffers.clear();
            this.library.cleanup();
            this.loaded = false;
        }
    }
    
    public void stop(final SoundInstance ecz) {
        if (this.loaded) {
            final ChannelAccess.ChannelHandle a3 = this.instanceToChannel.get(ecz);
            if (a3 != null) {
                a3.execute(Channel::stop);
            }
        }
    }
    
    public void stopAll() {
        if (this.loaded) {
            this.executor.flush();
            this.instanceToChannel.values().forEach(a -> a.execute(Channel::stop));
            this.instanceToChannel.clear();
            this.channelAccess.clear();
            this.queuedSounds.clear();
            this.tickingSounds.clear();
            this.instanceBySource.clear();
            this.soundDeleteTime.clear();
            this.queuedTickableSounds.clear();
        }
    }
    
    public void addEventListener(final SoundEventListener edy) {
        this.listeners.add(edy);
    }
    
    public void removeEventListener(final SoundEventListener edy) {
        this.listeners.remove(edy);
    }
    
    public void tick(final boolean boolean1) {
        if (!boolean1) {
            this.tickNonPaused();
        }
        this.channelAccess.scheduleTick();
    }
    
    private void tickNonPaused() {
        ++this.tickCount;
        this.queuedTickableSounds.forEach(this::play);
        this.queuedTickableSounds.clear();
        for (final TickableSoundInstance eda3 : this.tickingSounds) {
            eda3.tick();
            if (eda3.isStopped()) {
                this.stop(eda3);
            }
            else {
                final float float4 = this.calculateVolume(eda3);
                final float float5 = this.calculatePitch(eda3);
                final Vec3 cvl6 = new Vec3(eda3.getX(), eda3.getY(), eda3.getZ());
                final ChannelAccess.ChannelHandle a7 = this.instanceToChannel.get(eda3);
                if (a7 == null) {
                    continue;
                }
                final float volume;
                final float pitch;
                final Vec3 selfPosition;
                a7.execute(cws -> {
                    cws.setVolume(volume);
                    cws.setPitch(pitch);
                    cws.setSelfPosition(selfPosition);
                    return;
                });
            }
        }
        final Iterator<Map.Entry<SoundInstance, ChannelAccess.ChannelHandle>> iterator2 = this.instanceToChannel.entrySet().iterator();
        while (iterator2.hasNext()) {
            final Map.Entry<SoundInstance, ChannelAccess.ChannelHandle> entry3 = iterator2.next();
            final ChannelAccess.ChannelHandle a8 = entry3.getValue();
            final SoundInstance ecz5 = entry3.getKey();
            final float float6 = this.options.getSoundSourceVolume(ecz5.getSource());
            if (float6 <= 0.0f) {
                a8.execute(Channel::stop);
                iterator2.remove();
            }
            else {
                if (!a8.isStopped()) {
                    continue;
                }
                final int integer7 = this.soundDeleteTime.get(ecz5);
                if (integer7 > this.tickCount) {
                    continue;
                }
                final int integer8 = ecz5.getDelay();
                if (ecz5.isLooping() && integer8 > 0) {
                    this.queuedSounds.put(ecz5, this.tickCount + integer8);
                }
                iterator2.remove();
                SoundEngine.LOGGER.debug(SoundEngine.MARKER, "Removed channel {} because it's not playing anymore", a8);
                this.soundDeleteTime.remove(ecz5);
                try {
                    this.instanceBySource.remove(ecz5.getSource(), ecz5);
                }
                catch (RuntimeException ex) {}
                if (!(ecz5 instanceof TickableSoundInstance)) {
                    continue;
                }
                this.tickingSounds.remove(ecz5);
            }
        }
        final Iterator<Map.Entry<SoundInstance, Integer>> iterator3 = this.queuedSounds.entrySet().iterator();
        while (iterator3.hasNext()) {
            final Map.Entry<SoundInstance, Integer> entry4 = iterator3.next();
            if (this.tickCount >= entry4.getValue()) {
                final SoundInstance ecz5 = entry4.getKey();
                if (ecz5 instanceof TickableSoundInstance) {
                    ((TickableSoundInstance)ecz5).tick();
                }
                this.play(ecz5);
                iterator3.remove();
            }
        }
    }
    
    public boolean isActive(final SoundInstance ecz) {
        return this.loaded && ((this.soundDeleteTime.containsKey(ecz) && this.soundDeleteTime.get(ecz) <= this.tickCount) || this.instanceToChannel.containsKey(ecz));
    }
    
    public void play(final SoundInstance ecz) {
        if (!this.loaded) {
            return;
        }
        final WeighedSoundEvents eea3 = ecz.resolve(this.soundManager);
        final ResourceLocation sm4 = ecz.getLocation();
        if (eea3 == null) {
            if (SoundEngine.ONLY_WARN_ONCE.add(sm4)) {
                SoundEngine.LOGGER.warn(SoundEngine.MARKER, "Unable to play unknown soundEvent: {}", sm4);
            }
            return;
        }
        if (!this.listeners.isEmpty()) {
            for (final SoundEventListener edy6 : this.listeners) {
                edy6.onPlaySound(ecz, eea3);
            }
        }
        if (this.listener.getGain() <= 0.0f) {
            SoundEngine.LOGGER.debug(SoundEngine.MARKER, "Skipped playing soundEvent: {}, master volume was zero", sm4);
            return;
        }
        final Sound ecw5 = ecz.getSound();
        if (ecw5 == SoundManager.EMPTY_SOUND) {
            if (SoundEngine.ONLY_WARN_ONCE.add(sm4)) {
                SoundEngine.LOGGER.warn(SoundEngine.MARKER, "Unable to play empty soundEvent: {}", sm4);
            }
            return;
        }
        final float float6 = ecz.getVolume();
        final float float7 = Math.max(float6, 1.0f) * ecw5.getAttenuationDistance();
        final SoundSource aaj8 = ecz.getSource();
        final float float8 = this.calculateVolume(ecz);
        final float float9 = this.calculatePitch(ecz);
        final SoundInstance.Attenuation a11 = ecz.getAttenuation();
        final boolean boolean12 = ecz.isRelative();
        if (float8 == 0.0f && !ecz.canStartSilent()) {
            SoundEngine.LOGGER.debug(SoundEngine.MARKER, "Skipped playing sound {}, volume was zero.", ecw5.getLocation());
            return;
        }
        final boolean boolean13 = ecz.isLooping() && ecz.getDelay() == 0;
        final Vec3 cvl14 = new Vec3(ecz.getX(), ecz.getY(), ecz.getZ());
        final ChannelAccess.ChannelHandle a12 = this.channelAccess.createHandle(ecw5.shouldStream() ? Library.Pool.STREAMING : Library.Pool.STATIC);
        SoundEngine.LOGGER.debug(SoundEngine.MARKER, "Playing sound {} for event {}", ecw5.getLocation(), sm4);
        this.soundDeleteTime.put(ecz, this.tickCount + 20);
        this.instanceToChannel.put(ecz, a12);
        this.instanceBySource.put(aaj8, ecz);
        final float pitch;
        final float volume;
        final SoundInstance.Attenuation attenuation;
        final float float10;
        final boolean looping;
        final Vec3 selfPosition;
        final boolean relative;
        a12.execute(cws -> {
            cws.setPitch(pitch);
            cws.setVolume(volume);
            if (attenuation == SoundInstance.Attenuation.LINEAR) {
                cws.linearAttenuation(float10);
            }
            else {
                cws.disableAttenuation();
            }
            cws.setLooping(looping);
            cws.setSelfPosition(selfPosition);
            cws.setRelative(relative);
            return;
        });
        if (!ecw5.shouldStream()) {
            this.soundBuffers.getCompleteBuffer(ecw5.getPath()).thenAccept(cwx -> a12.execute(cws -> {
                cws.attachStaticBuffer(cwx);
                cws.play();
            }));
        }
        else {
            this.soundBuffers.getStream(ecw5.getPath()).thenAccept(eds -> a12.execute(cws -> {
                cws.attachBufferStream(eds);
                cws.play();
            }));
        }
        if (ecz instanceof TickableSoundInstance) {
            this.tickingSounds.add((TickableSoundInstance)ecz);
        }
    }
    
    public void queueTickingSound(final TickableSoundInstance eda) {
        this.queuedTickableSounds.add(eda);
    }
    
    public void requestPreload(final Sound ecw) {
        this.preloadQueue.add(ecw);
    }
    
    private float calculatePitch(final SoundInstance ecz) {
        return Mth.clamp(ecz.getPitch(), 0.5f, 2.0f);
    }
    
    private float calculateVolume(final SoundInstance ecz) {
        return Mth.clamp(ecz.getVolume() * this.getVolume(ecz.getSource()), 0.0f, 1.0f);
    }
    
    public void pause() {
        if (this.loaded) {
            this.channelAccess.executeOnChannels(stream -> stream.forEach(Channel::pause));
        }
    }
    
    public void resume() {
        if (this.loaded) {
            this.channelAccess.executeOnChannels(stream -> stream.forEach(Channel::unpause));
        }
    }
    
    public void playDelayed(final SoundInstance ecz, final int integer) {
        this.queuedSounds.put(ecz, this.tickCount + integer);
    }
    
    public void updateSource(final Camera dbb) {
        if (!this.loaded || !dbb.isInitialized()) {
            return;
        }
        final Vec3 cvl3 = dbb.getPosition();
        final Vector3f e4 = dbb.getLookVector();
        final Vector3f e5 = dbb.getUpVector();
        final Vec3 listenerPosition;
        final Vector3f e6;
        final Vector3f e7;
        this.executor.execute(() -> {
            this.listener.setListenerPosition(listenerPosition);
            this.listener.setListenerOrientation(e6, e7);
        });
    }
    
    public void stop(@Nullable final ResourceLocation sm, @Nullable final SoundSource aaj) {
        if (aaj != null) {
            for (final SoundInstance ecz5 : this.instanceBySource.get(aaj)) {
                if (sm == null || ecz5.getLocation().equals(sm)) {
                    this.stop(ecz5);
                }
            }
        }
        else if (sm == null) {
            this.stopAll();
        }
        else {
            for (final SoundInstance ecz5 : this.instanceToChannel.keySet()) {
                if (ecz5.getLocation().equals(sm)) {
                    this.stop(ecz5);
                }
            }
        }
    }
    
    public String getDebugString() {
        return this.library.getDebugString();
    }
    
    static {
        MARKER = MarkerManager.getMarker("SOUNDS");
        LOGGER = LogManager.getLogger();
        ONLY_WARN_ONCE = Sets.newHashSet();
    }
}
