/*
 * This file is part of MapReflectionAPI.
 * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package tech.sbdevelopment.mapreflectionapi.api;

import lombok.Data;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;

import java.util.ArrayList;
import java.util.List;

import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;

/**
 * The {@link MapSender} sends the Map packets to players.
 */
public class MapSender {
    private static final List<QueuedMap> sendQueue = new ArrayList<>();
    private static int senderID = -1;

    private MapSender() {
    }

    /**
     * Add a map to the send queue
     *
     * @param id      The ID of the map
     * @param content The {@link ArrayImage} to view on the map
     * @param player  The {@link Player} to view for
     */
    public static void addToQueue(final int id, final ArrayImage content, final Player player) {
        QueuedMap toSend = new QueuedMap(id, content, player);
        if (sendQueue.contains(toSend)) return;
        sendQueue.add(toSend);

        runSender();
    }

    /**
     * Cancels a senderID in the sender queue
     *
     * @param s The senderID to cancel
     */
    public static void cancelID(int s) {
        sendQueue.removeIf(queuedMap -> queuedMap.id == s);
    }

    /**
     * Run the sender task
     */
    private static void runSender() {
        if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty())
            return;

        senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> {
            if (sendQueue.isEmpty()) return;

            for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) {
                QueuedMap current = sendQueue.get(0);
                if (current == null) return;

                sendMap(current.id, current.image, current.player);

                if (!sendQueue.isEmpty()) sendQueue.remove(0);
            }
        }, 0, 2);
    }

    private static final Class<?> packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap");
    private static final Class<?> worldMapData = supports(17) ? getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null;

    /**
     * Send a map to a player
     *
     * @param id0     The ID of the map
     * @param content The {@link ArrayImage} to view on the map
     * @param player  The {@link Player} to view for
     */
    public static void sendMap(final int id0, final ArrayImage content, final Player player) {
        if (player == null || !player.isOnline()) {
            List<QueuedMap> toRemove = new ArrayList<>();
            for (QueuedMap qMap : sendQueue) {
                if (qMap == null) continue;

                if (qMap.player == null || !qMap.player.isOnline()) {
                    toRemove.add(qMap);
                }
            }
            Bukkit.getScheduler().cancelTask(senderID);
            sendQueue.removeAll(toRemove);

            return;
        }

        final int id = -id0;
        Object packet;
        if (supports(17)) { //1.17+
            Object updateData = ReflectionUtil.callConstructor(worldMapData,
                    content.minX, //X pos
                    content.minY, //Y pos
                    content.maxX, //X size (2nd X pos)
                    content.maxY, //Y size (2nd Y pos)
                    content.array //Data
            );

            packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
                    id, //ID
                    (byte) 0, //Scale, 0 = 1 block per pixel
                    false, //Show icons
                    new ReflectionUtil.CollectionParam<>(), //Icons
                    updateData
            );
        } else if (supports(14)) { //1.16-1.14
            packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
                    id, //ID
                    (byte) 0, //Scale, 0 = 1 block per pixel
                    false, //Tracking position
                    false, //Locked
                    new ReflectionUtil.CollectionParam<>(), //Icons
                    content.array, //Data
                    content.minX, //X pos
                    content.minY, //Y pos
                    content.maxX, //X size (2nd X pos)
                    content.maxY //Y size (2nd Y pos)
            );
        } else { //1.13-
            packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
                    id, //ID
                    (byte) 0, //Scale, 0 = 1 block per pixel
                    false, //???
                    new ReflectionUtil.CollectionParam<>(), //Icons
                    content.array, //Data
                    content.minX, //X pos
                    content.minY, //Y pos
                    content.maxX, //X size (2nd X pos)
                    content.maxY //Y size (2nd Y pos)
            );
        }

        sendPacket(player, packet);
    }

    @Data
    static final class QueuedMap {
        private final int id;
        private final ArrayImage image;
        private final Player player;
    }
}