// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.builtin.buildertools;

import javax.annotation.Nullable;
import com.hypixel.hytale.protocol.Opacity;
import com.hypixel.hytale.protocol.DrawType;
import javax.annotation.Nonnull;
import com.hypixel.hytale.protocol.Color;
import java.util.Iterator;
import java.util.Set;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import java.util.logging.Level;
import java.util.Comparator;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import java.util.ArrayList;
import java.util.List;
import com.hypixel.hytale.logger.HytaleLogger;

public final class BlockColorIndex
{
    private static final HytaleLogger LOGGER;
    private final List<BlockColorEntry> entries;
    private boolean initialized;
    
    public BlockColorIndex() {
        this.entries = new ArrayList<BlockColorEntry>();
        this.initialized = false;
    }
    
    private void ensureInitialized() {
        if (this.initialized) {
            return;
        }
        final BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
        final Set<String> keys = assetMap.getAssetMap().keySet();
        for (final String key : keys) {
            final BlockType blockType = assetMap.getAsset(key);
            if (blockType == null) {
                continue;
            }
            if (!this.isSolidCube(blockType)) {
                continue;
            }
            final Color particleColor = blockType.getParticleColor();
            if (particleColor == null) {
                continue;
            }
            final int blockId = assetMap.getIndex(key);
            final int r = particleColor.red & 0xFF;
            final int g = particleColor.green & 0xFF;
            final int b = particleColor.blue & 0xFF;
            final double[] lab = rgbToLab(r, g, b);
            this.entries.add(new BlockColorEntry(blockId, key, r, g, b, lab[0], lab[1], lab[2]));
        }
        this.entries.sort(Comparator.comparingDouble(e -> e.labL));
        this.initialized = true;
        BlockColorIndex.LOGGER.at(Level.INFO).log("BlockColorIndex initialized with %d solid cube blocks", this.entries.size());
    }
    
    private boolean isSolidCube(@Nonnull final BlockType blockType) {
        final DrawType drawType = blockType.getDrawType();
        final Opacity opacity = blockType.getOpacity();
        return drawType == DrawType.Cube && opacity == Opacity.Solid;
    }
    
    public int findClosestBlock(final int r, final int g, final int b) {
        this.ensureInitialized();
        if (this.entries.isEmpty()) {
            return -1;
        }
        final double[] lab = rgbToLab(r, g, b);
        final double targetL = lab[0];
        final double targetA = lab[1];
        final double targetB = lab[2];
        double minDist = Double.MAX_VALUE;
        int bestId = -1;
        for (final BlockColorEntry entry : this.entries) {
            final double dist = colorDistanceLab(targetL, targetA, targetB, entry.labL, entry.labA, entry.labB);
            if (dist < minDist) {
                minDist = dist;
                bestId = entry.blockId;
            }
        }
        return bestId;
    }
    
    public int findDarkerVariant(final int blockId, final float darkenAmount) {
        this.ensureInitialized();
        final BlockColorEntry source = this.findEntry(blockId);
        if (source == null) {
            return blockId;
        }
        final double targetL = source.labL * (1.0 - darkenAmount);
        final double targetA = source.labA;
        final double targetB = source.labB;
        double minDist = Double.MAX_VALUE;
        int bestId = blockId;
        for (final BlockColorEntry entry : this.entries) {
            if (entry.labL > source.labL) {
                continue;
            }
            final double dist = colorDistanceLab(targetL, targetA, targetB, entry.labL, entry.labA, entry.labB);
            if (dist >= minDist) {
                continue;
            }
            minDist = dist;
            bestId = entry.blockId;
        }
        return bestId;
    }
    
    public int getBlockColor(final int blockId) {
        this.ensureInitialized();
        final BlockColorEntry entry = this.findEntry(blockId);
        if (entry == null) {
            return -1;
        }
        return entry.r << 16 | entry.g << 8 | entry.b;
    }
    
    public int findBlockForLerpedColor(final int rA, final int gA, final int bA, final int rB, final int gB, final int bB, final float t) {
        this.ensureInitialized();
        final double[] labA = rgbToLab(rA, gA, bA);
        final double[] labB = rgbToLab(rB, gB, bB);
        final double l = labA[0] + (labB[0] - labA[0]) * t;
        final double a = labA[1] + (labB[1] - labA[1]) * t;
        final double b = labA[2] + (labB[2] - labA[2]) * t;
        final int[] rgb = labToRgb(l, a, b);
        return this.findClosestBlock(rgb[0], rgb[1], rgb[2]);
    }
    
    public boolean isEmpty() {
        this.ensureInitialized();
        return this.entries.isEmpty();
    }
    
    @Nullable
    private BlockColorEntry findEntry(final int blockId) {
        for (final BlockColorEntry entry : this.entries) {
            if (entry.blockId == blockId) {
                return entry;
            }
        }
        return null;
    }
    
    private static double colorDistanceLab(final double l1, final double a1, final double b1, final double l2, final double a2, final double b2) {
        final double dL = l1 - l2;
        final double dA = a1 - a2;
        final double dB = b1 - b2;
        return dL * dL + dA * dA + dB * dB;
    }
    
    private static double[] rgbToLab(final int r, final int g, final int b) {
        double rn = r / 255.0;
        double gn = g / 255.0;
        double bn = b / 255.0;
        rn = ((rn > 0.04045) ? Math.pow((rn + 0.055) / 1.055, 2.4) : (rn / 12.92));
        gn = ((gn > 0.04045) ? Math.pow((gn + 0.055) / 1.055, 2.4) : (gn / 12.92));
        bn = ((bn > 0.04045) ? Math.pow((bn + 0.055) / 1.055, 2.4) : (bn / 12.92));
        double x = rn * 0.4124564 + gn * 0.3575761 + bn * 0.1804375;
        double y = rn * 0.2126729 + gn * 0.7151522 + bn * 0.072175;
        double z = rn * 0.0193339 + gn * 0.119192 + bn * 0.9503041;
        x /= 0.95047;
        y /= 1.0;
        z /= 1.08883;
        x = ((x > 0.008856) ? Math.cbrt(x) : (7.787 * x + 0.13793103448275862));
        y = ((y > 0.008856) ? Math.cbrt(y) : (7.787 * y + 0.13793103448275862));
        z = ((z > 0.008856) ? Math.cbrt(z) : (7.787 * z + 0.13793103448275862));
        final double labL = 116.0 * y - 16.0;
        final double labA = 500.0 * (x - y);
        final double labB = 200.0 * (y - z);
        return new double[] { labL, labA, labB };
    }
    
    private static int[] labToRgb(final double labL, final double labA, final double labB) {
        double y = (labL + 16.0) / 116.0;
        double x = labA / 500.0 + y;
        double z = y - labB / 200.0;
        final double x2 = x * x * x;
        final double y2 = y * y * y;
        final double z2 = z * z * z;
        x = ((x2 > 0.008856) ? x2 : ((x - 0.13793103448275862) / 7.787));
        y = ((y2 > 0.008856) ? y2 : ((y - 0.13793103448275862) / 7.787));
        z = ((z2 > 0.008856) ? z2 : ((z - 0.13793103448275862) / 7.787));
        x *= 0.95047;
        y *= 1.0;
        z *= 1.08883;
        double rn = x * 3.2404542 + y * -1.5371385 + z * -0.4985314;
        double gn = x * -0.969266 + y * 1.8760108 + z * 0.041556;
        double bn = x * 0.0556434 + y * -0.2040259 + z * 1.0572252;
        rn = ((rn > 0.0031308) ? (1.055 * Math.pow(rn, 0.4166666666666667) - 0.055) : (12.92 * rn));
        gn = ((gn > 0.0031308) ? (1.055 * Math.pow(gn, 0.4166666666666667) - 0.055) : (12.92 * gn));
        bn = ((bn > 0.0031308) ? (1.055 * Math.pow(bn, 0.4166666666666667) - 0.055) : (12.92 * bn));
        final int r = Math.max(0, Math.min(255, (int)Math.round(rn * 255.0)));
        final int g = Math.max(0, Math.min(255, (int)Math.round(gn * 255.0)));
        final int b = Math.max(0, Math.min(255, (int)Math.round(bn * 255.0)));
        return new int[] { r, g, b };
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    record BlockColorEntry(int blockId, String key, int r, int g, int b, double labL, double labA, double labB) {}
}
