package client.renderer.blockmodel;

import java.io.FileNotFoundException;
import java.util.List;
import java.util.Map;

import client.renderer.model.ModelBox;
import client.renderer.texture.Sprite;
import client.renderer.texture.TextureMap;
import client.renderer.texture.TextureUtil;
import common.collect.Lists;
import common.collect.Maps;
import common.log.Log;
import common.util.Facing;
import common.util.Vector3f;

public abstract class ModelGenerator {
	public static List<ModelBox> bakeModel(String textureLocation, int tx, int ty, int tw, int th, float x, float y, float z, int w, int h, int d)
    {
        int[] img;
        try
        {
            img = TextureUtil.readImageData(textureLocation);
        }
        catch(Throwable e) {
        	if(e instanceof FileNotFoundException)
				Log.IO.warn("Textur ist nicht zum Zusammenstellen vorhanden: " + textureLocation);
			else
				Log.IO.error(e, "Konnte Textur '" + textureLocation + "' nicht zum Zusammenstellen laden");
        	return Lists.<ModelBox>newArrayList();
        }
        if(img.length < tw * th) {
        	Log.IO.warn("Konnte Textur '" + textureLocation.toString() + "' nicht zum Zusammenstellen laden weil sie zu klein ist " + 
			"(benötige " + (tw*th) + " [" + tw + "x" + th + "], habe " + img.length + ")");
        	return Lists.<ModelBox>newArrayList();
        }
    	return bakeModel(img, tx, ty, tw, th, x, y, z, w, h, d);
    }

	public static List<ModelBox> bakeModel(int[] texture, int tx, int ty, int tw, int th, float x, float y, float z, int w, int h, int d) {
		List<ModelBox> parts = Lists.<ModelBox>newArrayList();
		List<Span> spans = Lists.<Span>newArrayList();
        int size = 1;
        while(size * 2 * tw * size * 2 * th <= texture.length) {
        	size *= 2;
        }
        compileSpans(spans, texture, tx * size, ty * size, w * size, h * size, tw * size, th * size);
		if(spans.isEmpty()) {
			return parts;
		}
		parts.add(new ModelBox(tw, th, tx + w, ty, tx, ty + h, x, y, z, x + (float)w, y + (float)h, z, Facing.SOUTH));
		parts.add(new ModelBox(tw, th, tx, ty, tx + w, ty + h, x, y, z + (float)d, x + (float)w, y + (float)h, z + (float)d, Facing.NORTH));
		for(ModelGenerator.Span span : spans) {
			int x1 = 0;
			int y1 = 0;
			int x2 = 0;
			int y2 = 0;
			int u1 = 0;
			int u2 = 0;
			int v1 = 0;
			int v2 = 0;
			int start = span.getStart();
			int end = span.getEnd();
			int adj = span.getAdjacent();
			ModelGenerator.SpanFacing facing = span.getFacing();
			switch(facing) {
			case UP:
				u1 = start;
				x1 = start;
				x2 = u2 = end + 1;
				v1 = adj;
				y1 = adj;
				v2 = adj + 1;
				y2 = adj;
				break;
			case DOWN:
				v2 = adj + 1;
				v1 = adj;
				u1 = start;
				x1 = start;
				x2 = u2 = end + 1;
				y1 = adj + 1;
				y2 = adj + 1;
				break;
			case LEFT:
				u1 = adj;
				x1 = adj;
				u2 = adj + 1;
				x2 = adj;
				v2 = start;
				y1 = start;
				y2 = v1 = end + 1;
				break;
			case RIGHT:
				u2 = adj + 1;
				u1 = adj;
				x1 = adj + 1;
				x2 = adj + 1;
				v2 = start;
				y1 = start;
				y2 = v1 = end + 1;
				break;
			}
			parts.add(new ModelBox(tw * size, th * size, tx * size + u1, ty * size + v1, tx * size + u2, ty * size + v2, 
					x + (float)x1 / (float)size, y + (float)y2 / (float)size, z, x + (float)x2 / (float)size, y + (float)y1 / (float)size, z + (float)d, 
					facing.getFacing()));
		}
		return parts;
	}

	public static ModelBlock makeItemModel(TextureMap texmap, ModelBlock model) {
		List<BlockPart> parts = Lists.<BlockPart>newArrayList();
		for(int n = 0; n < model.getNumTextures(); ++n) {
			String name = model.getTexture(n);
			parts.addAll(compileParts(n, name, texmap.getAtlasSprite(name)));
		}
		if(parts.isEmpty()) {
			return null;
		}
		else {
			return new ModelBlock(model.getPrimary(), parts, model.getTransform(), model.getTextures());
		}
	}

	private static List<BlockPart> compileParts(int layer, String tex, Sprite sprite) {
		Map<Facing, BlockPartFace> map = Maps.<Facing, BlockPartFace>newHashMap();
		map.put(Facing.SOUTH, new BlockPartFace(null, layer, tex, new BlockFaceUV(new float[] {0.0F, 0.0F, 16.0F, 16.0F}, 0)));
		map.put(Facing.NORTH, new BlockPartFace(null, layer, tex, new BlockFaceUV(new float[] {16.0F, 0.0F, 0.0F, 16.0F}, 0)));
		List<BlockPart> parts = Lists.<BlockPart>newArrayList();
		parts.add(new BlockPart(new Vector3f(0.0F, 0.0F, 7.5F), new Vector3f(16.0F, 16.0F, 8.5F), map, null, true));
		parts.addAll(bakeParts(sprite, tex, layer));
		return parts;
	}

	private static List<BlockPart> bakeParts(Sprite sprite, String tex, int layer) {
		float w = (float)sprite.getIconWidth();
		float h = (float)sprite.getIconHeight();
		List<BlockPart> parts = Lists.<BlockPart>newArrayList();
		for(ModelGenerator.Span span : compileSpans(sprite)) {
			float x1 = 0.0F;
			float y1 = 0.0F;
			float x2 = 0.0F;
			float y2 = 0.0F;
			float u1 = 0.0F;
			float u2 = 0.0F;
			float v1 = 0.0F;
			float v2 = 0.0F;
			float xs = 0.0F;
			float ys = 0.0F;
			float start = (float)span.getStart();
			float end = (float)span.getEnd();
			float adj = (float)span.getAdjacent();
			ModelGenerator.SpanFacing facing = span.getFacing();
			switch(facing) {
			case UP:
				u1 = start;
				x1 = start;
				x2 = u2 = end + 1.0F;
				v1 = adj;
				y1 = adj;
				v2 = adj;
				y2 = adj;
				xs = 16.0F / w;
				ys = 16.0F / (h - 1.0F);
				break;
			case DOWN:
				v2 = adj;
				v1 = adj;
				u1 = start;
				x1 = start;
				x2 = u2 = end + 1.0F;
				y1 = adj + 1.0F;
				y2 = adj + 1.0F;
				xs = 16.0F / w;
				ys = 16.0F / (h - 1.0F);
				break;
			case LEFT:
				u1 = adj;
				x1 = adj;
				u2 = adj;
				x2 = adj;
				v2 = start;
				y1 = start;
				y2 = v1 = end + 1.0F;
				xs = 16.0F / (w - 1.0F);
				ys = 16.0F / h;
				break;
			case RIGHT:
				u2 = adj;
				u1 = adj;
				x1 = adj + 1.0F;
				x2 = adj + 1.0F;
				v2 = start;
				y1 = start;
				y2 = v1 = end + 1.0F;
				xs = 16.0F / (w - 1.0F);
				ys = 16.0F / h;
				break;
			}
			float rx = 16.0F / w;
			float ry = 16.0F / h;
			x1 = x1 * rx;
			x2 = x2 * rx;
			y1 = y1 * ry;
			y2 = y2 * ry;
			y1 = 16.0F - y1;
			y2 = 16.0F - y2;
			u1 = u1 * xs;
			u2 = u2 * xs;
			v1 = v1 * ys;
			v2 = v2 * ys;
			Map<Facing, BlockPartFace> map = Maps.<Facing, BlockPartFace>newHashMap();
			map.put(facing.getFacing(), new BlockPartFace(null, layer, tex, new BlockFaceUV(new float[] {u1, v1, u2, v2}, 0)));
			switch(facing) {
			case UP:
				parts.add(new BlockPart(new Vector3f(x1, y1, 7.5F), new Vector3f(x2, y1, 8.5F), map, null, true));
				break;
			case DOWN:
				parts.add(new BlockPart(new Vector3f(x1, y2, 7.5F), new Vector3f(x2, y2, 8.5F), map, null, true));
				break;
			case LEFT:
				parts.add(new BlockPart(new Vector3f(x1, y1, 7.5F), new Vector3f(x1, y2, 8.5F), map, null, true));
				break;
			case RIGHT:
				parts.add(new BlockPart(new Vector3f(x2, y1, 7.5F), new Vector3f(x2, y2, 8.5F), map, null, true));
				break;
			}
		}
		return parts;
	}

	private static List<Span> compileSpans(Sprite sprite) {
		int w = sprite.getIconWidth();
		int h = sprite.getIconHeight();
		List<Span> spans = Lists.<Span>newArrayList();
		for(int f = 0; f < sprite.getFrameCount(); ++f) {
			compileSpans(spans, sprite.getFrameTextureData(f), 0, 0, w, h, w, h);
		}
		return spans;
	}

	private static void compileSpans(List<Span> spans, int[] data, int tx, int ty, int w, int h, int tw, int th) {
		for(int y = 0; y < h; ++y) {
			for(int x = 0; x < w; ++x) {
				if(!isTransparent(data, tw, tx+x, ty+y, tx, ty, w, h)) {
					checkAdd(SpanFacing.UP, spans, data, x, y, tx, ty, w, h, tw, th);
					checkAdd(SpanFacing.DOWN, spans, data, x, y, tx, ty, w, h, tw, th);
					checkAdd(SpanFacing.LEFT, spans, data, x, y, tx, ty, w, h, tw, th);
					checkAdd(SpanFacing.RIGHT, spans, data, x, y, tx, ty, w, h, tw, th);
				}
			}
		}
	}

	private static void checkAdd(SpanFacing facing, List<Span> spans, int[] data, int x, int y, int tx, int ty, int w, int h, int tw, int th) {
		if(!isTransparent(data, tw, tx+x + facing.getXDirection(), ty+y + facing.getZDirection(), tx, ty, w, h)) {
			return;
		}
		Span nspan = null;
		for(Span span : spans) {
			if(span.getFacing() == facing) {
				int adj = facing.isYAxis() ? y : x;
				if(span.getAdjacent() == adj) {
					nspan = span;
					break;
				}
			}
		}
		int adj = facing.isYAxis() ? y : x;
		int pos = facing.isYAxis() ? x : y;
		if(nspan == null) {
			spans.add(new Span(facing, pos, adj));
		}
		else {
			nspan.addPosition(pos);
		}
	}

	private static boolean isTransparent(int[] data, int tw, int x, int y, int tx, int ty, int w, int h) {
		return x >= tx && y >= ty && x < tx+w && y < ty+h ? (data[y * tw + x] >> 24 & 255) == 0 : true;
	}

	private static class Span {
		private final SpanFacing facing;
		private int start;
		private int end;
		private final int adjacent;

		public Span(SpanFacing facing, int position, int adjacent) {
			this.facing = facing;
			this.start = position;
			this.end = position;
			this.adjacent = adjacent;
		}

		public void addPosition(int position) {
			if(position < this.start) {
				this.start = position;
			}
			else if(position > this.end) {
				this.end = position;
			}
		}

		public SpanFacing getFacing() {
			return this.facing;
		}

		public int getStart() {
			return this.start;
		}

		public int getEnd() {
			return this.end;
		}

		public int getAdjacent() {
			return this.adjacent;
		}
	}

	private static enum SpanFacing {
		UP(Facing.UP, 0, -1), DOWN(Facing.DOWN, 0, 1), LEFT(Facing.EAST, -1, 0), RIGHT(Facing.WEST, 1, 0);

		private final Facing facing;
		private final int xdir;
		private final int zdir;

		private SpanFacing(Facing facing, int xdir, int zdir) {
			this.facing = facing;
			this.xdir = xdir;
			this.zdir = zdir;
		}

		public Facing getFacing() {
			return this.facing;
		}

		public int getXDirection() {
			return this.xdir;
		}

		public int getZDirection() {
			return this.zdir;
		}

		private boolean isYAxis() {
			return this == DOWN || this == UP;
		}
	}
}
