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 com.mojang.math.Vector3f;
import net.minecraft.client.Camera;
import net.minecraft.util.Mth;
import java.util.concurrent.CompletableFuture;
import net.minecraft.world.phys.Vec3;
import java.util.function.Consumer;
import com.mojang.blaze3d.audio.Channel;
import javax.annotation.Nullable;
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 epn, final Options dmb, final ResourceManager abc) {
        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 = epn;
        this.options = dmb;
        this.soundBuffers = new SoundBufferLibrary(abc);
    }
    
    public void reload() {
        SoundEngine.ONLY_WARN_ONCE.clear();
        for (final SoundEvent ack3 : Registry.SOUND_EVENT) {
            final ResourceLocation uh4 = ack3.getLocation();
            if (this.soundManager.getSoundEvent(uh4) == null) {
                SoundEngine.LOGGER.warn("Missing sound for event: {}", Registry.SOUND_EVENT.getKey(ack3));
                SoundEngine.ONLY_WARN_ONCE.add(uh4);
            }
        }
        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(@Nullable final SoundSource acm) {
        if (acm == null || acm == SoundSource.MASTER) {
            return 1.0f;
        }
        return this.options.getSoundSourceVolume(acm);
    }
    
    public void updateCategoryVolume(final SoundSource acm, final float float2) {
        if (!this.loaded) {
            return;
        }
        if (acm == SoundSource.MASTER) {
            this.listener.setGain(float2);
            return;
        }
        final float float3;
        final float volume;
        this.instanceToChannel.forEach((eom, a) -> {
            float3 = this.calculateVolume(eom);
            a.execute(dft -> {
                if (volume <= 0.0f) {
                    dft.stop();
                }
                else {
                    dft.setVolume(volume);
                }
            });
        });
    }
    
    public void destroy() {
        if (this.loaded) {
            this.stopAll();
            this.soundBuffers.clear();
            this.library.cleanup();
            this.loaded = false;
        }
    }
    
    public void stop(final SoundInstance eom) {
        if (this.loaded) {
            final ChannelAccess.ChannelHandle a3 = this.instanceToChannel.get(eom);
            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 epm) {
        this.listeners.add(epm);
    }
    
    public void removeEventListener(final SoundEventListener epm) {
        this.listeners.remove(epm);
    }
    
    public void tick(final boolean boolean1) {
        if (!boolean1) {
            this.tickNonPaused();
        }
        this.channelAccess.scheduleTick();
    }
    
    private void tickNonPaused() {
        ++this.tickCount;
        this.queuedTickableSounds.stream().filter(SoundInstance::canPlaySound).forEach(this::play);
        this.queuedTickableSounds.clear();
        for (final TickableSoundInstance eon3 : this.tickingSounds) {
            if (!eon3.canPlaySound()) {
                this.stop(eon3);
            }
            eon3.tick();
            if (eon3.isStopped()) {
                this.stop(eon3);
            }
            else {
                final float float4 = this.calculateVolume(eon3);
                final float float5 = this.calculatePitch(eon3);
                final Vec3 dem6 = new Vec3(eon3.getX(), eon3.getY(), eon3.getZ());
                final ChannelAccess.ChannelHandle a7 = this.instanceToChannel.get(eon3);
                if (a7 == null) {
                    continue;
                }
                final float volume;
                final float pitch;
                final Vec3 selfPosition;
                a7.execute(dft -> {
                    dft.setVolume(volume);
                    dft.setPitch(pitch);
                    dft.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 eom5 = entry3.getKey();
            final float float6 = this.options.getSoundSourceVolume(eom5.getSource());
            if (float6 <= 0.0f) {
                a8.execute(Channel::stop);
                iterator2.remove();
            }
            else {
                if (!a8.isStopped()) {
                    continue;
                }
                final int integer7 = this.soundDeleteTime.get(eom5);
                if (integer7 > this.tickCount) {
                    continue;
                }
                if (shouldLoopManually(eom5)) {
                    this.queuedSounds.put(eom5, this.tickCount + eom5.getDelay());
                }
                iterator2.remove();
                SoundEngine.LOGGER.debug(SoundEngine.MARKER, "Removed channel {} because it's not playing anymore", a8);
                this.soundDeleteTime.remove(eom5);
                try {
                    this.instanceBySource.remove(eom5.getSource(), eom5);
                }
                catch (RuntimeException ex) {}
                if (!(eom5 instanceof TickableSoundInstance)) {
                    continue;
                }
                this.tickingSounds.remove(eom5);
            }
        }
        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 eom5 = entry4.getKey();
                if (eom5 instanceof TickableSoundInstance) {
                    ((TickableSoundInstance)eom5).tick();
                }
                this.play(eom5);
                iterator3.remove();
            }
        }
    }
    
    private static boolean requiresManualLooping(final SoundInstance eom) {
        return eom.getDelay() > 0;
    }
    
    private static boolean shouldLoopManually(final SoundInstance eom) {
        return eom.isLooping() && requiresManualLooping(eom);
    }
    
    private static boolean shouldLoopAutomatically(final SoundInstance eom) {
        return eom.isLooping() && !requiresManualLooping(eom);
    }
    
    public boolean isActive(final SoundInstance eom) {
        return this.loaded && ((this.soundDeleteTime.containsKey(eom) && this.soundDeleteTime.get(eom) <= this.tickCount) || this.instanceToChannel.containsKey(eom));
    }
    
    public void play(final SoundInstance eom) {
        if (!this.loaded) {
            return;
        }
        if (!eom.canPlaySound()) {
            return;
        }
        final WeighedSoundEvents epo3 = eom.resolve(this.soundManager);
        final ResourceLocation uh4 = eom.getLocation();
        if (epo3 == null) {
            if (SoundEngine.ONLY_WARN_ONCE.add(uh4)) {
                SoundEngine.LOGGER.warn(SoundEngine.MARKER, "Unable to play unknown soundEvent: {}", uh4);
            }
            return;
        }
        final Sound eoj5 = eom.getSound();
        if (eoj5 == SoundManager.EMPTY_SOUND) {
            if (SoundEngine.ONLY_WARN_ONCE.add(uh4)) {
                SoundEngine.LOGGER.warn(SoundEngine.MARKER, "Unable to play empty soundEvent: {}", uh4);
            }
            return;
        }
        final float float6 = eom.getVolume();
        final float float7 = Math.max(float6, 1.0f) * eoj5.getAttenuationDistance();
        final SoundSource acm8 = eom.getSource();
        final float float8 = this.calculateVolume(eom);
        final float float9 = this.calculatePitch(eom);
        final SoundInstance.Attenuation a11 = eom.getAttenuation();
        final boolean boolean12 = eom.isRelative();
        if (float8 == 0.0f && !eom.canStartSilent()) {
            SoundEngine.LOGGER.debug(SoundEngine.MARKER, "Skipped playing sound {}, volume was zero.", eoj5.getLocation());
            return;
        }
        final Vec3 dem13 = new Vec3(eom.getX(), eom.getY(), eom.getZ());
        if (!this.listeners.isEmpty()) {
            final boolean boolean13 = boolean12 || a11 == SoundInstance.Attenuation.NONE || this.listener.getListenerPosition().distanceToSqr(dem13) < float7 * float7;
            if (boolean13) {
                for (final SoundEventListener epm16 : this.listeners) {
                    epm16.onPlaySound(eom, epo3);
                }
            }
            else {
                SoundEngine.LOGGER.debug(SoundEngine.MARKER, "Did not notify listeners of soundEvent: {}, it is too far away to hear", uh4);
            }
        }
        if (this.listener.getGain() <= 0.0f) {
            SoundEngine.LOGGER.debug(SoundEngine.MARKER, "Skipped playing soundEvent: {}, master volume was zero", uh4);
            return;
        }
        final boolean boolean13 = shouldLoopAutomatically(eom);
        final boolean boolean14 = eoj5.shouldStream();
        final CompletableFuture<ChannelAccess.ChannelHandle> completableFuture16 = this.channelAccess.createHandle(eoj5.shouldStream() ? Library.Pool.STREAMING : Library.Pool.STATIC);
        final ChannelAccess.ChannelHandle a12 = completableFuture16.join();
        if (a12 == null) {
            SoundEngine.LOGGER.warn("Failed to create new sound handle");
            return;
        }
        SoundEngine.LOGGER.debug(SoundEngine.MARKER, "Playing sound {} for event {}", eoj5.getLocation(), uh4);
        this.soundDeleteTime.put(eom, this.tickCount + 20);
        this.instanceToChannel.put(eom, a12);
        this.instanceBySource.put(acm8, eom);
        final float pitch;
        final float volume;
        final SoundInstance.Attenuation attenuation;
        final float float10;
        final boolean b;
        final boolean b2;
        final Vec3 selfPosition;
        final boolean relative;
        a12.execute(dft -> {
            dft.setPitch(pitch);
            dft.setVolume(volume);
            if (attenuation == SoundInstance.Attenuation.LINEAR) {
                dft.linearAttenuation(float10);
            }
            else {
                dft.disableAttenuation();
            }
            dft.setLooping(b && !b2);
            dft.setSelfPosition(selfPosition);
            dft.setRelative(relative);
            return;
        });
        if (!boolean14) {
            this.soundBuffers.getCompleteBuffer(eoj5.getPath()).thenAccept(dfy -> a12.execute(dft -> {
                dft.attachStaticBuffer(dfy);
                dft.play();
            }));
        }
        else {
            this.soundBuffers.getStream(eoj5.getPath(), boolean13).thenAccept(epf -> a12.execute(dft -> {
                dft.attachBufferStream(epf);
                dft.play();
            }));
        }
        if (eom instanceof TickableSoundInstance) {
            this.tickingSounds.add((TickableSoundInstance)eom);
        }
    }
    
    public void queueTickingSound(final TickableSoundInstance eon) {
        this.queuedTickableSounds.add(eon);
    }
    
    public void requestPreload(final Sound eoj) {
        this.preloadQueue.add(eoj);
    }
    
    private float calculatePitch(final SoundInstance eom) {
        return Mth.clamp(eom.getPitch(), 0.5f, 2.0f);
    }
    
    private float calculateVolume(final SoundInstance eom) {
        return Mth.clamp(eom.getVolume() * this.getVolume(eom.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 eom, final int integer) {
        this.queuedSounds.put(eom, this.tickCount + integer);
    }
    
    public void updateSource(final Camera dlj) {
        if (!this.loaded || !dlj.isInitialized()) {
            return;
        }
        final Vec3 dem3 = dlj.getPosition();
        final Vector3f g4 = dlj.getLookVector();
        final Vector3f g5 = dlj.getUpVector();
        final Vec3 listenerPosition;
        final Vector3f g6;
        final Vector3f g7;
        this.executor.execute(() -> {
            this.listener.setListenerPosition(listenerPosition);
            this.listener.setListenerOrientation(g6, g7);
        });
    }
    
    public void stop(@Nullable final ResourceLocation uh, @Nullable final SoundSource acm) {
        if (acm != null) {
            for (final SoundInstance eom5 : this.instanceBySource.get(acm)) {
                if (uh == null || eom5.getLocation().equals(uh)) {
                    this.stop(eom5);
                }
            }
        }
        else if (uh == null) {
            this.stopAll();
        }
        else {
            for (final SoundInstance eom5 : this.instanceToChannel.keySet()) {
                if (eom5.getLocation().equals(uh)) {
                    this.stop(eom5);
                }
            }
        }
    }
    
    public String getDebugString() {
        return this.library.getDebugString();
    }
    
    static {
        MARKER = MarkerManager.getMarker("SOUNDS");
        LOGGER = LogManager.getLogger();
        ONLY_WARN_ONCE = Sets.newHashSet();
    }
}
