package com.plusminus.craft;

import java.awt.Graphics2D;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;

/***
 * Convenience class to quickly load/generate textures
 * 
 * @author Vincent
 */
public class TextureTool {
	public static double invln2 = 1.0 / Math.log(2.0);
	public static ColorModel glAlphaColorModel= new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
	new int[] {8,8,8,8},
	true,
	false,
	ComponentColorModel.TRANSLUCENT,
	DataBuffer.TYPE_BYTE);
	public static ColorModel glColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
	 new int[] {8,8,8,0},
	 false,
	 false,
	 ComponentColorModel.OPAQUE,
	 DataBuffer.TYPE_BYTE);
	
	public static Texture allocateTexture(Texture frame) {
			return TextureTool.allocateTexture(frame, GL11.GL_RGBA,     // dst pixel format
					GL11.GL_LINEAR, // min filter (unused)
					GL11.GL_LINEAR, // mag filter (unused)
                        true);
	}
	
	public static Texture  allocateTexture(Texture  frame,  int dstPixelFormat, 
	           int minFilter, 
	           int magFilter, 
	           boolean wrap) {
		int srcPixelFormat = 0;
        
        // create the texture ID for this texture
        int textureId = TextureTool.createTextureID(); 
 
		//System.out.println("frame: " + frame.getFrameNum());
        frame.setTextureId(textureId);
		
		frame.bind();
					       
		frame.initializeTextureCompatibleBuffer();
		frame.updateTextureCompatibleBuffer();
        
        if (frame.getImage().getColorModel().hasAlpha()) {
            srcPixelFormat = GL11.GL_RGBA;
        } else {
            srcPixelFormat = GL11.GL_RGB;
        }
        
        // convert that image into a byte buffer of texture data
        //ByteBuffer textureBuffer = TextureLoader.convertImageData(bufferedImage,texture); 
        int wrapMode = wrap ? GL11.GL_REPEAT : GL11.GL_CLAMP; 

		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrapMode); 
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrapMode); 
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, minFilter); 
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, magFilter);
 
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 
                          0, 
                          dstPixelFormat, 
                          frame.getTextureWidth(), 
						  frame.getTextureHeight(), 
                          0, 
                          srcPixelFormat, 
						  GL11.GL_UNSIGNED_BYTE, 
						  frame.getTextureCompatibleBuffer()); 
		
		return frame;
	}
	
	public static Texture  allocateTexture(int width, int height) throws IOException {
       return TextureTool.allocateTexture(
			   GL11.GL_RGBA,     // dst pixel format
			   GL11.GL_LINEAR, // min filter (unused)
			   GL11.GL_LINEAR, // mag filter (unused)
                        width,
                        height,
                        true);  // wrap? 
	}
	
	public static Texture  allocateTexture(BufferedImage image) throws IOException {
	       Texture tex = TextureTool.allocateTexture(
				   GL11.GL_RGBA,     // dst pixel format
				   GL11.GL_LINEAR, // min filter (unused)
				   GL11.GL_LINEAR, // mag filter (unused)
	                       image.getWidth(),
	                        image.getHeight(),
	                        true);  // wrap? 
	       Graphics2D g = tex.getImage().createGraphics();
	       g.drawImage(image, 0, 0, null);
	       return tex;
		}
	public static Texture allocateTexture(BufferedImage image, int filter) throws IOException {
		Texture tex = TextureTool.allocateTexture(
				   GL11.GL_RGBA,     // dst pixel format
				   filter, 
				   filter, 
	                       image.getWidth(),
	                        image.getHeight(),
	                        true);  // wrap? 
	       Graphics2D g = tex.getImage().createGraphics();
	       g.drawImage(image, 0, 0, null);
	       return tex;
	}
	public static int TEXTURETYPE=BufferedImage.TYPE_4BYTE_ABGR;
	
	public static Texture  allocateTexture(
           int dstPixelFormat, 
           int minFilter, 
           int magFilter, 
           int width,
           int height,
           boolean wrap) throws IOException {
		  	int srcPixelFormat = 0;
	        
	        // create the texture ID for this texture
	        int textureId = TextureTool.createTextureID(); 
	 
	        BufferedImage bufferedImage = new BufferedImage(width, height, TEXTURETYPE);
	        Texture  frame = new Texture(bufferedImage);
			
			frame.setTextureId(textureId);
			
			//gl.glBindTexture(GL.GL_TEXTURE_2D, textureID); 
			frame.bind();
						       
			frame.initializeTextureCompatibleBuffer();
			frame.updateTextureCompatibleBuffer();
	        //System.out.println(realWidth);
	        //System.out.println(realHeight);
	        
	        if (bufferedImage.getColorModel().hasAlpha()) {
	            srcPixelFormat = GL11.GL_RGBA;
	        } else {
	            srcPixelFormat = GL11.GL_RGB;
	        }
	        
	        // convert that image into a byte buffer of texture data
	        //ByteBuffer textureBuffer = TextureLoader.convertImageData(bufferedImage,texture); 
	        int wrapMode = wrap ? GL11.GL_REPEAT : GL11.GL_CLAMP; 

			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrapMode); 
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrapMode); 
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, minFilter); 
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, magFilter);
	 
			
			GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 
	                          0, 
	                          dstPixelFormat, 
	                          frame.getTextureWidth(), 
							  frame.getTextureHeight(), 
	                          0, 
	                          srcPixelFormat, 
							  GL11.GL_UNSIGNED_BYTE, 
							  frame.getTextureCompatibleBuffer()); 

	        return frame; 
	}
	
   private static int createTextureID() 
   { 
	  IntBuffer newBuffer = BufferUtils.createIntBuffer(1);//ByteBuffer.allocateDirect(Integer.SIZE/8).asIntBuffer();
	  GL11.glGenTextures(newBuffer);
      return newBuffer.get(0); 
   } 
   
	
	public static void updateTexture(Texture frame){			
		frame.bind();
				
		GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, frame.getTextureWidth(), frame.getTextureHeight(), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, frame.getTextureCompatibleBuffer());
	}
	
	public static void updateTexture(Texture frame, BufferedImage image){
		frame.setImage(image);
		frame.updateTextureCompatibleBuffer();
		
		frame.bind();
				
		GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, frame.getTextureWidth(), frame.getTextureHeight(), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, frame.getTextureCompatibleBuffer());
	}
	
	public static int get2Fold2(int num) {
		return (int) Math.pow(2, Math.ceil(Math.log(num) * invln2));
	}
	
	/**
	 * Calculate how many bits wide a number is, i.e. position of highest 1 bit.
	 * Fully unraveled binary search method.
	 * 
	 * @return p where 2**p is first power of two >= n. e.g. binary 0001_0101 ->
	 *         5, 0xffffffff -> 32, 0 -> 0, 1 -> 1, 2 -> 2, 3 -> 2, 4 -> 3
	 *         (Vincent) Changed to return 2^n instead of n
	 * @author Dirk Bosmans Dirk.Bosmans@tijd.com
	 * @author Vincent Vollers
	 */
	static public final int get2Fold(int n) {
		if (n < 0)
			return 32;
		if (n > 0x00010000) {
			if (n > 0x01000000) {
				if (n > 0x10000000) {
					if (n > 0x40000000) {
						// if ( n > 0x7fffffff )
						// return 32
						// else
						// return 2147483648; is too much, so return -1
						return -1;
					} else {
						// !( n > 0x3fffffff )
						if (n > 0x20000000)
							return 1073741824;
						else
							return 536870912;
					}
				} else {
					// !( n > 0x0fffffff )
					if (n > 0x04000000) {
						if (n > 0x08000000)
							return 268435456;
						else
							return 134217728;
					} else {
						// !( n > 0x03ffffff )
						if (n > 0x02000000)
							return 67108864;
						else
							return 33554432;
					}
				}
			} else {
				// !( n > 0x00ffffff )
				if (n > 0x00100000) {
					if (n > 0x00400000) {
						if (n > 0x00800000)
							return 16777216;
						else
							return 8388608;
					} else {
						// !( n > 0x003fffff )
						if (n > 0x00200000)
							return 4194304;
						else
							return 2097152;
					}
				} else {
					// !( n > 0x000fffff )
					if (n > 0x00040000) {
						if (n > 0x00080000)
							return 1048576;
						else
							return 524288;
					} else {
						// !( n > 0x0003ffff )
						if (n > 0x00020000)
							return 262144;
						else
							return 131072;
					}
				}
			}
		} else {
			// !( n > 0x0000ffff )
			if (n > 0x00000100) {
				if (n > 0x00001000) {
					if (n > 0x00004000) {
						if (n > 0x00008000)
							return 65536;
						else
							return 32768;
					} else {
						// !( n > 0x00003fff )
						if (n > 0x00002000)
							return 16384;
						else
							return 8192;
					}
				} else {
					// !( n > 0x00000fff )
					if (n > 0x00000400) {
						if (n > 0x00000800)
							return 4096;
						else
							return 2048;
					} else {
						// !( n > 0x000003ff )
						if (n > 0x00000200)
							return 1024;
						else
							return 512;
					}
				}
			} else {
				// !( n > 0x000000ff )
				if (n > 0x00000010) {
					if (n > 0x00000040) {
						if (n > 0x00000080)
							return 256;
						else
							return 128;
					} else {
						// !( n > 0x0000003f )
						if (n > 0x00000020)
							return 64;
						else
							return 32;
					}
				} else {
					// !( n > 0x0000000f )
					if (n > 0x00000004) {
						if (n > 0x00000008)
							return 16;
						else
							return 8;
					} else {
						// !( n > 0x00000003 )
						if (n > 0x00000002)
							return 4;

						return n;
					}
				}
			}
		}
	} // end widthInBits
}
