package electroblob.wizardry.client.model;

import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.renderer.block.model.ItemOverrideList;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad;
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
import net.minecraftforge.common.ForgeModContainer;
import net.minecraftforge.fml.client.FMLClientHandler;
import org.apache.commons.lang3.tuple.Pair;

import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
import java.util.ArrayList;
import java.util.List;

/**
 * Custom baked model that stores a list of texture names and sets the lighting to full brightness for any quads
 * with one of those textures. Most of this code was copied and adapted from refined storage, which is licensed under
 * the MIT license. https://github.com/raoulvdberge/refinedstorage<br>
 * <br>
 * <i>N.B. This doesn't cover item models, and all of the code/model solutions to this that I have tried haven't worked.
 * However, I noticed that the shade tag seems to work perfectly for items, so instead I've simply duplicated the block
 * models into the item  models folder and add {@code "shade":false} where appropriate. (If it works, it works, right?)</i>
 *
 * @author Electroblob
 * @author raoulvdberge
 */
public class BakedModelGlowingOverlay implements IBakedModel {

	// Something something something cache, says Forge
	// This breaks randomised block models (because it's cached, duh...) but simply including the rand parameter will
	// completely defeat the point of the cache so I need some way of storing the randomised model variants... hmmm...
	// See WeightedBakedModel for more on randomisation (it's pretty similar to my 1.7 implementation from years ago)

//	private class CacheKey {
//
//		private IBakedModel base;
//		private String suffix;
//		private IBlockState state;
//		private EnumFacing side;
//
//		public CacheKey(IBakedModel base, String suffix, IBlockState state, EnumFacing side){
//			this.base = base;
//			this.suffix = suffix;
//			this.state = state;
//			this.side = side;
//		}
//
//		@Override
//		public boolean equals(Object o){
//
//			if(this == o) return true;
//			if(o == null || getClass() != o.getClass()) return false;
//
//			CacheKey cacheKey = (CacheKey)o;
//
//			if(cacheKey.side != side) return false;
//			if(!state.equals(cacheKey.state)) return false;
//
//			return true;
//		}
//
//		@Override
//		public int hashCode() {
//			return state.hashCode() + (31 * (side != null ? side.hashCode() : 0));
//		}
//	}
//
//	private static final LoadingCache<CacheKey, List<BakedQuad>> CACHE = CacheBuilder.newBuilder().build(new CacheLoader<CacheKey, List<BakedQuad>>() {
//		@Override
//		public List<BakedQuad> load(CacheKey key) {
//			return transformQuads(key.base.getQuads(key.state, key.side, 0), key.suffix);
//		}
//	});

	private final IBakedModel delegate;
	private String suffix;

	public BakedModelGlowingOverlay(IBakedModel delegate, String suffix){
		this.delegate = delegate;
		this.suffix = suffix;
	}

	@Override
	public List<BakedQuad> getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand){
		if(state == null) return delegate.getQuads(state, side, rand);
		return transformQuads(delegate.getQuads(state, side, rand), suffix);
		//return CACHE.getUnchecked(new CacheKey(delegate, suffix, state instanceof IExtendedBlockState ? ((IExtendedBlockState) state).getClean() : state, side));
	}

	// I would write these myself but I'd end up with almost the exact same thing anyway
	// They replace the quads from the original (delegate) model with full-brightness quads if their texture has the given suffix

	private static List<BakedQuad> transformQuads(List<BakedQuad> oldQuads, String suffix){

		List<BakedQuad> quads = new ArrayList<>(oldQuads);

		for(int i = 0; i < quads.size(); ++i){
			BakedQuad quad = quads.get(i);

			if(quad.getSprite().getIconName().endsWith(suffix)){
				quads.set(i, transformQuad(quad, 0.007F)); // What's the significance of 0.007?
			}
		}

		return quads;
	}

	private static BakedQuad transformQuad(BakedQuad quad, float light){

		if(isLightMapDisabled()){
			return quad;
		}

		VertexFormat newFormat = getFormatWithLightMap(quad.getFormat());

		UnpackedBakedQuad.Builder builder = new UnpackedBakedQuad.Builder(newFormat);

		VertexLighterFlat trans = new VertexLighterFlat(Minecraft.getMinecraft().getBlockColors()) {
			@Override
			protected void updateLightmap(float[] normal, float[] lightmap, float x, float y, float z){
				lightmap[0] = light;
				lightmap[1] = light;
			}

			@Override
			public void setQuadTint(int tint){
				// NO OP
			}
		};

		trans.setParent(builder);

		quad.pipe(trans);

		builder.setQuadTint(quad.getTintIndex());
		builder.setQuadOrientation(quad.getFace());
		builder.setTexture(quad.getSprite());
		builder.setApplyDiffuseLighting(false);

		return builder.build();
	}

	@Override
	public boolean isAmbientOcclusion(){
		return delegate.isAmbientOcclusion();
	}

	@Override
	public boolean isGui3d(){
		return delegate.isGui3d();
	}

	@Override
	public boolean isBuiltInRenderer(){
		return delegate.isBuiltInRenderer();
	}

	@Override
	public TextureAtlasSprite getParticleTexture(){
		return delegate.getParticleTexture();
	}

	@Override
	public ItemCameraTransforms getItemCameraTransforms(){
		return delegate.getItemCameraTransforms();
	}

	@Override
	public ItemOverrideList getOverrides(){
		return delegate.getOverrides();//BakedModelItemOverride.instance;
	}

	@Override
	public boolean isAmbientOcclusion(IBlockState state){
		return delegate.isAmbientOcclusion(state);
	}

	@Override
	public Pair<? extends IBakedModel, Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType){
		return delegate.handlePerspective(cameraTransformType);
	}

	// Utilities

	private static boolean isLightMapDisabled(){
		return FMLClientHandler.instance().hasOptifine() || !ForgeModContainer.forgeLightPipelineEnabled;
	}

	private static final VertexFormat ITEM_FORMAT_WITH_LIGHTMAP = new VertexFormat(DefaultVertexFormats.ITEM).addElement(DefaultVertexFormats.TEX_2S);

	private static VertexFormat getFormatWithLightMap(VertexFormat format){

		if(isLightMapDisabled()){
			return format;
		}

		if(format == DefaultVertexFormats.BLOCK){
			return DefaultVertexFormats.BLOCK;
		}else if(format == DefaultVertexFormats.ITEM){
			return ITEM_FORMAT_WITH_LIGHTMAP;
		}else if(!format.hasUvOffset(1)){
			VertexFormat result = new VertexFormat(format);
			result.addElement(DefaultVertexFormats.TEX_2S);
			return result;
		}

		return format;
	}

	// Bit of a weird way of doing things if you ask me, but as far as I can tell it's how you're supposed to do it
//	public static final class BakedModelItemOverride extends ItemOverrideList {
//
//		// This class doesn't contain any data of its own so it can be a singleton
//		public static final BakedModelItemOverride instance = new BakedModelItemOverride();
//
//		private BakedModelItemOverride(){
//			super(ImmutableList.of()); // We're not using the list functionality
//		}
//
//		@Override
//		public IBakedModel handleItemState(IBakedModel originalModel, ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity){
//			return new BakedModelGlowingOverlay(originalModel, "overlay"); // Bish bash bosh
//		}
//	}
}
