package net.minecraft.client.renderer;

import org.apache.logging.log4j.LogManager;
import javax.annotation.Nullable;
import net.minecraft.client.renderer.texture.AbstractTexture;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import java.io.IOException;
import java.util.Iterator;
import net.minecraft.server.packs.resources.Resource;
import java.io.Closeable;
import org.apache.commons.io.IOUtils;
import com.mojang.blaze3d.shaders.ProgramManager;
import com.google.gson.JsonObject;
import net.minecraft.server.ChainedJsonException;
import com.google.gson.JsonElement;
import com.google.gson.JsonArray;
import java.io.Reader;
import net.minecraft.util.GsonHelper;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import net.minecraft.resources.ResourceLocation;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.minecraft.server.packs.resources.ResourceManager;
import com.mojang.blaze3d.shaders.Program;
import com.mojang.blaze3d.shaders.BlendMode;
import com.mojang.blaze3d.shaders.Uniform;
import java.util.List;
import java.util.Map;
import com.mojang.blaze3d.shaders.AbstractUniform;
import org.apache.logging.log4j.Logger;
import com.mojang.blaze3d.shaders.Effect;

public class EffectInstance implements Effect, AutoCloseable {
    private static final Logger LOGGER;
    private static final AbstractUniform DUMMY_UNIFORM;
    private static EffectInstance lastAppliedEffect;
    private static int lastProgramId;
    private final Map<String, Object> samplerMap;
    private final List<String> samplerNames;
    private final List<Integer> samplerLocations;
    private final List<Uniform> uniforms;
    private final List<Integer> uniformLocations;
    private final Map<String, Uniform> uniformMap;
    private final int programId;
    private final String name;
    private final boolean cull;
    private boolean dirty;
    private final BlendMode blend;
    private final List<Integer> attributes;
    private final List<String> attributeNames;
    private final Program vertexProgram;
    private final Program fragmentProgram;
    
    public EffectInstance(final ResourceManager zb, final String string) throws IOException {
        this.samplerMap = Maps.newHashMap();
        this.samplerNames = Lists.newArrayList();
        this.samplerLocations = Lists.newArrayList();
        this.uniforms = Lists.newArrayList();
        this.uniformLocations = Lists.newArrayList();
        this.uniformMap = Maps.newHashMap();
        final ResourceLocation sm4 = new ResourceLocation("shaders/program/" + string + ".json");
        this.name = string;
        Resource za5 = null;
        try {
            za5 = zb.getResource(sm4);
            final JsonObject jsonObject6 = GsonHelper.parse(new InputStreamReader(za5.getInputStream(), StandardCharsets.UTF_8));
            final String string2 = GsonHelper.getAsString(jsonObject6, "vertex");
            final String string3 = GsonHelper.getAsString(jsonObject6, "fragment");
            final JsonArray jsonArray9 = GsonHelper.getAsJsonArray(jsonObject6, "samplers", null);
            if (jsonArray9 != null) {
                int integer10 = 0;
                for (final JsonElement jsonElement12 : jsonArray9) {
                    try {
                        this.parseSamplerNode(jsonElement12);
                    }
                    catch (Exception exception13) {
                        final ChainedJsonException sp14 = ChainedJsonException.forException(exception13);
                        sp14.prependJsonKey("samplers[" + integer10 + "]");
                        throw sp14;
                    }
                    ++integer10;
                }
            }
            final JsonArray jsonArray10 = GsonHelper.getAsJsonArray(jsonObject6, "attributes", null);
            if (jsonArray10 != null) {
                int integer11 = 0;
                this.attributes = Lists.newArrayListWithCapacity(jsonArray10.size());
                this.attributeNames = Lists.newArrayListWithCapacity(jsonArray10.size());
                for (final JsonElement jsonElement13 : jsonArray10) {
                    try {
                        this.attributeNames.add(GsonHelper.convertToString(jsonElement13, "attribute"));
                    }
                    catch (Exception exception14) {
                        final ChainedJsonException sp15 = ChainedJsonException.forException(exception14);
                        sp15.prependJsonKey("attributes[" + integer11 + "]");
                        throw sp15;
                    }
                    ++integer11;
                }
            }
            else {
                this.attributes = null;
                this.attributeNames = null;
            }
            final JsonArray jsonArray11 = GsonHelper.getAsJsonArray(jsonObject6, "uniforms", null);
            if (jsonArray11 != null) {
                int integer12 = 0;
                for (final JsonElement jsonElement14 : jsonArray11) {
                    try {
                        this.parseUniformNode(jsonElement14);
                    }
                    catch (Exception exception15) {
                        final ChainedJsonException sp16 = ChainedJsonException.forException(exception15);
                        sp16.prependJsonKey("uniforms[" + integer12 + "]");
                        throw sp16;
                    }
                    ++integer12;
                }
            }
            this.blend = parseBlendNode(GsonHelper.getAsJsonObject(jsonObject6, "blend", null));
            this.cull = GsonHelper.getAsBoolean(jsonObject6, "cull", true);
            this.vertexProgram = getOrCreate(zb, Program.Type.VERTEX, string2);
            this.fragmentProgram = getOrCreate(zb, Program.Type.FRAGMENT, string3);
            this.programId = ProgramManager.createProgram();
            ProgramManager.linkProgram(this);
            this.updateLocations();
            if (this.attributeNames != null) {
                for (final String string4 : this.attributeNames) {
                    final int integer13 = Uniform.glGetAttribLocation(this.programId, string4);
                    this.attributes.add(integer13);
                }
            }
        }
        catch (Exception exception16) {
            final ChainedJsonException sp17 = ChainedJsonException.forException(exception16);
            sp17.setFilenameAndFlush(sm4.getPath());
            throw sp17;
        }
        finally {
            IOUtils.closeQuietly((Closeable)za5);
        }
        this.markDirty();
    }
    
    public static Program getOrCreate(final ResourceManager zb, final Program.Type a, final String string) throws IOException {
        Program cyc4 = a.getPrograms().get(string);
        if (cyc4 == null) {
            final ResourceLocation sm5 = new ResourceLocation("shaders/program/" + string + a.getExtension());
            final Resource za6 = zb.getResource(sm5);
            try {
                cyc4 = Program.compileShader(a, string, za6.getInputStream());
            }
            finally {
                IOUtils.closeQuietly((Closeable)za6);
            }
        }
        return cyc4;
    }
    
    public static BlendMode parseBlendNode(final JsonObject jsonObject) {
        if (jsonObject == null) {
            return new BlendMode();
        }
        int integer2 = 32774;
        int integer3 = 1;
        int integer4 = 0;
        int integer5 = 1;
        int integer6 = 0;
        boolean boolean7 = true;
        boolean boolean8 = false;
        if (GsonHelper.isStringValue(jsonObject, "func")) {
            integer2 = BlendMode.stringToBlendFunc(jsonObject.get("func").getAsString());
            if (integer2 != 32774) {
                boolean7 = false;
            }
        }
        if (GsonHelper.isStringValue(jsonObject, "srcrgb")) {
            integer3 = BlendMode.stringToBlendFactor(jsonObject.get("srcrgb").getAsString());
            if (integer3 != 1) {
                boolean7 = false;
            }
        }
        if (GsonHelper.isStringValue(jsonObject, "dstrgb")) {
            integer4 = BlendMode.stringToBlendFactor(jsonObject.get("dstrgb").getAsString());
            if (integer4 != 0) {
                boolean7 = false;
            }
        }
        if (GsonHelper.isStringValue(jsonObject, "srcalpha")) {
            integer5 = BlendMode.stringToBlendFactor(jsonObject.get("srcalpha").getAsString());
            if (integer5 != 1) {
                boolean7 = false;
            }
            boolean8 = true;
        }
        if (GsonHelper.isStringValue(jsonObject, "dstalpha")) {
            integer6 = BlendMode.stringToBlendFactor(jsonObject.get("dstalpha").getAsString());
            if (integer6 != 0) {
                boolean7 = false;
            }
            boolean8 = true;
        }
        if (boolean7) {
            return new BlendMode();
        }
        if (boolean8) {
            return new BlendMode(integer3, integer4, integer5, integer6, integer2);
        }
        return new BlendMode(integer3, integer4, integer2);
    }
    
    @Override
    public void close() {
        for (final Uniform cye3 : this.uniforms) {
            cye3.close();
        }
        ProgramManager.releaseProgram(this);
    }
    
    public void clear() {
        RenderSystem.assertThread(RenderSystem::isOnRenderThread);
        ProgramManager.glUseProgram(0);
        EffectInstance.lastProgramId = -1;
        EffectInstance.lastAppliedEffect = null;
        for (int integer2 = 0; integer2 < this.samplerLocations.size(); ++integer2) {
            if (this.samplerMap.get(this.samplerNames.get(integer2)) != null) {
                GlStateManager._activeTexture(33984 + integer2);
                GlStateManager._bindTexture(0);
            }
        }
    }
    
    public void apply() {
        RenderSystem.assertThread(RenderSystem::isOnGameThread);
        this.dirty = false;
        EffectInstance.lastAppliedEffect = this;
        this.blend.apply();
        if (this.programId != EffectInstance.lastProgramId) {
            ProgramManager.glUseProgram(this.programId);
            EffectInstance.lastProgramId = this.programId;
        }
        if (this.cull) {
            RenderSystem.enableCull();
        }
        else {
            RenderSystem.disableCull();
        }
        for (int integer2 = 0; integer2 < this.samplerLocations.size(); ++integer2) {
            if (this.samplerMap.get(this.samplerNames.get(integer2)) != null) {
                RenderSystem.activeTexture(33984 + integer2);
                RenderSystem.enableTexture();
                final Object object3 = this.samplerMap.get(this.samplerNames.get(integer2));
                int integer3 = -1;
                if (object3 instanceof RenderTarget) {
                    integer3 = ((RenderTarget)object3).colorTextureId;
                }
                else if (object3 instanceof AbstractTexture) {
                    integer3 = ((AbstractTexture)object3).getId();
                }
                else if (object3 instanceof Integer) {
                    integer3 = (int)object3;
                }
                if (integer3 != -1) {
                    RenderSystem.bindTexture(integer3);
                    Uniform.uploadInteger(Uniform.glGetUniformLocation(this.programId, this.samplerNames.get(integer2)), integer2);
                }
            }
        }
        for (final Uniform cye3 : this.uniforms) {
            cye3.upload();
        }
    }
    
    @Override
    public void markDirty() {
        this.dirty = true;
    }
    
    @Nullable
    public Uniform getUniform(final String string) {
        RenderSystem.assertThread(RenderSystem::isOnRenderThread);
        return this.uniformMap.get(string);
    }
    
    public AbstractUniform safeGetUniform(final String string) {
        RenderSystem.assertThread(RenderSystem::isOnGameThread);
        final Uniform cye3 = this.getUniform(string);
        return (cye3 == null) ? EffectInstance.DUMMY_UNIFORM : cye3;
    }
    
    private void updateLocations() {
        RenderSystem.assertThread(RenderSystem::isOnRenderThread);
        for (int integer2 = 0, integer3 = 0; integer2 < this.samplerNames.size(); ++integer2, ++integer3) {
            final String string4 = this.samplerNames.get(integer2);
            final int integer4 = Uniform.glGetUniformLocation(this.programId, string4);
            if (integer4 == -1) {
                EffectInstance.LOGGER.warn("Shader {}could not find sampler named {} in the specified shader program.", this.name, string4);
                this.samplerMap.remove(string4);
                this.samplerNames.remove(integer3);
                --integer3;
            }
            else {
                this.samplerLocations.add(integer4);
            }
        }
        for (final Uniform cye3 : this.uniforms) {
            final String string4 = cye3.getName();
            final int integer4 = Uniform.glGetUniformLocation(this.programId, string4);
            if (integer4 == -1) {
                EffectInstance.LOGGER.warn("Could not find uniform named {} in the specified shader program.", string4);
            }
            else {
                this.uniformLocations.add(integer4);
                cye3.setLocation(integer4);
                this.uniformMap.put(string4, cye3);
            }
        }
    }
    
    private void parseSamplerNode(final JsonElement jsonElement) {
        final JsonObject jsonObject3 = GsonHelper.convertToJsonObject(jsonElement, "sampler");
        final String string4 = GsonHelper.getAsString(jsonObject3, "name");
        if (!GsonHelper.isStringValue(jsonObject3, "file")) {
            this.samplerMap.put(string4, null);
            this.samplerNames.add(string4);
            return;
        }
        this.samplerNames.add(string4);
    }
    
    public void setSampler(final String string, final Object object) {
        if (this.samplerMap.containsKey(string)) {
            this.samplerMap.remove(string);
        }
        this.samplerMap.put(string, object);
        this.markDirty();
    }
    
    private void parseUniformNode(final JsonElement jsonElement) throws ChainedJsonException {
        final JsonObject jsonObject3 = GsonHelper.convertToJsonObject(jsonElement, "uniform");
        final String string4 = GsonHelper.getAsString(jsonObject3, "name");
        final int integer5 = Uniform.getTypeFromString(GsonHelper.getAsString(jsonObject3, "type"));
        final int integer6 = GsonHelper.getAsInt(jsonObject3, "count");
        final float[] arr7 = new float[Math.max(integer6, 16)];
        final JsonArray jsonArray8 = GsonHelper.getAsJsonArray(jsonObject3, "values");
        if (jsonArray8.size() != integer6 && jsonArray8.size() > 1) {
            throw new ChainedJsonException("Invalid amount of values specified (expected " + integer6 + ", found " + jsonArray8.size() + ")");
        }
        int integer7 = 0;
        for (final JsonElement jsonElement2 : jsonArray8) {
            try {
                arr7[integer7] = GsonHelper.convertToFloat(jsonElement2, "value");
            }
            catch (Exception exception12) {
                final ChainedJsonException sp13 = ChainedJsonException.forException(exception12);
                sp13.prependJsonKey("values[" + integer7 + "]");
                throw sp13;
            }
            ++integer7;
        }
        if (integer6 > 1 && jsonArray8.size() == 1) {
            while (integer7 < integer6) {
                arr7[integer7] = arr7[0];
                ++integer7;
            }
        }
        final int integer8 = (integer6 > 1 && integer6 <= 4 && integer5 < 8) ? (integer6 - 1) : 0;
        final Uniform cye11 = new Uniform(string4, integer5 + integer8, integer6, this);
        if (integer5 <= 3) {
            cye11.setSafe((int)arr7[0], (int)arr7[1], (int)arr7[2], (int)arr7[3]);
        }
        else if (integer5 <= 7) {
            cye11.setSafe(arr7[0], arr7[1], arr7[2], arr7[3]);
        }
        else {
            cye11.set(arr7);
        }
        this.uniforms.add(cye11);
    }
    
    @Override
    public Program getVertexProgram() {
        return this.vertexProgram;
    }
    
    @Override
    public Program getFragmentProgram() {
        return this.fragmentProgram;
    }
    
    @Override
    public int getId() {
        return this.programId;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        DUMMY_UNIFORM = new AbstractUniform();
        EffectInstance.lastProgramId = -1;
    }
}
