package net.glowstone.map;

import net.glowstone.GlowServer;
import net.glowstone.ServerProvider;
import net.glowstone.block.GlowBlock;
import net.glowstone.block.MaterialValueManager.ValueCollection;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.map.MapCanvas;
import org.bukkit.map.MapRenderer;
import org.bukkit.map.MapView;

import static net.glowstone.map.GlowMapCanvas.MAP_SIZE;

/**
 * Glowstone's built-in map renderer.
 */
public final class GlowMapRenderer extends MapRenderer {

    private static final int MAP_SIGHT_DISTANCE_SQUARED = 64 * 64;
    private final GlowMapView map;

    public GlowMapRenderer(GlowMapView map) {
        super(false);
        this.map = map;
    }

    /**
     * Based on https://minecraft.gamepedia.com/Slime#.22Slime_chunks.22 but simplified (doesn't
     * instantiate a Random, and doesn't vary with the world seed. Designed to be reproducible, so
     * that updating a map doesn't change the color unless the map contents have changed.
     */
    private static byte pseudoRandomShade(int worldX, int worldZ) {
        return (byte) ((
            ((worldX * worldX * 0x4c1906) + (worldX * 0x5ac0db) + ((worldZ * worldZ) * 0x4307a7)
                + (worldZ * 0x5f24f))) % 4);
    }

    private static byte colorFor(Block block, int worldX, int worldZ) {
        // TODO: Some blocks vary in map color based on block states (e.g. wood species)
        ValueCollection materialValues;
        materialValues = block instanceof GlowBlock ? ((GlowBlock) block).getMaterialValues()
            : ((GlowServer) ServerProvider.getServer()).getMaterialValueManager()
            .getValues(block.getType());
        byte baseColor = materialValues.getBaseMapColor();
        return (byte) (baseColor | pseudoRandomShade(worldX, worldZ));
    }

    @Override
    public void render(MapView map, MapCanvas canvas, Player player) {
        World world = map.getWorld();
        int scaleShift = map.getScale().getValue();
        Location playerLoc = player.getLocation();
        int playerX = playerLoc.getBlockX();
        int playerZ = playerLoc.getBlockZ();
        int cornerX = map.getCenterX() - ((MAP_SIZE / 2) << scaleShift);
        int cornerZ = map.getCenterZ() - ((MAP_SIZE / 2) << scaleShift);
        for (int pixelX = 0; pixelX < MAP_SIZE; pixelX++) {
            for (int pixelY = 0; pixelY < MAP_SIZE; pixelY++) {
                int worldX = cornerX + (pixelX << scaleShift);
                int worldZ = cornerZ + (pixelY << scaleShift);
                if (((worldX - playerX) * (worldX - playerX)
                    + (worldZ - playerZ) * (worldZ - playerZ)) < MAP_SIGHT_DISTANCE_SQUARED) {
                    // TODO: Should the highest block be skipped over if it's e.g. a flower or a
                    // technical block?
                    byte blockColor =
                        colorFor(world.getHighestBlockAt(worldX, worldZ), worldX, worldZ);
                    canvas.setPixel(pixelX, pixelY, blockColor);
                }
            }
        }
    }
}
