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.audience.Audience;
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.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;

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 File SONG_DIR = new File("songs");
    static {
        if (!SONG_DIR.exists()) {
            SONG_DIR.mkdir();
        }
    }

    public static final SongPlayer INSTANCE = new SongPlayer(MinecraftClient.getInstance());

    public Song currentSong;
    public LinkedList<Song> songQueue = new LinkedList<>();
    public Timer playTimer;
    public SongLoaderThread loaderThread;
    private int ticksUntilPausedActionbar = 20;

    public boolean useCore = true;
    public boolean actionbar = true;

    public float pitch = 0;

    private final MinecraftClient client;

    public SongPlayer (MinecraftClient client) {
        this.client = client;
    }

    // TODO: Less duplicate code

    public void loadSong (Path location) {
        if (loaderThread != null) {
            ((Audience) client.player).sendMessage(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
            return;
        }

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

    public void loadSong (URL location) {
        if (loaderThread != null) {
            ((Audience) client.player).sendMessage(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
            return;
        }

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

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

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

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

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

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

                    currentSong = songQueue.poll();
                    ((Audience) 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) ((Audience) client.player).sendActionBar(generateActionbar());
                    else if (actionbar) CommandCore.INSTANCE.run("title " + SELECTOR + " actionbar " + GsonComponentSerializer.gson().serialize(generateActionbar()));
                } catch (Exception e) {
                    e.printStackTrace();
                }

                if (currentSong.paused) return;

                handlePlaying();

                if (currentSong.finished()) {
                    ((Audience) 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 ClientPlayerEntity player = client.player;

        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 (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[] thing = note.instrument.sound.split(":");

                    if (thing[1] == null) return; // idk if this can be null but ill just protect it for now i guess

                    client.submit(() -> client.world.playSound(
                            client.player.getX(),
                            client.player.getY(),
                            client.player.getZ(),
                            SoundEvent.of(Identifier.of(thing[0], thing[1])),
                            SoundCategory.RECORDS,
                            note.volume,
                            floatingPitch,
                            true
                    ));
                } else {
                    final float floatingPitch = MathUtilities.clamp((float) (0.5 * (Math.pow(2, ((note.pitch + (pitch / 10)) / 12)))), 0F, 2F);

                    CommandCore.INSTANCE.run("execute as " + SELECTOR + " at @s run playsound " + note.instrument.sound + " record @s ~ ~ ~ " + note.volume + " " + floatingPitch);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
