package dev.conorthedev.mediamod.gui;

import dev.conorthedev.mediamod.config.ProgressStyle;
import dev.conorthedev.mediamod.config.Settings;
import dev.conorthedev.mediamod.event.SongChangeEvent;
import dev.conorthedev.mediamod.gui.util.DynamicTextureWrapper;
import dev.conorthedev.mediamod.gui.util.IMediaGui;
import dev.conorthedev.mediamod.media.base.IMediaHandler;
import dev.conorthedev.mediamod.media.base.ServiceHandler;
import dev.conorthedev.mediamod.media.spotify.api.playing.CurrentlyPlayingObject;
import dev.conorthedev.mediamod.media.spotify.api.track.Track;
import dev.conorthedev.mediamod.util.ChatColor;
import dev.conorthedev.mediamod.util.PlayerMessager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.client.FMLClientHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class PlayerOverlay {
    /**
     * An instance of this class
     */
    public static final PlayerOverlay INSTANCE = new PlayerOverlay();
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("mm:ss");
    /**
     * Average Color Cache
     */
    private static final HashMap<BufferedImage, Color> avgColorCache = new HashMap<>();

    /**
     * If the current tick is the first tick being called
     */
    private boolean first = true;

    /**
     * The current song
     *
     * @see CurrentlyPlayingObject
     */
    private CurrentlyPlayingObject currentlyPlayingObject = null;

    /**
     * The previous song
     *
     * @see CurrentlyPlayingObject
     */
    private CurrentlyPlayingObject previousPlayingObject = null;

    /**
     * The length of the concatenated song name
     */
    private int concatNameCount = 0;
    private int concatArtistCount = 0;
    private boolean artistFirstRun = true;
    private boolean firstRun = true;

    private static Color averageColor(BufferedImage bi, int w, int h) {
        final Color[] color = {Color.gray};
        if (avgColorCache.containsKey(bi)) {
            return avgColorCache.get(bi);
        } else {
            new Thread(() -> {
                long sumr = 0, sumg = 0, sumb = 0;
                for (int x = 0; x < w; x++) {
                    for (int y = 0; y < h; y++) {
                        Color pixel = new Color(bi.getRGB(x, y));
                        sumr += pixel.getRed();
                        sumg += pixel.getGreen();
                        sumb += pixel.getBlue();
                    }
                }
                int num = w * h;
                color[0] = new Color((int) sumr / num, (int) sumg / num, (int) sumb / num);

                avgColorCache.put(bi, color[0]);
            }).start();

            return color[0];
        }
    }

    public static void drawModalRectWithCustomSizedTexture(double x, double y, float u, float v, double width, double height, float textureWidth, float textureHeight) {
        float f = 1.0F / textureWidth;
        float f1 = 1.0F / textureHeight;
        Tessellator tessellator = Tessellator.getInstance();
        WorldRenderer worldrenderer = tessellator.getWorldRenderer();
        worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX);
        worldrenderer.pos(x, y + height, 0.0D).tex(u * f, (v + (float) height) * f1).endVertex();
        worldrenderer.pos(x + width, y + height, 0.0D).tex((u + (float) width) * f, (v + (float) height) * f1).endVertex();
        worldrenderer.pos(x + width, y, 0.0D).tex((u + (float) width) * f, v * f1).endVertex();
        worldrenderer.pos(x, y, 0.0D).tex(u * f, v * f1).endVertex();
        tessellator.draw();
    }

    public static int getComplementaryColor(Color colorToInvert) {
        if (colorToInvert == Color.gray || colorToInvert == Color.green) {
            return Color.WHITE.getRGB();
        }
        double y = (299 * colorToInvert.getRed() + 587 * colorToInvert.getGreen() + 114 * colorToInvert.getBlue()) / 1000.0;
        return y >= 128 ? Color.BLACK.getRGB() : Color.WHITE.getRGB();
    }

    private static String formatTime(int milliseconds) {
        return DATE_FORMAT.format(Date.from(Instant.ofEpochMilli(milliseconds)));
    }

    public static void drawRect(double left, double top, double right, double bottom, int color) {
        if (left < right) {
            double i = left;
            left = right;
            right = i;
        }

        if (top < bottom) {
            double j = top;
            top = bottom;
            bottom = j;
        }

        float f3 = (float) (color >> 24 & 255) / 255.0F;
        float f = (float) (color >> 16 & 255) / 255.0F;
        float f1 = (float) (color >> 8 & 255) / 255.0F;
        float f2 = (float) (color & 255) / 255.0F;
        Tessellator tessellator = Tessellator.getInstance();
        WorldRenderer worldrenderer = tessellator.getWorldRenderer();
        GlStateManager.enableBlend();
        GlStateManager.disableTexture2D();
        GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
        GlStateManager.color(f, f1, f2, f3);
        worldrenderer.begin(7, DefaultVertexFormats.POSITION);
        worldrenderer.pos(left, bottom, 0.0D).endVertex();
        worldrenderer.pos(right, bottom, 0.0D).endVertex();
        worldrenderer.pos(right, top, 0.0D).endVertex();
        worldrenderer.pos(left, top, 0.0D).endVertex();
        tessellator.draw();
        GlStateManager.enableTexture2D();
        GlStateManager.disableBlend();
    }

    /**
     * Fired when a game overlay is being rendered
     *
     * @param event - RenderGameOverlayEvent
     * @see RenderGameOverlayEvent
     */
    @SubscribeEvent
    public void onRender(RenderGameOverlayEvent.Post event) {
        // Get a Minecraft Instance
        Minecraft mc = FMLClientHandler.instance().getClient();

        if (event.type.equals(RenderGameOverlayEvent.ElementType.EXPERIENCE) && Settings.SHOW_PLAYER && Settings.ENABLED) {
            if (this.first) {
                // Make sure that this is never ran again
                this.first = false;

                // Setup a ScheduledExecutorService to run every 3 seconds
                ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
                exec.scheduleAtFixedRate(() -> {
                    try {
                        // Check if we are ready
                        if (ServiceHandler.INSTANCE.getCurrentMediaHandler() != null) {
                            if (ServiceHandler.INSTANCE.getCurrentMediaHandler().handlerReady()) {
                                this.currentlyPlayingObject = ServiceHandler.INSTANCE.getCurrentMediaHandler().getCurrentTrack();
                                if(this.previousPlayingObject == null || !this.previousPlayingObject.item.name.equals(this.currentlyPlayingObject.item.name)) {
                                    this.previousPlayingObject = this.currentlyPlayingObject;
                                    MinecraftForge.EVENT_BUS.post(new SongChangeEvent(this.currentlyPlayingObject));
                                    if(Settings.ANNOUNCE_TRACKS) PlayerMessager.sendMessage(ChatColor.GRAY + "Current track: " + this.currentlyPlayingObject.item.name + " by "  + this.currentlyPlayingObject.item.album.artists[0].name, true);
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }, 0, 3, TimeUnit.SECONDS);
            }

            // Make sure that a MediaHandler exists and is ready
            IMediaHandler currentHandler = ServiceHandler.INSTANCE.getCurrentMediaHandler();
            if (currentHandler != null && currentHandler.handlerReady() && currentlyPlayingObject != null) {
                // Make sure there's no GUI screen being displayed
                if (mc.currentScreen == null && !mc.gameSettings.showDebugInfo) {
                    this.drawPlayer(Settings.PLAYER_X, Settings.PLAYER_Y, Settings.MODERN_PLAYER_STYLE, false, Settings.PLAYER_ZOOM);
                }
            }
        }
    }

    /**
     * Renders the media player on the screen
     *
     * @param x        - the x coordinate of the top left corner
     * @param y        - the y coordinate of the top right corner
     * @param isModern - if the player should be rendered as the modern style
     * @param testing  - if it is a testing player i.e. in the settings menu
     * @param scale    - the scale to use
     */
    void drawPlayer(double x, double y, boolean isModern, boolean testing, double scale) {
        float cornerX = -75.5f;
        float cornerY = -25.5f;

        // Get a Minecraft Instance
        Minecraft mc = FMLClientHandler.instance().getClient();

        mc.mcProfiler.startSection("mediamod_player");

        // Establish a FontRenderer
        FontRenderer fontRenderer = mc.fontRendererObj;

        // Track Metadata
        Track track = null;
        if (this.currentlyPlayingObject != null) {
            track = this.currentlyPlayingObject.item;
        }

        // Track Name
        String trackName = I18n.format("player.text.song_name");
        // Track Artist
        String trackArtist = I18n.format("player.text.artist_name");
        // URL of album art
        URL url = null;
        // Color of the album art
        Color color = Color.gray;

        if (!testing && track != null) {
            // Get the track metadata
            trackName = track.name;
            if (track.album != null) {
                if (track.album.artists != null && track.album.artists.length > 0) {
                    trackArtist = track.album.artists[0].name;
                }
                if (track.album.images != null && track.album.images.length > 0) {
                    try {
                        url = new URL(track.album.images[0].url);
                    } catch (MalformedURLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        GlStateManager.pushMatrix();
        GlStateManager.translate(x - cornerX, y - cornerY, 0);
        GlStateManager.scale(scale, scale, scale);

        // Set the X Position for the text to be rendered at
        float textXPosition = cornerX + 10;
        if (Settings.SHOW_ALBUM_ART && (testing || url != null)) {
            // If the album art is being rendered we must move the text to the right
            textXPosition = cornerX + 50;
        }

        if (isModern) {
            // Draw the outline of the player
            drawRect(cornerX + 151, cornerY + 4, cornerX + 4, cornerY + 51, new Color(0, 0, 0, 75).getRGB());
        }

        // Background
        if (Settings.AUTO_COLOR_SELECTION && Settings.SHOW_ALBUM_ART && !testing) {
            if (url != null) {
                BufferedImage image = DynamicTextureWrapper.getImage(url);

                color = averageColor(image, image.getWidth(), image.getHeight());

                if (color.equals(Color.black)) {
                    color = Color.gray;
                }
            }

            // Draw the background of the player
            drawRect(cornerX + 150, cornerY + 5, cornerX + 5, cornerY + 50, color.darker().getRGB());
        } else {
            // Draw the background of the player
            drawRect(cornerX + 150, cornerY + 5, cornerX + 5, cornerY + 50, Color.darkGray.getRGB());
        }

        // Draw the metadata of the track (title, artist, album art)
        if (!(Settings.SHOW_ALBUM_ART && (testing || url != null))) {
            if (trackName.length() >= 28) {
                String concatName = trackName + "    " + trackName;
                AtomicInteger concatNameCount2 = new AtomicInteger(concatNameCount + 26);

                if ((concatNameCount + 26) >= concatName.length()) {
                    concatNameCount = 0;
                    concatNameCount2.set(concatNameCount + 26);
                }

                if (firstRun) {
                    // Set the firstRun variable to false
                    firstRun = false;

                    ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
                    exec.scheduleAtFixedRate(() -> {
                        concatNameCount++;
                        concatNameCount2.set(concatNameCount + 26);
                    }, 0, 500, TimeUnit.MILLISECONDS);
                }

                // String concatenation for tracks
                fontRenderer.drawString(concatName.substring(concatNameCount, concatNameCount2.get()), textXPosition, cornerY + 11, -1, false);
            } else {
                // Draw the track name normally
                fontRenderer.drawString(trackName, textXPosition, cornerY + 11, -1, false);
            }
        } else {
            if (trackName.length() >= 19) {
                String concatName = trackName + "    " + trackName;
                AtomicInteger concatNameCount2 = new AtomicInteger(concatNameCount + 17);

                if ((concatNameCount + 16) >= concatName.length()) {
                    concatNameCount = 0;
                    concatNameCount2.set(concatNameCount + 17);
                }

                if (firstRun) {
                    // Set the firstRun variable to false
                    firstRun = false;

                    ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
                    exec.scheduleAtFixedRate(() -> {
                        concatNameCount++;
                        concatNameCount2.set(concatNameCount + 16);
                    }, 0, 500, TimeUnit.MILLISECONDS);
                }

                // String concatenation for tracks
                fontRenderer.drawString(concatName.substring(concatNameCount, concatNameCount2.get()), textXPosition, cornerY + 11, -1, false);
            } else {
                // Draw the track name normally
                fontRenderer.drawString(trackName, textXPosition, cornerY + 11, -1, false);
            }
        }

        String by = I18n.format("player.text.by") + " ";

        int max = Settings.SHOW_ALBUM_ART && (testing || (currentlyPlayingObject != null && currentlyPlayingObject.item != null && currentlyPlayingObject.item.album != null && currentlyPlayingObject.item.album.images.length > 0)) ? 18 : 30;

        if (trackArtist != null) {
            if ((by + trackArtist).length() >= max) {
                String concatName = (by + trackArtist) + "    " + (by + trackArtist);
                AtomicInteger concatArtistCount2 = new AtomicInteger(concatArtistCount + max - 1);

                if ((concatArtistCount + max - 2) >= concatName.length()) {
                    concatArtistCount = 0;
                    concatArtistCount2.set(concatArtistCount + max - 1);
                }

                if (artistFirstRun) {
                    // Set the artistFirstRun variable to false
                    artistFirstRun = false;

                    ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
                    exec.scheduleAtFixedRate(() -> {
                        concatArtistCount++;
                        concatArtistCount2.set(concatArtistCount + max - 2);
                    }, 0, 500, TimeUnit.MILLISECONDS);
                }

                // String concatenation for tracks
                fontRenderer.drawString(concatName.substring(concatArtistCount, concatArtistCount2.get()), textXPosition, cornerY + 20, Color.white.darker().getRGB(), false);
            } else {
                fontRenderer.drawString(by + trackArtist, textXPosition, cornerY + 20, Color.white.darker().getRGB(), false);
            }
        }

        if (testing || (currentlyPlayingObject != null && currentlyPlayingObject.item != null)) {
            if (testing || (currentlyPlayingObject.item.duration_ms > 0 && currentlyPlayingObject.progress_ms >= 0)) {
                float right = textXPosition + 91;
                int offset = 91;
                int progressMultiplier = 90;
                if (!(Settings.SHOW_ALBUM_ART && (testing || (currentlyPlayingObject.item.album != null && currentlyPlayingObject.item.album.images.length > 0)))) {
                    right = textXPosition + 135;
                    offset = 135;
                    progressMultiplier = 135;
                }
                Color displayColor = Settings.AUTO_COLOR_SELECTION && Settings.SHOW_ALBUM_ART ? color : Color.green;

                if (Settings.PROGRESS_STYLE != ProgressStyle.NUMBERS_ONLY) {
                    float progressTop = cornerY + 30;
                    float progressBottom = Settings.PROGRESS_STYLE == ProgressStyle.BAR_AND_NUMBERS_OLD ? cornerY + 39 : cornerY + 43;
                    // Draw the progress bar
                    if (Settings.MODERN_PLAYER_STYLE) {
                        // Draw outline
                        drawRect(textXPosition - 1, progressTop, right, progressBottom, new Color(0, 0, 0, 75).getRGB());
                    }

                    // Draw background
                    drawRect(textXPosition, progressTop + 1, right - 1, progressBottom - 1, Color.darkGray.darker().getRGB());

                    // Get the percent complete
                    float percentComplete = (float) 0.75;
                    if (track != null && ServiceHandler.INSTANCE.getCurrentMediaHandler() != null && !testing) {
                        percentComplete = (float) ServiceHandler.INSTANCE.getCurrentMediaHandler().getEstimatedProgressMs() / (float) track.duration_ms;
                    }
                    if (Settings.MODERN_PLAYER_STYLE) {
                        // Draw the gradient styled progress bar
                        drawGradientRect(textXPosition, progressTop + 1, (textXPosition + (progressMultiplier * percentComplete)), progressBottom - 1, displayColor.getRGB(), displayColor.darker().getRGB());
                    } else {
                        // Draw the normal progress bar
                        drawRect(textXPosition, progressTop + 1, (textXPosition + (progressMultiplier * percentComplete)), progressBottom - 1, displayColor.getRGB());
                    }
                }

                if (Settings.PROGRESS_STYLE != ProgressStyle.BAR_ONLY) {
                    int progressMs = track == null || ServiceHandler.INSTANCE.getCurrentMediaHandler() == null ? 45000 : ServiceHandler.INSTANCE.getCurrentMediaHandler().getEstimatedProgressMs();
                    int durationMs = track == null ? 60000 : track.duration_ms;
                    int color2 = Settings.PROGRESS_STYLE == ProgressStyle.BAR_AND_NUMBERS_NEW ? getComplementaryColor(displayColor) : Color.white.darker().getRGB();
                    float y2 = Settings.PROGRESS_STYLE == ProgressStyle.BAR_AND_NUMBERS_OLD ? cornerY + 41 : cornerY + 33;
                    if (Settings.PROGRESS_STYLE != ProgressStyle.NUMBERS_ONLY) {
                        String str = formatTime(durationMs);
                        fontRenderer.drawString(formatTime(progressMs), textXPosition + 1, y2, color2, false);
                        fontRenderer.drawString(str, right - (fontRenderer.getStringWidth(str) + 2), y2, color2, false);
                    } else {
                        String str = formatTime(progressMs) + " / " + formatTime(durationMs);
                        fontRenderer.drawString(str, textXPosition + (offset / 2.f) - (fontRenderer.getStringWidth(str) / 2.f), y2, color2, false);
                    }
                }
            }


            // Draw the album art
            if (Settings.SHOW_ALBUM_ART && (testing || (currentlyPlayingObject.item.album != null && currentlyPlayingObject.item.album.images.length > 0))) {
                if (Settings.MODERN_PLAYER_STYLE) {
                    // Draw outline
                    drawRect(cornerX + 46, cornerY + 9, cornerX + 9, cornerY + 46, new Color(0, 0, 0, 75).getRGB());
                }

                // Setup OpenGL
                GlStateManager.pushMatrix();
                GlStateManager.color(1, 1, 1, 1);

                // Bind the texture for rendering
                if (testing) {
                    // Since it's a testing player we bind the MediaMod Logo
                    mc.getTextureManager().bindTexture(IMediaGui.iconResource);
                } else {
                    if (url != null) {
                        mc.getTextureManager().bindTexture(DynamicTextureWrapper.getTexture(url));
                    }
                }

                // Render the album art as 35x35
                drawModalRectWithCustomSizedTexture(cornerX + 10, cornerY + 10, 0, 0, 35, 35, 35, 35);
                GlStateManager.popMatrix();
            }
        }

        GlStateManager.popMatrix();
        mc.mcProfiler.endSection();
    }

    /**
     * Draws a rectangle with a vertical gradient between the specified colors (ARGB format).
     * Args: x1, y1, x2, y2, topColor, bottomColor
     *
     * @author ScottehBoeh
     */
    private void drawGradientRect(double left, double top, double right, double bottom, int startColor, int endColor) {
        float f = (float) (startColor >> 24 & 255) / 255.0F;
        float f1 = (float) (startColor >> 16 & 255) / 255.0F;
        float f2 = (float) (startColor >> 8 & 255) / 255.0F;
        float f3 = (float) (startColor & 255) / 255.0F;
        float f4 = (float) (endColor >> 24 & 255) / 255.0F;
        float f5 = (float) (endColor >> 16 & 255) / 255.0F;
        float f6 = (float) (endColor >> 8 & 255) / 255.0F;
        float f7 = (float) (endColor & 255) / 255.0F;
        GlStateManager.disableTexture2D();
        GlStateManager.enableBlend();
        GlStateManager.disableAlpha();
        GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
        GlStateManager.shadeModel(7425);
        Tessellator tessellator = Tessellator.getInstance();
        WorldRenderer worldrenderer = tessellator.getWorldRenderer();
        worldrenderer.begin(7, DefaultVertexFormats.POSITION_COLOR);
        worldrenderer.pos(right, top, 0).color(f1, f2, f3, f).endVertex();
        worldrenderer.pos(left, top, 0).color(f1, f2, f3, f).endVertex();
        worldrenderer.pos(left, bottom, 0).color(f5, f6, f7, f4).endVertex();
        worldrenderer.pos(right, bottom, 0).color(f5, f6, f7, f4).endVertex();
        tessellator.draw();
        GlStateManager.shadeModel(7424);
        GlStateManager.disableBlend();
        GlStateManager.enableAlpha();
        GlStateManager.enableTexture2D();
    }
}