package land.chipmunk.chipmunkmod.modules;

import land.chipmunk.chipmunkmod.song.Note;
import land.chipmunk.chipmunkmod.song.Song;
import land.chipmunk.chipmunkmod.song.SongLoaderException;
import land.chipmunk.chipmunkmod.song.SongLoaderThread;
import land.chipmunk.chipmunkmod.util.MathUtilities;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.resources.Identifier;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;

public class SongPlayer {
    public static final String SELECTOR = "@a[tag=!nomusic,tag=!chipmunkmod_nomusic]";
    public static final SongPlayer INSTANCE = new SongPlayer(Minecraft.getInstance());
    public static File SONG_DIR = new File("songs");

    static {
        if (!SONG_DIR.exists()) {
            SONG_DIR.mkdir();
        }
    }

    private final Minecraft client;
    public Song currentSong;
    public LinkedList<Song> songQueue = new LinkedList<>();
    public Timer playTimer;
    public SongLoaderThread loaderThread;
    public boolean useCore = true;
    public boolean actionbar = true;

    public float pitch = 0;
    private int ticksUntilPausedActionbar = 20;

    public SongPlayer (final Minecraft client) {
        this.client = client;
    }

    // TODO: Less duplicate code
    // TODO: Reduce the mess

    public void loadSong (final Path location) {
        final LocalPlayer player = client.player;

        if (player == null) return;

        if (loaderThread != null) {
            player.sendMessage(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
            return;
        }

        try {
            final SongLoaderThread _loaderThread = new SongLoaderThread(location);
            player.sendMessage(Component.translatable("Loading %s", Component.text(location.toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
            _loaderThread.start();
            loaderThread = _loaderThread;
        } catch (final SongLoaderException e) {
            player.sendMessage(Component.translatable("Failed to load song: %s", Component.text(e.message.getString())).color(NamedTextColor.RED));
            loaderThread = null;
        }
    }

    public void loadSong (final URL location) {
        final LocalPlayer player = client.player;

        if (player == null) return;

        if (loaderThread != null) {
            player.sendMessage(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
            return;
        }

        try {
            final SongLoaderThread _loaderThread = new SongLoaderThread(location);
            player.sendMessage(Component.translatable("Loading %s", Component.text(location.toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
            _loaderThread.start();
            loaderThread = _loaderThread;
        } catch (final SongLoaderException e) {
            player.sendMessage(Component.translatable("Failed to load song: %s", Component.text(e.message.getString())).color(NamedTextColor.RED));
            loaderThread = null;
        }
    }

    public void coreReady () {
        playTimer = new Timer();

        final TimerTask playTask = new TimerTask() {
            @Override
            public void run () {
                final ClientPacketListener networkHandler = client.getConnection();

                if (networkHandler == null) {
                    disconnected();
                    return;
                }

                if (loaderThread != null && !loaderThread.isAlive() && client.player != null) {
                    if (loaderThread.exception != null) {
                        final net.minecraft.network.chat.Component exceptionMessage = loaderThread.exception.message;
                        client.execute(() -> client.player.sendMessage(Component.translatable("Failed to load song: %s", Component.text(exceptionMessage.getString())).color(NamedTextColor.RED)));
                    } else {
                        songQueue.add(loaderThread.song);
                        final Component name = loaderThread.song.name;
                        client.execute(() -> client.player.sendMessage(Component.translatable("Added %s to the song queue", Component.empty().append(name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN)));
                    }
                    loaderThread = null;
                }

                if (currentSong == null) {
                    if (songQueue.isEmpty()) return;

                    currentSong = songQueue.poll();
                    if (client.player != null) {
                        client.execute(() -> client.player.sendMessage(
                                Component
                                        .translatable(
                                                "Now playing %s",
                                                Component.empty()
                                                        .append(currentSong.name)
                                                        .color(NamedTextColor.DARK_GREEN)
                                        )
                                        .color(NamedTextColor.GREEN)
                        ));
                    }
                    currentSong.play();
                }

                if (currentSong.paused && ticksUntilPausedActionbar-- < 0) return;
                else ticksUntilPausedActionbar = 20;

                try {
                    if (!useCore && actionbar && client.player != null)
                        client.player.sendActionBar(generateActionbar());
                    else if (actionbar)
                        CommandCore.INSTANCE.run("title " + SELECTOR + " actionbar " + GsonComponentSerializer.gson().serialize(generateActionbar()));
                } catch (final Exception e) {
                    e.printStackTrace();
                }

                if (currentSong == null || currentSong.paused) return;

                handlePlaying();

                if (currentSong.finished()) {
                    if (client.player != null)
                        client.player.sendMessage(Component.translatable("Finished playing %s", Component.empty().append(currentSong.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
                    currentSong = null;
                }
            }
        };

        playTimer.schedule(playTask, 60, 50);

        if (currentSong != null) currentSong.play();
    }

    public Component generateActionbar () {
        final LocalPlayer player = client.player;

        final Component component = Component.empty()
                .append(Component.translatable("%s", player.getName().getString()).color(NamedTextColor.GREEN))
                .append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
                .append(Component.translatable("Now playing %s", Component.empty().append(currentSong.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN))
                .append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
                .append(Component.translatable("%s / %s", formatTime(currentSong.time).color(NamedTextColor.GREEN), formatTime(currentSong.length).color(NamedTextColor.GREEN)).color(NamedTextColor.GRAY))
                .append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
                .append(Component.translatable("%s / %s", Component.text(currentSong.position, NamedTextColor.GREEN), Component.text(currentSong.size(), NamedTextColor.GREEN)).color(NamedTextColor.GRAY));

        if (currentSong.paused) {
            return component
                    .append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
                    .append(Component.translatable("Paused", NamedTextColor.DARK_GREEN));
        }

        if (currentSong.looping) {
            if (currentSong.loopCount > 0) {
                return component
                        .append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
                        .append(Component.translatable("Looping (%s/%s)", Component.text(currentSong.currentLoop), Component.text(currentSong.loopCount)).color(NamedTextColor.DARK_GREEN));
            }

            return component
                    .append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
                    .append(Component.translatable("Looping", NamedTextColor.DARK_GREEN));
        }

        return component;
    }

    public Component formatTime (final long millis) {
        final int seconds = (int) millis / 1000;

        final String minutePart = String.valueOf(seconds / 60);
        final String unpaddedSecondPart = String.valueOf(seconds % 60);

        return Component.translatable(
                "%s:%s",
                Component.text(minutePart),
                Component.text(unpaddedSecondPart.length() < 2 ? "0" + unpaddedSecondPart : unpaddedSecondPart)
        );
    }

    public void stopPlaying () {
        currentSong = null;
    }

    public void disconnected () {
        playTimer.cancel();
        playTimer.purge();

        if (currentSong != null) currentSong.pause();
    }

    public void handlePlaying () {
        currentSong.advanceTime();
        while (currentSong.reachedNextNote()) {
            final Note note = currentSong.getNextNote();

            try {
                if (!useCore && client.player != null) {
                    final float floatingPitch = (float) (0.5 * (Math.pow(2, ((note.pitch + (pitch / 10)) / 12))));

                    final String sound = note.instrument.sound;

                    client.submit(() -> client.level.playLocalSound(
                            client.player.getX() + note.position.x(),
                            client.player.getY() + note.position.y(),
                            client.player.getZ() + note.position.z(),
                            SoundEvent.createVariableRangeEvent(Identifier.fromNamespaceAndPath(Key.MINECRAFT_NAMESPACE, sound)),
                            SoundSource.RECORDS,
                            note.volume,
                            floatingPitch,
                            true
                    ));
                } else {
                    final double floatingPitch = MathUtilities.clamp(0.5 * (Math.pow(2, ((note.pitch + (pitch / 10)) / 12))), 0F, 2F);

                    CommandCore.INSTANCE.run(
                            String.format(
                                    "execute as %s at @s run playsound %s record @s ^%f ^%f ^%f %f %f",

                                    SELECTOR,

                                    note.instrument.sound,

                                    note.position.x(),
                                    note.position.y(),
                                    note.position.z(),

                                    note.volume,
                                    floatingPitch
                            )
                    );
                }
            } catch (final Exception e) {
                e.printStackTrace();
            }
        }
    }
}
