package com.rs.cache.loaders;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.RGBImageFilter;
import java.util.ArrayList;
import java.util.Arrays;

import com.alex.io.InputStream;
import com.alex.io.OutputStream;
import com.alex.store.Store;
import com.alex.utils.Constants;

public final class IndexedColorImageFile {

	private BufferedImage[] images;

	private int pallete[];
	private int pixelsIndexes[][];
	private byte alpha[][];
	private boolean[] usesAlpha;
	private int biggestWidth;
	private int biggestHeight;

	public IndexedColorImageFile(BufferedImage... images) {
		this.images = images;
	}

	public IndexedColorImageFile(Store cache, int archiveId, int fileId) {
		this(cache, Constants.SPRITES_INDEX, archiveId, fileId);
	}

	/*
	 * 
	 */
	public IndexedColorImageFile(Store cache, int idx, int archiveId, int fileId) {
		decodeArchive(cache, idx, archiveId, fileId);
	}

	public void decodeArchive(Store cache, int idx, int archiveId, int fileId) {
		byte[] data = cache.getIndexes()[idx].getFile(archiveId, fileId);
		if (data == null)
			return;
		InputStream stream = new InputStream(data);
		stream.setOffset(data.length - 2);
		int count = stream.readUnsignedShort();
		images = new BufferedImage[count];
		pixelsIndexes = new int[images.length][];
		alpha = new byte[images.length][];
		usesAlpha = new boolean[images.length];
		int[] imagesMinX = new int[images.length];
		int[] imagesMinY = new int[images.length];
		int[] imagesWidth = new int[images.length];
		int[] imagesHeight = new int[images.length];
		stream.setOffset(data.length - 7 - images.length * 8);
		setBiggestWidth(stream.readShort()); // biggestWidth
		setBiggestHeight(stream.readShort()); // biggestHeight
		int palleteLength = (stream.readUnsignedByte() & 0xff) + 1; // 1 + up to
		// 255.
		for (int index = 0; index < images.length; index++)
			imagesMinX[index] = stream.readUnsignedShort();
		for (int index = 0; index < images.length; index++)
			imagesMinY[index] = stream.readUnsignedShort();
		for (int index = 0; index < images.length; index++)
			imagesWidth[index] = stream.readUnsignedShort();
		for (int index = 0; index < images.length; index++)
			imagesHeight[index] = stream.readUnsignedShort();
		stream.setOffset(data.length - 7 - images.length * 8 - (palleteLength - 1) * 3);
		pallete = new int[palleteLength];
		for (int index = 1; index < palleteLength; index++) {
			pallete[index] = stream.read24BitInt();
			if (pallete[index] == 0)
				pallete[index] = 1;
		}
		stream.setOffset(0);
		for (int i_20_ = 0; i_20_ < images.length; i_20_++) {
			int pixelsIndexesLength = imagesWidth[i_20_] * imagesHeight[i_20_];
			pixelsIndexes[i_20_] = new int[pixelsIndexesLength];
			alpha[i_20_] = new byte[pixelsIndexesLength];
			int maskData = stream.readUnsignedByte();
			if ((maskData & 0x2) == 0) {
				if ((maskData & 0x1) == 0) {
					for (int index = 0; index < pixelsIndexesLength; index++) {
						pixelsIndexes[i_20_][index] = (byte) stream.readByte();
					}
				} else {
					for (int i_24_ = 0; i_24_ < imagesWidth[i_20_]; i_24_++) {
						for (int i_25_ = 0; i_25_ < imagesHeight[i_20_]; i_25_++) {
							pixelsIndexes[i_20_][i_24_ + i_25_ * imagesWidth[i_20_]] = (byte) stream.readByte();
						}
					}
				}
			} else {
				usesAlpha[i_20_] = true;
				boolean bool = false;
				if ((maskData & 0x1) == 0) {
					for (int index = 0; index < pixelsIndexesLength; index++) {
						pixelsIndexes[i_20_][index] = (byte) stream.readByte();
					}
					for (int i_27_ = 0; i_27_ < pixelsIndexesLength; i_27_++) {
						byte i_28_ = (alpha[i_20_][i_27_] = (byte) stream.readByte());
						bool = bool | i_28_ != -1;
					}
				} else {
					for (int i_29_ = 0; i_29_ < imagesWidth[i_20_]; i_29_++) {
						for (int i_30_ = 0; i_30_ < imagesHeight[i_20_]; i_30_++) {
							pixelsIndexes[i_20_][i_29_ + i_30_ * imagesWidth[i_20_]] = stream.readByte();
						}
					}
					for (int i_31_ = 0; i_31_ < imagesWidth[i_20_]; i_31_++) {
						for (int i_32_ = 0; i_32_ < imagesHeight[i_20_]; i_32_++) {
							byte i_33_ = (alpha[i_20_][i_31_ + i_32_ * imagesWidth[i_20_]] = (byte) stream.readByte());
							bool = bool | i_33_ != -1;
						}
					}
				}
				if (!bool)
					alpha[i_20_] = null;
			}
			images[i_20_] = getBufferedImage(imagesWidth[i_20_], imagesHeight[i_20_], pixelsIndexes[i_20_], alpha[i_20_], usesAlpha[i_20_]);
		}
	}

	public BufferedImage getBufferedImage(int width, int height, int[] pixelsIndexes, byte[] extraPixels, boolean useExtraPixels) {
		if (width <= 0 || height <= 0)
			return null;
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
		int[] rgbArray = new int[width * height];
		int i = 0;
		int i_43_ = 0;
		if (useExtraPixels && extraPixels != null) {
			for (int i_44_ = 0; i_44_ < height; i_44_++) {
				for (int i_45_ = 0; i_45_ < width; i_45_++) {
					rgbArray[i_43_++] = (extraPixels[i] << 24 | (pallete[pixelsIndexes[i] & 0xff]));
					i++;
				}
			}
		} else {
			for (int i_46_ = 0; i_46_ < height; i_46_++) {
				for (int i_47_ = 0; i_47_ < width; i_47_++) {
					int i_48_ = pallete[pixelsIndexes[i++] & 0xff];
					rgbArray[i_43_++] = i_48_ != 0 ? ~0xffffff | i_48_ : 0;
				}
			}
		}
		image.setRGB(0, 0, width, height, rgbArray, 0, width);
		image.flush();
		return image;
	}

	public byte[] encodeFile() {
		if (pallete == null) // if not generated yet
			generatePallete();
		OutputStream stream = new OutputStream();
		// sets pallete indexes and int size bytes
		for (int imageId = 0; imageId < images.length; imageId++) {
			int pixelsMask = 0;
			if (usesAlpha[imageId])
				pixelsMask |= 0x2;
			// pixelsMask |= 0x1; //sets read all rgbarray indexes 1by1
			stream.writeByte(pixelsMask);
			for (int index = 0; index < pixelsIndexes[imageId].length; index++)
				stream.writeByte(pixelsIndexes[imageId][index]);
			if (usesAlpha[imageId])
				for (int index = 0; index < alpha[imageId].length; index++)
					stream.writeByte(alpha[imageId][index]);
		}

		// sets up to 255colors pallete, index0 is black
		for (int index = 1; index < pallete.length; index++)
			stream.write24BitInt(pallete[index]);

		// extra inform
		if (biggestWidth == 0 && biggestHeight == 0) {
			for (BufferedImage image : images) {
				if (image.getWidth() > biggestWidth)
					biggestWidth = image.getWidth();
				if (image.getHeight() > biggestHeight)
					biggestHeight = image.getHeight();
			}
		}
		stream.writeShort(biggestWidth); // probably used for textures
		stream.writeShort(biggestHeight);// probably used for textures
		stream.writeByte(pallete.length - 1); // sets pallete size, -1 cuz of
		// black index
		for (int imageId = 0; imageId < images.length; imageId++)
			stream.writeShort(images[imageId].getMinX());
		for (int imageId = 0; imageId < images.length; imageId++)
			stream.writeShort(images[imageId].getMinY());
		for (int imageId = 0; imageId < images.length; imageId++)
			stream.writeShort(images[imageId].getWidth());
		for (int imageId = 0; imageId < images.length; imageId++)
			stream.writeShort(images[imageId].getHeight());
		stream.writeShort(images.length); // amt of images
		// generates fixed byte data array
		byte[] container = new byte[stream.getOffset()];
		stream.setOffset(0);
		stream.getBytes(container, 0, container.length);
		return container;
	}

	public int getPalleteIndex(int rgb) {
		if (pallete == null) // index 0 is 0
			pallete = new int[] { 0 };
		for (int index = 0; index < pallete.length; index++) {
			if (pallete[index] == rgb)
				return index;
		}
		if (pallete.length == 256) {
			System.out.println("Pallete to big, please reduce images quality.");
			return 0;
		}
		// throw new RuntimeException("Pallete to big, please reduce images
		// quality.");
		int[] newpallete = new int[pallete.length + 1];
		System.arraycopy(pallete, 0, newpallete, 0, pallete.length);
		newpallete[pallete.length] = rgb;
		pallete = newpallete;
		return pallete.length - 1;
	}

	public int addImage(BufferedImage image) {
		BufferedImage[] newImages = Arrays.copyOf(images, images.length + 1);
		newImages[images.length] = image;
		images = newImages;
		pallete = null;
		pixelsIndexes = null;
		alpha = null;
		usesAlpha = null;
		return images.length - 1;
	}

	public void replaceImage(BufferedImage image, int index) {
		images[index] = image;
		pallete = null;
		pixelsIndexes = null;
		alpha = null;
		usesAlpha = null;
	}

	public void generatePallete() {
		pixelsIndexes = new int[images.length][];
		alpha = new byte[images.length][];
		usesAlpha = new boolean[images.length];
		for (int index = 0; index < images.length; index++) {
			BufferedImage image = filter(images[index]);
			int[] rgbArray = new int[image.getWidth() * image.getHeight()];
			image.getRGB(0, 0, image.getWidth(), image.getHeight(), rgbArray, 0, image.getWidth());
			pixelsIndexes[index] = new int[image.getWidth() * image.getHeight()];
			alpha[index] = new byte[image.getWidth() * image.getHeight()];
			for (int pixel = 0; pixel < pixelsIndexes[index].length; pixel++) {
				int rgb = rgbArray[pixel];
				Color c = new Color(rgb, true);
				int medintrgb = new Color(c.getRed(), c.getGreen(), c.getBlue()).getRGB();// convertToMediumInt(rgb);
				int i = getPalleteIndex(medintrgb);
				pixelsIndexes[index][pixel] = i;
				if (c.getAlpha() != 0) {
					alpha[index][pixel] = (byte) c.getAlpha();
					usesAlpha[index] = true;
				}
			}
		}
	}

	public BufferedImage[] getImages() {
		return images;
	}

	public int getBiggestWidth() {
		return biggestWidth;
	}

	public void setBiggestWidth(int biggestWidth) {
		this.biggestWidth = biggestWidth;
	}

	public int getBiggestHeight() {
		return biggestHeight;
	}

	public void setBiggestHeight(int biggestHeight) {
		this.biggestHeight = biggestHeight;
	}

	public static BufferedImage filter(BufferedImage image) {
		DetailFilter filter = new DetailFilter();
		while (!filter.done()) {
			for (int x = 0; x < image.getWidth(); x++) {
				for (int y = 0; y < image.getHeight(); y++) {
					int rgb = filter.filterRGB(x, y, image.getRGB(x, y));
					image.setRGB(x, y, rgb);
				}
			}
		}
		return image;
	}

	private static class DetailFilter extends RGBImageFilter {

		public static int START_DIFF = 1;

		private int diffB, diffG, diffR;

		private ArrayList<Integer> colours;

		public boolean done() {
			if (colours == null) {
				colours = new ArrayList<Integer>();
				diffB = diffG = diffR = START_DIFF;
				return false;
			}

			int increase = colours.size() > 1000 ? 5 : 1;
			if (increase == 0)
				increase = 1;

			// System.out.println(diffR+", "+diffG+", "+diffB+",
			// "+colours.size()+", "+increase);
			if (colours.size() <= 256)
				return true;
			colours.clear();

			if (diffR <= diffB)
				diffR += increase;
			else if (diffG <= diffB)
				diffG += increase;
			else if (diffB <= diffR)
				diffB += increase;
			return false;

		}

		@Override
		public int filterRGB(int x, int y, int rgb) {
			Color c = new Color(rgb, true);
			rgb = c.getRGB();
			int blue = (int) (c.getBlue() / diffB * diffB);
			int green = (int) (c.getGreen() / diffG * diffG);
			int red = (int) (c.getRed() / diffR * diffR);
			int alpha = c.getAlpha();

			rgb = new Color(red, green, blue).getRGB();
			if (!colours.contains(rgb)) {
				// System.out.println(colours.size() +", "+rgb+", "+red+",
				// "+green+", "+blue+", "+alpha);
				colours.add(rgb);
			}
			rgb = new Color(red, green, blue, alpha).getRGB();
			return rgb;
		}
	}

}
