package preprocessing;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.LinkedList;

import javax.imageio.ImageIO;

import parameters.io_arguments;
import regrouping.Box;
import tracking.Atributes;

public class Util {
	
	/*This function ...*/
	public static int Value (double value)
	{
		value = (value < 0) ? -value: value;
		value = (value > 1) ?     1 : value;
		return (int)( value * (255 - 100) + 100);
	}
	
	/*This function ...*/
	public static void Compute_Processing_Time (long start, String information) {
		
		long stop = System.currentTimeMillis();
		
		System.err.println("Processing time of " + information + " is : " + (stop-start)/1000 + " seconds." );
		
		//System.err.println("Processing time of " + information + " is : " + (stop-start) + " seconds." );
	}
			
	/*This function ...*/
	public static int[] getImageArray (BufferedImage image) {

	   int w = image.getWidth(null);
       int h = image.getHeight(null);

	   int array[] = new int[w*h];
//	   System.out.println("rgb : "+(image.getType() == BufferedImage.TYPE_INT_RGB)+" gray : "+(image.getType() == BufferedImage.TYPE_BYTE_GRAY)+" type : "+image.getType());
	   DataBuffer databuf = image.getData().getDataBuffer();
	  // DataBuffer databuf = image.getRaster().getDataBuffer();
	   
	   for(int i = 0 ; i < w*h; i++)
	   {
		   int pixel = 255 & databuf.getElem(i);
//		   int pixel = databuf.getElem(i);
		   array[i] = pixel;
	   }
	   return array;
	}
	
	public int[] getImageArray2 (BufferedImage image) {

		   int w = image.getWidth(null);
	       int h = image.getHeight(null);

		   int array[] = new int[w*h];
//		   System.out.println("rgb : "+(image.getType() == BufferedImage.TYPE_INT_RGB)+" gray : "+(image.getType() == BufferedImage.TYPE_BYTE_GRAY)+" type : "+image.getType());
		   //DataBuffer databuf = image.getData().getDataBuffer();
		   DataBuffer databuf = image.getRaster().getDataBuffer();
		   
		   for(int i = 0 ; i < w*h; i++)
		   {
			   int pixel = 255 & databuf.getElem(i);
//			   int pixel = databuf.getElem(i);
			   array[i] = pixel;
		   }
		   return array;
		}
	
	/*Copy an array of int to a new BufferedImage (colored)*/
	public static BufferedImage convertToImage (int w, int h, int[] array) {
		
		int size = w * h;
		
		int startX = 0; int startY = 0;
		
		int[] tmp = new int[size]; 
		
		for (int i = 0; i < size; i++) {tmp[i] = array[i];}
		
		BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		
		greyToColorImage (tmp, w, h);
		
		image.setRGB(startX, startY, w, h, tmp, 0, w);
		
		return image;
	}
	

	/*Converting an array of int to a BufferedImage (colored)*/
	public static BufferedImage getImage (int w, int h, int[] array) {
		
		int startX = 0; int startY = 0;
		
		BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		
		greyToColorImage (array, w, h);
		
		image.setRGB(startX, startY, w, h, array, 0, w);
		
		return image;
	}
	
	
    
	/*This function ...*/
	public static BufferedImage getSubImage (BufferedImage image, int xmin, int ymin, int xmax, int ymax)
	{
		if (xmin < 0) { xmin = 0; }
		if (ymin < 0) { ymin = 0; }
		if (xmin >= image.getWidth()) { xmin = image.getWidth()-1; System.err.printf("1: xmin : %d, xmax : %d, width : %d\n", xmin, xmax, image.getWidth());}
		if (ymin >= image.getHeight()) { ymin = image.getHeight()-1; System.err.printf("2: xmin : %d, xmax : %d, width : %d\n", xmin, xmax, image.getWidth());}
		if (xmax >= image.getWidth()) { xmax = image.getWidth()-1; System.err.printf("3: xmin : %d, xmax : %d, width : %d\n", xmin, xmax, image.getWidth());}
		if (ymax >= image.getHeight()) { ymax = image.getHeight()-1; System.err.printf("4: xmin : %d, xmax : %d, width : %d\n", xmin, xmax, image.getWidth());}
		//if ((xmax-xmin+1) >= image.getWidth() ) { xmax = image.getWidth()-xmin-1; System.err.printf("3: xmin : %d, xmax : %d, width : %d\n", xmin, xmax, image.getWidth());}
		//if ((ymax-ymin+1) >= image.getHeight() ) { ymax = image.getHeight()-ymin-1; System.err.printf("4: ymin : %d, ymax : %d, height : %d\n", ymin, ymax, image.getHeight());}
		System.err.printf("5: ymin : %d, ymax : %d, height : %d\n", ymin, ymax, image.getHeight());
		return image.getSubimage(xmin, ymin, xmax-xmin, ymax-ymin); 
	}
	
	/*This function ...*/
	public static int getPixelFromRGB (int R, int G, int B) {
		return ( B | (G << 8) | (R << 16) | (255 << 24 ));
	}
	
	/*This function ...*/	
	public static int[] getColorImageChannels (BufferedImage image) {

		int w = image.getWidth(null);
		int h = image.getHeight(null);
		int c = 3; /*image channels corresponding to R,G,B*/

		int[] array = new int [c*w*h];

		for (int y = 0, p = 0; y < h; y++) {
			for (int x = 0; x < w; x++, p += c) {
				
				int pixel = image.getRGB(x, y);
				
				int R = (pixel >> 16) & 255;
				int G = (pixel >> 8) & 255;
				int B = (pixel & 255);
				
				array[p+0] = R;
				array[p+1] = G;
				array[p+2] = B;
			}
		}
		return array;
	}
	
	public static BufferedImage[] Build_Pyramid (BufferedImage image, int number_of_levels) {
	
		BufferedImage[] image_pyramid = new BufferedImage[number_of_levels];
		
		image_pyramid[0] = image.getSubimage(0, 0, image.getWidth(), image.getHeight());//Rescale_Image (image, 1.0);
		
		for (int level = 1; level < number_of_levels; level++) {
			image_pyramid[level] = imageScale (image_pyramid[level-1], 0.5);
			//image_pyramid[level] = imageScale (image, Math.pow(0.5, level));
		}
		
		return image_pyramid;
	}
	
	
	/*This function ...*/
	public static BufferedImage colorToGreyImage (BufferedImage image) {

		if(image.getType() == BufferedImage.TYPE_BYTE_GRAY)
			return image;
		
	   int w = image.getWidth(null);
	   int h = image.getHeight(null);

	   BufferedImage outimg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

	   for (int y = 0; y < h; y++) {
		  for (int x = 0; x < w; x++) {
		     int pixel = image.getRGB(x, y);
		     int R = (pixel >> 16) & 255;
		     int G = (pixel >> 8) & 255;
		     int B = (pixel & 255);
		     outimg.setRGB(x, y, Math.round((float)(R * 0.2989 + G * 0.5870 + B * 0.1140)));
		  }
	   }
	   return outimg;
	}
	
	/*This function ...*/
	public static void greyToColorImage (int[] array, int w, int h) {

		for (int i = 0; i < (w*h); i++) {
			
			int pixel = array[i];

			int A = (pixel << 24);
			int R = (pixel << 16);
			int G = (pixel << 8);
			int B = (pixel);

			array[i] =  ( R | G | B | A );
		}
	}
		
	/*This function writes a PPM file*/
	public static void writePGMImage (BufferedImage image, String name) {

		File f = new File(name);

		try {
		    
			/*Opening output stream*/
			DataOutputStream stream = new DataOutputStream(new BufferedOutputStream (new FileOutputStream(f)));
		    
			/* Getting image dimensions */
		    int w = image.getWidth(null);
		    int h = image.getHeight(null);
			
		    /* Writing PPM header */
			stream.write('P');
			stream.write('5');
			stream.write('\n');
			
			stream.write(Integer.toString(w).getBytes());
			stream.write(' ');
			stream.write(Integer.toString(h).getBytes());
			stream.write('\n');
			
			stream.write(Integer.toString(255).getBytes());
			stream.write('\n');

			/* Write each row of pixels */
			int max = 0, min = 255;
			for (int y = 0; y < h; y++) {
			   for (int x = 0; x < w; x++) {
			      int pixel = image.getRGB(x, y);
			      int v = (pixel & 0xff);
			      if (v < min) {min = v;}
			      if (v > max) {max = v;}
			      stream.write(v);
			   }
			}
			stream.flush();
		    
			/*Closing the output stream*/
			stream.close();
		}
		catch (IOException e) {
		    System.err.println("Can't record the file : " + name);
		}
	}
	
	public static void writeArrayToPGMImage (int w, int h, int[] array, String name) {
		
		BufferedImage image = convertToImage (w, h, array);

		Util.writePGMImage(image, name);
	}
		
	/*This function ...*/
	public static void writeDoubletoPGM (double[] data, int w, int h, double vmin, double vmax, String name) 
	{
		BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

		int[] array = new int [w*h];

		for (int n = 0; n < (w*h); n++) {
			array[n] = (int)((data[n]-vmin)/(vmax-vmin)*255 + 0.5);
			if (array[n] < 0) {array[n] = 0;}
			if (array[n] > 255) {array[n] = 255;}
		}
		
		int startX = 0; int startY = 0;
		
		Util.greyToColorImage (array, w, h);
		
		image.setRGB (startX, startY, w, h, array, 0, w);

		Util.writePGMImage(image, "HOG." + name + ".pgm");
	}
	
	public static void writeIntegerPGM (int[] data, int w, int h, double vmin, double vmax, String name)  
	{
		BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

		int[] array = new int [w*h];

		for (int n = 0; n < (w*h); n++) {
			array[n] = (int)((data[n]-vmin)/(vmax-vmin)*255 + 0.5);
			if (array[n] < 0) {array[n] = 0;}
			if (array[n] > 255) {array[n] = 255;}
		}
		
		int startX = 0; int startY = 0;
		
		Util.greyToColorImage (array, w, h);
		
		image.setRGB (startX, startY, w, h, array, 0, w);

		Util.writePGMImage(image, "HOG." + name + ".pgm");
	}
		
	/*This function ...*/
	public static BufferedImage imageScale (BufferedImage image, double scaleValue) {
        AffineTransform tx = new AffineTransform();
        tx.scale(scaleValue, scaleValue);
        AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC);
        return op.filter(image, op.createCompatibleDestImage(image, null));
    }
	
	/*This function ...*/
    public static BufferedImage imageResize (BufferedImage image, int new_height, int w_mod) {
            int height = image.getHeight();
            int width = image.getWidth();
            double y_scale_factor = (double)(new_height)/(double)(height);
            int new_width = (int)(Math.floor(width*y_scale_factor/w_mod + 0.5))*w_mod;
            if (new_width == 0) {
                    new_width = 1;
            }
            assert((new_width % w_mod) == 0);
            double x_scale_factor = (double)(new_width)/(double)(width);
            AffineTransform tx = new AffineTransform();
            tx.scale(x_scale_factor, y_scale_factor);
            AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC);
            return op.filter(image, op.createCompatibleDestImage(image, null));
    }
	
	/*for(int y = 0; y < outputImage.getHeight(); y++) {
	    for(int x = 0; x < outputImage.getWidth(); x++) {
	        //the point in the original image that maps to (x,y)
	        double p = x/scaleX;
	        double q = y/scaleY;

	        //find the four points that surround (p,q)
	        ...

	        //do the bilinear interpolation
	        ...
	        
	        //store the results in (x,y)
	        int color = (255<<24)|(xfinalRed<<16)|(xfinalGreen<<8)|(xfinalBlue);
	        outputImage.setRGB(x, y, color);
	    }
	}*/
	
	/*public static BufferedImage imageResize (BufferedImage image, int new_height) 
	{ 
		int height = image.getHeight();
		int width = image.getWidth();
		double y_scale_factor = (double)(new_height)/(double)(height);
		int new_width = (int)(Math.floor(width*y_scale_factor + 0.5));
		double x_scale_factor = (double)(new_width)/(double)(width);

	    int type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType();
	    BufferedImage resized = new BufferedImage(new_width, new_height, type);
	    Graphics2D g = resized.createGraphics();
	    g.setComposite(AlphaComposite.Src);
	    g.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
	    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
	    g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);

	    g.scale(x_scale_factor,y_scale_factor);
	    g.drawRenderedImage(image, null);
	    g.dispose();
	    return resized; 
	}*/
	
	/*This function ...*/
	public static BufferedImage Rescale_Image (BufferedImage image, double scaleValue) {
        AffineTransform tx = new AffineTransform();
        tx.scale(scaleValue, scaleValue);
        AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC);
        BufferedImage tmp = op.filter(image, op.createCompatibleDestImage(image, null));
        return op.createCompatibleDestImage(tmp, tmp.getColorModel());
    }
	
	/*This function draws a white line in an image from (x1,y1) to (x2,y2) */
	public static void drawLine (BufferedImage image, int x1, int y1, int x2, int y2)
    { 
    	Graphics2D g = image.createGraphics();
	   	g.setColor(Color.WHITE);
    	g.drawLine(x1, y1, x2, y2);
    	g.dispose();
    }
	
	public static void writePolartoPNG (double[] norm, double[] angle, int w, int h, double nmax, double amin, double amax, String name) 
	{
		BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		
		for (int x = 0; x < w; x++) {
			for (int y = 0; y < h; y++) {
				int pos = y * w + x;
				double nv = norm[pos]/nmax;
				double av = (angle[pos]-amin)/(amax - amin); 
				float hue = (float) Math.max(0.0,Math.min(1.0, av));
				float val = (float) Math.max(0.0,Math.min(1.0, nv));
				float sat = (float) 1.0;
				image.setRGB(x, y, Color. HSBtoRGB(hue, sat, val));
			}
		}
		
		try {
			ImageIO.write(image, "png", new File("HOG."+name+".png"));
		}
		catch (Exception e) { 
				System.err.printf("cannot write image %s\n", name);
		}
		
		
	}

	
	public static void addTransparency (BufferedImage source, BufferedImage image) 
	{
		double factor = 0.7;
		int w = image.getWidth();		
		int h = image.getHeight();		
		for (int x = 0; x < w; x++) {
			for (int y = 0; y < h; y++) {
				int pixel = source.getRGB(x, y);
				/*int A = (pixel << 24);
				int R = (int)((1 - factor)*(pixel << 16) + factor * ((pixel << 16) * 0xFFFFFF));
				int G = (int)((1 - factor)*(pixel << 8) + factor * ((pixel << 8) * 0xFFFFFF));
				int B = (int)((1 - factor)*(pixel) + factor * (pixel * 0xFFFFFF));*/
				
				//int A = (pixel << 24);
				/*int R = (pixel << 16);
				int G = (pixel << 8);
				int B = (pixel);
				pixel =  ( R | G | B );
				image.setRGB(x, y, pixel);*/
			    int R = (pixel >> 16) & 255;
			    int G = (pixel >> 8) & 255;
			    int B = (pixel & 255);
			    
			    //R = (int) Math.round( ( (1.0-factor) * 0xFFFFFF ) + (factor * R) );
			    
			    image.setRGB(x, y, Math.round((float)(R * 0.2989 + G * 0.5870 + B * 0.1140)));
			}
		}		
	}
	
	public static String make_name (String prefix, boolean inversion, int scale, String suffix) {
		String inv_char = (inversion?"d":"i");
		return prefix + "_" + inv_char + "_" + String.format("%02d", scale) + suffix;
	}
	
	public static void write_boxes (BufferedImage image, LinkedList<regrouping.Box> list, String name) {

		for (int i = 0; i < list.size(); i++) {

			Graphics2D g = image.createGraphics();

			g.setStroke(new BasicStroke(2.0f));
			
			Box A = list.get (i);
			
			if(A.getValid() < 0) {
				g.setColor(Color.yellow);
			}
			else 
				g.setColor(Color.red);

			int x = (int)(A.getXMin()/2); 
			
			int y = (int)(A.getYMin()/2);
			
			int w = (A.getXMax()-A.getXMin())/2;//A.getW(); 
			
			int h = (A.getYMax()-A.getYMin())/2;//A.getW();

			g.drawRect(x, y, w, h);

			g.dispose();
		}
		try {
			ImageIO.write(image, "png", new File(name));
		}
		catch (Exception e) { 
				System.err.printf("cannot write image\n");
		}

	}
	
	public static void write_boxes (BufferedImage image, LinkedList<regrouping.Box> list, io_arguments io_parameters, String optional, String option, int frame) {

		for (int i = 0; i < list.size(); i++) {

			Graphics2D g = image.createGraphics();

			g.setStroke(new BasicStroke(2.0f));
			
			Box A = list.get (i);
			
			if(A.getValid() < 0) {
				g.setColor(Color.yellow);
			}
			else 
				g.setColor(Color.red);

			int x = (int)(A.getXMin()/2); 
			
			int y = (int)(A.getYMin()/2);
			
			int w = (A.getXMax()-A.getXMin())/2;//A.getW(); 
			
			int h = (A.getYMax()-A.getYMin())/2;//A.getW();

			g.drawRect(x, y, w, h);

			g.dispose();
		}
		try {
			if (option.equals("d")) {
				ImageIO.write(image, "png", new File(io_parameters.get_out_image() + ".list"+optional+".png"));
			}
			else if (option.equals("l")) {
				ImageIO.write(image, "png", new File(io_parameters.get_out_path() + optional + ".png"));
			}
			else if (option.equals("t")) {
				ImageIO.write(image, "png", new File(io_parameters.get_out_path() + String.format("%05d", frame) + "/detection"+optional+".png"));
			}
		}
		catch (Exception e) { 
			if (option.equals("d")) {
				System.err.printf("cannot write image %s\n", io_parameters.get_out_image());
			}
			else if (option.equals("l")) {
				System.err.printf("cannot write image %s", io_parameters.get_out_path() + optional + ".list.png");
			}
			else if (option.equals("t")) {
				System.err.printf("cannot write image %s\n", io_parameters.get_out_path() + String.format("%05d", frame) + "/detection"+optional+".png");
			}
		}

	}
	
	public static void write_set_regions (BufferedImage image, ArrayList<Atributes> set, io_arguments io_parameters, String optional, String option, int frame) {

		for (int i = 0; i < set.size(); i++) {

			Graphics2D g = image.createGraphics();

			g.setStroke(new BasicStroke(2.0f));

			g.setColor(Color.yellow);

			Atributes A = set.get (i);

			int x = (int)(A.get_x()); 
			
			int y = (int)(A.get_y());
			
			int w = A.get_width(); 
			
			int h = A.get_height();

			g.drawRect(x, y, w, h);

			g.dispose();
		}
		try {
			if (option.equals("d")) {
				//ImageIO.write(image, "png", new File(io_parameters.get_out_image() + ".list"+optional+".png"));
				ImageIO.write(image, "png", new File(io_parameters.get_out_image()));
			}
			else if (option.equals("l")) {
				ImageIO.write(image, "png", new File(io_parameters.get_out_path() + optional + ".png"));
			}
			else if (option.equals("t")) {
				ImageIO.write(image, "png", new File(io_parameters.get_out_path() + String.format("%05d", frame) + "/detection"+optional+".png"));
			}
		}
		catch (Exception e) { 
			if (option.equals("d")) {
				System.err.printf("cannot write image %s\n", io_parameters.get_out_image());
			}
			else if (option.equals("l")) {
				System.err.printf("cannot write image %s", io_parameters.get_out_path() + optional + ".list.png");
			}
			else if (option.equals("t")) {
				System.err.printf("cannot write image %s\n", io_parameters.get_out_path() + String.format("%05d", frame) + "/detection"+optional+".png");
			}
		}

	}
	
	public static String Get_Name (LineNumberReader file)
	{
		Parser parser = new Parser();

		String[] s = parser.getTokens (file, "\n");

		if (s != null)
			return s[0];
		else 
			return null;
	}

	public static BufferedImage Get_Image (String s)
	{
		BufferedImage image = null;

		if (s == null)
			return null;

		try {
			image = ImageIO.read (new File(s));
    	} catch (IOException e) {
			System.err.println("Failed to open image : " + s);
		}
		return image;
	}
	
	/*public static BufferedImage Get_Image (LineNumberReader file)
	{
		BufferedImage image = null;

		String s = Get_Name (file);

		if (s == null)
			return null;

		try {
			image = ImageIO.read (new File(s));
    	} catch (IOException e) {
			System.err.println("Failed to open image : " + s);
		}
		return image;
	}*/
	
	public static BufferedImage Get_Image (LineNumberReader file, String[] s)
	{
		BufferedImage image = null;

		s[0] = Get_Name (file);

		if (s[0] == null)
			return null;

		try {
			image = ImageIO.read (new File(s[0]));
		} catch (IOException e) {
			System.err.println("Failed to open image : " + s);
		}
		return image;
	}

	
}