package net.minecraft.client.renderer.block.model;

import com.google.common.collect.Maps;
import com.google.gson.JsonArray;
import com.google.gson.JsonParseException;
import com.google.gson.JsonObject;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonDeserializer;
import java.lang.reflect.Type;
import com.google.gson.GsonBuilder;
import org.apache.logging.log4j.LogManager;
import java.util.Optional;
import net.minecraft.client.renderer.texture.TextureAtlas;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import net.minecraft.core.Direction;
import net.minecraft.client.resources.model.SimpleBakedModel;
import net.minecraft.client.resources.model.BuiltInModel;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import java.util.Objects;
import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import com.mojang.datafixers.util.Pair;
import java.util.function.Function;
import java.util.Iterator;
import java.util.Set;
import com.google.common.collect.Sets;
import java.util.Collection;
import net.minecraft.client.resources.model.ModelBakery;
import java.io.StringReader;
import net.minecraft.util.GsonHelper;
import java.io.Reader;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.client.resources.model.Material;
import com.mojang.datafixers.util.Either;
import java.util.Map;
import javax.annotation.Nullable;
import java.util.List;
import com.google.common.annotations.VisibleForTesting;
import com.google.gson.Gson;
import org.apache.logging.log4j.Logger;
import net.minecraft.client.resources.model.UnbakedModel;

public class BlockModel implements UnbakedModel {
    private static final Logger LOGGER;
    private static final FaceBakery FACE_BAKERY;
    @VisibleForTesting
    static final Gson GSON;
    private final List<BlockElement> elements;
    @Nullable
    private final GuiLight guiLight;
    private final boolean hasAmbientOcclusion;
    private final ItemTransforms transforms;
    private final List<ItemOverride> overrides;
    public String name;
    @VisibleForTesting
    protected final Map<String, Either<Material, String>> textureMap;
    @Nullable
    protected BlockModel parent;
    @Nullable
    protected ResourceLocation parentLocation;
    
    public static BlockModel fromStream(final Reader reader) {
        return GsonHelper.<BlockModel>fromJson(BlockModel.GSON, reader, BlockModel.class);
    }
    
    public static BlockModel fromString(final String string) {
        return fromStream(new StringReader(string));
    }
    
    public BlockModel(@Nullable final ResourceLocation sm, final List<BlockElement> list2, final Map<String, Either<Material, String>> map, final boolean boolean4, @Nullable final GuiLight b, final ItemTransforms drz, final List<ItemOverride> list7) {
        this.name = "";
        this.elements = list2;
        this.hasAmbientOcclusion = boolean4;
        this.guiLight = b;
        this.textureMap = map;
        this.parentLocation = sm;
        this.transforms = drz;
        this.overrides = list7;
    }
    
    public List<BlockElement> getElements() {
        if (this.elements.isEmpty() && this.parent != null) {
            return this.parent.getElements();
        }
        return this.elements;
    }
    
    public boolean hasAmbientOcclusion() {
        if (this.parent != null) {
            return this.parent.hasAmbientOcclusion();
        }
        return this.hasAmbientOcclusion;
    }
    
    public GuiLight getGuiLight() {
        if (this.guiLight != null) {
            return this.guiLight;
        }
        if (this.parent != null) {
            return this.parent.getGuiLight();
        }
        return GuiLight.SIDE;
    }
    
    public List<ItemOverride> getOverrides() {
        return this.overrides;
    }
    
    private ItemOverrides getItemOverrides(final ModelBakery ebz, final BlockModel drs) {
        if (this.overrides.isEmpty()) {
            return ItemOverrides.EMPTY;
        }
        return new ItemOverrides(ebz, drs, ebz::getModel, this.overrides);
    }
    
    @Override
    public Collection<ResourceLocation> getDependencies() {
        final Set<ResourceLocation> set2 = Sets.newHashSet();
        for (final ItemOverride drw4 : this.overrides) {
            set2.add(drw4.getModel());
        }
        if (this.parentLocation != null) {
            set2.add(this.parentLocation);
        }
        return set2;
    }
    
    @Override
    public Collection<Material> getMaterials(final Function<ResourceLocation, UnbakedModel> function, final Set<Pair<String, String>> set) {
        final Set<UnbakedModel> set2 = Sets.newLinkedHashSet();
        for (BlockModel drs5 = this; drs5.parentLocation != null && drs5.parent == null; drs5 = drs5.parent) {
            set2.add(drs5);
            UnbakedModel ecf6 = function.apply(drs5.parentLocation);
            if (ecf6 == null) {
                BlockModel.LOGGER.warn("No parent '{}' while loading model '{}'", this.parentLocation, drs5);
            }
            if (set2.contains(ecf6)) {
                BlockModel.LOGGER.warn("Found 'parent' loop while loading model '{}' in chain: {} -> {}", drs5, set2.stream().map(Object::toString).collect(Collectors.joining(" -> ")), this.parentLocation);
                ecf6 = null;
            }
            if (ecf6 == null) {
                drs5.parentLocation = ModelBakery.MISSING_MODEL_LOCATION;
                ecf6 = function.apply(drs5.parentLocation);
            }
            if (!(ecf6 instanceof BlockModel)) {
                throw new IllegalStateException("BlockModel parent has to be a block model.");
            }
            drs5.parent = (BlockModel)ecf6;
        }
        final Set<Material> set3 = Sets.<Material>newHashSet(this.getMaterial("particle"));
        for (final BlockElement dro8 : this.getElements()) {
            for (final BlockElementFace drp10 : dro8.faces.values()) {
                final Material eby11 = this.getMaterial(drp10.texture);
                if (Objects.equals(eby11.texture(), MissingTextureAtlasSprite.getLocation())) {
                    set.add((Pair<String, String>)Pair.of(drp10.texture, this.name));
                }
                set3.add(eby11);
            }
        }
        final UnbakedModel ecf7;
        final Set set4;
        this.overrides.forEach(drw -> {
            ecf7 = function.apply(drw.getModel());
            if (Objects.equals(ecf7, this)) {
                return;
            }
            else {
                set4.addAll(ecf7.getMaterials(function, set));
                return;
            }
        });
        if (this.getRootModel() == ModelBakery.GENERATION_MARKER) {
            ItemModelGenerator.LAYERS.forEach(string -> set3.add(this.getMaterial(string)));
        }
        return set3;
    }
    
    @Override
    public BakedModel bake(final ModelBakery ebz, final Function<Material, TextureAtlasSprite> function, final ModelState ecc, final ResourceLocation sm) {
        return this.bake(ebz, this, function, ecc, sm, true);
    }
    
    public BakedModel bake(final ModelBakery ebz, final BlockModel drs, final Function<Material, TextureAtlasSprite> function, final ModelState ecc, final ResourceLocation sm, final boolean boolean6) {
        final TextureAtlasSprite eaj8 = function.apply(this.getMaterial("particle"));
        if (this.getRootModel() == ModelBakery.BLOCK_ENTITY_MARKER) {
            return new BuiltInModel(this.getTransforms(), this.getItemOverrides(ebz, drs), eaj8, this.getGuiLight().lightLikeBlock());
        }
        final SimpleBakedModel.Builder a9 = new SimpleBakedModel.Builder(this, this.getItemOverrides(ebz, drs), boolean6).particle(eaj8);
        for (final BlockElement dro11 : this.getElements()) {
            for (final Direction fp13 : dro11.faces.keySet()) {
                final BlockElementFace drp14 = dro11.faces.get(fp13);
                final TextureAtlasSprite eaj9 = function.apply(this.getMaterial(drp14.texture));
                if (drp14.cullForDirection == null) {
                    a9.addUnculledFace(bakeFace(dro11, drp14, eaj9, fp13, ecc, sm));
                }
                else {
                    a9.addCulledFace(Direction.rotate(ecc.getRotation().getMatrix(), drp14.cullForDirection), bakeFace(dro11, drp14, eaj9, fp13, ecc, sm));
                }
            }
        }
        return a9.build();
    }
    
    private static BakedQuad bakeFace(final BlockElement dro, final BlockElementFace drp, final TextureAtlasSprite eaj, final Direction fp, final ModelState ecc, final ResourceLocation sm) {
        return BlockModel.FACE_BAKERY.bakeQuad(dro.from, dro.to, drp, eaj, fp, ecc, dro.rotation, dro.shade, sm);
    }
    
    public boolean hasTexture(final String string) {
        return !MissingTextureAtlasSprite.getLocation().equals(this.getMaterial(string).texture());
    }
    
    public Material getMaterial(String string) {
        if (isTextureReference(string)) {
            string = string.substring(1);
        }
        final List<String> list3 = Lists.newArrayList();
        while (true) {
            final Either<Material, String> either4 = this.findTextureEntry(string);
            final Optional<Material> optional5 = (Optional<Material>)either4.left();
            if (optional5.isPresent()) {
                return optional5.get();
            }
            string = either4.right().get();
            if (list3.contains(string)) {
                BlockModel.LOGGER.warn("Unable to resolve texture due to reference chain {}->{} in {}", Joiner.on("->").join(list3), string, this.name);
                return new Material(TextureAtlas.LOCATION_BLOCKS, MissingTextureAtlasSprite.getLocation());
            }
            list3.add(string);
        }
    }
    
    private Either<Material, String> findTextureEntry(final String string) {
        for (BlockModel drs3 = this; drs3 != null; drs3 = drs3.parent) {
            final Either<Material, String> either4 = drs3.textureMap.get(string);
            if (either4 != null) {
                return either4;
            }
        }
        return (Either<Material, String>)Either.left(new Material(TextureAtlas.LOCATION_BLOCKS, MissingTextureAtlasSprite.getLocation()));
    }
    
    private static boolean isTextureReference(final String string) {
        return string.charAt(0) == '#';
    }
    
    public BlockModel getRootModel() {
        return (this.parent == null) ? this : this.parent.getRootModel();
    }
    
    public ItemTransforms getTransforms() {
        final ItemTransform dry2 = this.getTransform(ItemTransforms.TransformType.THIRD_PERSON_LEFT_HAND);
        final ItemTransform dry3 = this.getTransform(ItemTransforms.TransformType.THIRD_PERSON_RIGHT_HAND);
        final ItemTransform dry4 = this.getTransform(ItemTransforms.TransformType.FIRST_PERSON_LEFT_HAND);
        final ItemTransform dry5 = this.getTransform(ItemTransforms.TransformType.FIRST_PERSON_RIGHT_HAND);
        final ItemTransform dry6 = this.getTransform(ItemTransforms.TransformType.HEAD);
        final ItemTransform dry7 = this.getTransform(ItemTransforms.TransformType.GUI);
        final ItemTransform dry8 = this.getTransform(ItemTransforms.TransformType.GROUND);
        final ItemTransform dry9 = this.getTransform(ItemTransforms.TransformType.FIXED);
        return new ItemTransforms(dry2, dry3, dry4, dry5, dry6, dry7, dry8, dry9);
    }
    
    private ItemTransform getTransform(final ItemTransforms.TransformType b) {
        if (this.parent != null && !this.transforms.hasTransform(b)) {
            return this.parent.getTransform(b);
        }
        return this.transforms.getTransform(b);
    }
    
    @Override
    public String toString() {
        return this.name;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        FACE_BAKERY = new FaceBakery();
        GSON = new GsonBuilder().registerTypeAdapter(BlockModel.class, new Deserializer()).registerTypeAdapter(BlockElement.class, new BlockElement.Deserializer()).registerTypeAdapter(BlockElementFace.class, new BlockElementFace.Deserializer()).registerTypeAdapter(BlockFaceUV.class, new BlockFaceUV.Deserializer()).registerTypeAdapter(ItemTransform.class, new ItemTransform.Deserializer()).registerTypeAdapter(ItemTransforms.class, new ItemTransforms.Deserializer()).registerTypeAdapter(ItemOverride.class, new ItemOverride.Deserializer()).create();
    }
    
    public static class Deserializer implements JsonDeserializer<BlockModel> {
        @Override
        public BlockModel deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
            final JsonObject jsonObject5 = jsonElement.getAsJsonObject();
            final List<BlockElement> list6 = this.getElements(jsonDeserializationContext, jsonObject5);
            final String string7 = this.getParentName(jsonObject5);
            final Map<String, Either<Material, String>> map8 = this.getTextureMap(jsonObject5);
            final boolean boolean9 = this.getAmbientOcclusion(jsonObject5);
            ItemTransforms drz10 = ItemTransforms.NO_TRANSFORMS;
            if (jsonObject5.has("display")) {
                final JsonObject jsonObject6 = GsonHelper.getAsJsonObject(jsonObject5, "display");
                drz10 = jsonDeserializationContext.<ItemTransforms>deserialize(jsonObject6, ItemTransforms.class);
            }
            final List<ItemOverride> list7 = this.getOverrides(jsonDeserializationContext, jsonObject5);
            GuiLight b12 = null;
            if (jsonObject5.has("gui_light")) {
                b12 = GuiLight.getByName(GsonHelper.getAsString(jsonObject5, "gui_light"));
            }
            final ResourceLocation sm13 = string7.isEmpty() ? null : new ResourceLocation(string7);
            return new BlockModel(sm13, list6, map8, boolean9, b12, drz10, list7);
        }
        
        protected List<ItemOverride> getOverrides(final JsonDeserializationContext jsonDeserializationContext, final JsonObject jsonObject) {
            final List<ItemOverride> list4 = Lists.newArrayList();
            if (jsonObject.has("overrides")) {
                final JsonArray jsonArray5 = GsonHelper.getAsJsonArray(jsonObject, "overrides");
                for (final JsonElement jsonElement7 : jsonArray5) {
                    list4.add(jsonDeserializationContext.<ItemOverride>deserialize(jsonElement7, ItemOverride.class));
                }
            }
            return list4;
        }
        
        private Map<String, Either<Material, String>> getTextureMap(final JsonObject jsonObject) {
            final ResourceLocation sm3 = TextureAtlas.LOCATION_BLOCKS;
            final Map<String, Either<Material, String>> map4 = Maps.newHashMap();
            if (jsonObject.has("textures")) {
                final JsonObject jsonObject2 = GsonHelper.getAsJsonObject(jsonObject, "textures");
                for (final Map.Entry<String, JsonElement> entry7 : jsonObject2.entrySet()) {
                    map4.put(entry7.getKey(), parseTextureLocationOrReference(sm3, entry7.getValue().getAsString()));
                }
            }
            return map4;
        }
        
        private static Either<Material, String> parseTextureLocationOrReference(final ResourceLocation sm, final String string) {
            if (isTextureReference(string)) {
                return (Either<Material, String>)Either.right(string.substring(1));
            }
            final ResourceLocation sm2 = ResourceLocation.tryParse(string);
            if (sm2 == null) {
                throw new JsonParseException(string + " is not valid resource location");
            }
            return (Either<Material, String>)Either.left(new Material(sm, sm2));
        }
        
        private String getParentName(final JsonObject jsonObject) {
            return GsonHelper.getAsString(jsonObject, "parent", "");
        }
        
        protected boolean getAmbientOcclusion(final JsonObject jsonObject) {
            return GsonHelper.getAsBoolean(jsonObject, "ambientocclusion", true);
        }
        
        protected List<BlockElement> getElements(final JsonDeserializationContext jsonDeserializationContext, final JsonObject jsonObject) {
            final List<BlockElement> list4 = Lists.newArrayList();
            if (jsonObject.has("elements")) {
                for (final JsonElement jsonElement6 : GsonHelper.getAsJsonArray(jsonObject, "elements")) {
                    list4.add(jsonDeserializationContext.<BlockElement>deserialize(jsonElement6, BlockElement.class));
                }
            }
            return list4;
        }
    }
    
    public enum GuiLight {
        FRONT("front"), 
        SIDE("side");
        
        private final String name;
        
        private GuiLight(final String string3) {
            this.name = string3;
        }
        
        public static GuiLight getByName(final String string) {
            for (final GuiLight b5 : values()) {
                if (b5.name.equals(string)) {
                    return b5;
                }
            }
            throw new IllegalArgumentException("Invalid gui light: " + string);
        }
        
        public boolean lightLikeBlock() {
            return this == GuiLight.SIDE;
        }
    }
}
