package dataBase.subPicture;

import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;

import mosaik.MosaikConstants;

import dataBase.search.distance.AbstractDistance;

import util.Tools;

public class SubPicture implements Serializable {
	
	private static final long serialVersionUID = 8L;
	/* predefined, important for data base and search */
	public static final int partsX=MosaikConstants.partsX, partsY=MosaikConstants.partsY;
	
	/** used temporary at search **/
	transient public float[] distance_ToSearchPic;
	transient public SubPicture[] distance_searchPic;
	
	//private BufferedImage image;
	//private DataBuffer dataBuffer;
	//private boolean hasAlpha;
	public File imageFile;
	public PixelByte[] pixels = new PixelByte[partsY*partsX];
	public PixelFloat avg;
	
	/**
	 * Constructor: Full, generates SubPicture information
	 * @param image - image with data
	 * @param offsetX - offset (start offset)
	 * @param offsetY - offset (start offset)
	 * @param limitX - limit (width)
	 * @param limitY - limit (height)
	 */
	public SubPicture(File imageFile,BufferedImage image, int offsetX,int offsetY, int limitX,int limitY){
		
		assert offsetX>0 && offsetY>0 && limitX>0 && limitY>0;
		assert (limitX-offsetX)>partsX && (limitY-offsetY)>partsY;
		
		this.imageFile = imageFile;
		initPixels(offsetX,offsetY, limitX,limitY,  image);
		initAVG();
	}
	/**
	 * Constructor: simple
	 * @param image
	 */
	public SubPicture(File imageFile, BufferedImage image){
		this(imageFile,image, 0,0, image.getWidth(),image.getHeight());
	}
	
	/**
	 * @param all - see above
	 */
	private void initPixels(float offX, float offY, int limitX, int limitY,
			BufferedImage image){
		
		//Out.pl("> Init subpicture for X="+offX+"-"+w+", Y="+offY+"-"+h);
		WritableRaster raster = image.getRaster();
		int[] pixel;
		if(image.getColorModel().hasAlpha())	// needed to avoid exceptions
			pixel = new int[4];					// -
		else									// -
			pixel = new int[3];					// -
		
		float partLengthX = limitX/(float)partsX;	// single part length for pixel calculation
		float partLengthY = limitY/(float)partsY;	// -
		
		if(partLengthX >= 1f && partLengthY >= 1f){	// the usual algorithm:
			
			offX += 0.5f;							// for better rounding
			offY += 0.5f;							// -
			
			for(int y=0; y<partsY; y++){
				for(int x=0; x<partsX; x++){
					
					int startX = (int) (offX + x*partLengthX);
					int stopX  = (int) (offX + (x+1)*partLengthX);
					
					int startY = (int) (offY + y*partLengthY);
					int stopY  = (int) (offY + (y+1)*partLengthY);
					
					pixels[y*partsX+x] = getSingleSubPicture(startX, startY, stopX, stopY,  pixel, raster);
				}
			}
			
		}
		else{										// algorithm for small pictures:
			
			limitX -= 1;
			limitY -= 1;
			
			partLengthX = limitX/(float)partsX;	// single part length for pixel calculation
			partLengthY = limitY/(float)partsY;	// -
			
			for(int y=0; y<partsY; y++){
				for(int x=0; x<partsX; x++){
					
					int startX = (int) (offX + x*partLengthX);
					int stopX  = (int) (offX + (x+1)*partLengthX) +2;
//					if(startX==stopX)
//						stopX++;
					
					int startY = (int) (offY + y*partLengthY);
					int stopY  = (int) (offY + (y+1)*partLengthY) +2;
//					if(startY==stopY)
//						stopY++;
					
					pixels[y*partsX+x] = getSingleSubPicture(startX, startY, stopX, stopY,  pixel, raster);
				}
			}
		}
		

	}
	
	/**
	 * Init average value.
	 */
	private void initAVG(){
		
		int pixelCount = 0;				// Pixel counter for average
		float avgR     = 0;				// Average RGB
		float avgG     = 0;				// -
		float avgB     = 0;				// -
		
		for(int y=0; y<partsY; y++){
			for(int x=0; x<partsX; x++){
				
				PixelByte p = pixels[y*partsX+x];
				
				avgR += (p.r1&0xff);			// sum up pixel data
				avgG += (p.g1&0xff);			// -
				avgB += (p.b1&0xff);			// -
				pixelCount++;							// count number of pixel
			}
		}
		
		avgR /= pixelCount;
		avgG /= pixelCount;
		avgB /= pixelCount;
		PixelFloat avg = new PixelFloat(avgR, avgG, avgB);

		assert avg.test();
		this.avg = avg;
	}
	
	/**
	 * Calculate Distance internally
	 * @param distanceFkt
	 * @param searchPic
	 */
	public void setDistanceToSearchPic(AbstractDistance distanceFkt, SubPicture searchPic, int threadID){
		
		SubPicture lastSearchPic = distance_searchPic[threadID];
			
//			distance_ToSearchPic = new float[processors];
//		distance_searchPic = new SubPicture[processors];
		
		if(lastSearchPic != searchPic){
			float tmp = distanceFkt.getDistance(this, searchPic);
			//Out.pl(" >>> Setting distance at '"+imageFile.getName()+"' from "+distance_ToSearchPic+" to "+tmp);
			distance_ToSearchPic[threadID] = tmp;
		}
	}
	
	/**
	 * 
	 * @param startX - offset (start offset)
	 * @param startY - offset (start offset)
	 * @param stopX - limit (width)
	 * @param stopY - limit (height)
	 * @param pixel - tmp array for data, length is 3 or 4
	 * @param raster - raster with pixel data
	 * @return Pixel object for this picture section.
	 */
	private PixelByte getSingleSubPicture(int startX, int startY, int stopX, int stopY,
			int[] pixel, WritableRaster raster){
		
		//Out.pl(" >> Making subpicture for X="+startX+"-"+stopX+", Y="+startY+"-"+stopY);
		
		int pixelCount = 0;				// Pixel counter for average
		float avgR     = 0;				// Average RGB
		float avgG     = 0;				// -
		float avgB     = 0;				// -
		
		for(int y=startY; y<stopY; y++){
			for(int x=startX; x<stopX; x++){
				
				if(x>=raster.getHeight() || y>=raster.getWidth()){
					x--; y--;
				}
					
				
				raster.getPixel(x, y, pixel);			// load picture pixel data
				avgR += pixel[0];						// sum up pixel data
				avgG += pixel[1];						// -
				avgB += pixel[2];						// -
				pixelCount++;							// count number of pixel
			}
		}
		
		pixel[0] = (int) (avgR/pixelCount+0.5f);		// get absolute value, rounded
		pixel[1] = (int) (avgG/pixelCount+0.5f);		// -
		pixel[2] = (int) (avgB/pixelCount+0.5f);		// -
		PixelByte tmp = new PixelByte(pixel);			// return new pixel information

		return tmp;
	}
	
	@Override
	public String toString(){
		
		StringBuffer s = new StringBuffer(120);
		s.append("'"+imageFile.getName()+"' avg="+avg+"");
		
//		for(int y=0; y<partsY; y++){
//			for(int x=0; x<partsX; x++){
//				s.append( pixels[y][x].toString() );
//				if(x+1<partsX)
//					s.append( ", " );
//			}
//			if(y+1<partsY)
//				s.append( "; " );
//		}
		
		return s.toString();
	}
	
	/**
	 * Manual writing. (less size)
	 * @param out
	 * @throws IOException
	 */
	private void writeObject(java.io.ObjectOutputStream out) throws IOException {
		
		out.writeObject(imageFile);
		
		int size = partsY*partsX;
		for(int i=0; i<size; i++){
			pixels[i].writeObjectNew(out);
		}
		avg.writeObjectNew(out);
	}
	/**
	 * Manual reading. (less size)
	 * @param in
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
		imageFile = (File) in.readObject();
		
		pixels = new PixelByte[partsY*partsX];
		int size = partsY*partsX;
		for(int i=0; i<size; i++){
			pixels[i] = new PixelByte().readObjectNew(in);
		}
		
		avg = new PixelFloat().readObjectNew(in);
		initStuff();
	}
	
	private void initStuff(){
		int processors = Tools.getAvailableProcessors();
		distance_ToSearchPic = new float[processors];
		distance_searchPic = new SubPicture[processors];
	}
}
