package info.papdt.pano.processor;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.CRC32;

import info.papdt.pano.support.FastBitmapReader;
import info.papdt.pano.support.FastBitmapWriter;
import static info.papdt.pano.BuildConfig.DEBUG;
import static info.papdt.pano.support.Utility.*;
import static info.papdt.pano.support.BitmapUtility.*;

/*
 * To compose a list of screenshots into one
 * But preserving the areas where they are no difference
 *
 */
public class ScreenshotComposer
{
	private static final String TAG = ScreenshotComposer.class.getSimpleName();
	//private static final String SEPERATOR = ",";
	
	static class Region {
		int startLine;
		int endLine;
	}
	
	public static interface ProgressListener {
		void onAnalyzingImage(int i, int j, int total);
		void onComposingImage();
	}
	
	private static ScreenshotComposer sInstance;
	
	private String mOutDir = "/sdcard/Pictures/Panoramic";
	private float mThreshold = 0.08f;
	private int mStatusBarHeight = 40; // px
	private int mShadowHeight = 10; // px
	
	public static final ScreenshotComposer getInstance() {
		if (sInstance == null) {
			sInstance = new ScreenshotComposer();
		}
		
		return sInstance;
	}
	
	private ScreenshotComposer() {
		
	}
	
	public void setOutputDir(String opt) {
		mOutDir = opt;
		
		File out = new File(mOutDir);
		if (!out.exists()) {
			out.mkdirs();
			out.mkdir();
		}
	}
	
	public void setThreshold(float threshold) {
		if (threshold > 1 || threshold < 0)
			throw new IllegalArgumentException("illegal threshold");
		
		mThreshold = threshold;
	}
	
	public void setStatusBarHeight(int height) {
		mStatusBarHeight = height;
	}
	
	public void setShadowHeight(int height) {
		mShadowHeight = height;
		
		if (DEBUG) {
			Log.d(TAG, "shadow height " + mShadowHeight);
		}
	}
	
	public String compose(String[] images, ProgressListener listener) {
		File[] files = new File[images.length];
		
		for (int i = 0; i < images.length; i++) {
			files[i] = new File(images[i]);
			
			if (DEBUG) {
				Log.d(TAG, "Adding " + images[i]);
			}
			
			if (!files[i].exists()) {
				
				if (DEBUG) {
					Log.d(TAG, images[i] + " not exist");
				}
				
				return null;
			}
		}
		
		return compose(files, listener);
	}
	
	public String compose(File[] images, ProgressListener listener) {
		Region[] regions = new Region[images.length];
		
		FastBitmapReader currentBmp = null, nextBmp = null;
		int fullHeight = 0, fullWidth = 0;
		
		for (int i = 0; i < images.length - 1; i++) {
			long thisStartTime = System.currentTimeMillis();
			
			if (listener != null) {
				listener.onAnalyzingImage(i + 1, i + 2, images.length);
			}
			
			long decodeStartTime = System.currentTimeMillis();
			
			// Intented to use thresholding but failed. Help needed.
			if (currentBmp == null) {	
				currentBmp = new FastBitmapReader(BitmapFactory.decodeFile(images[i].getAbsolutePath()));
				fullWidth = currentBmp.getWidth();
			}
			
			nextBmp = new FastBitmapReader(BitmapFactory.decodeFile(images[i + 1].getAbsolutePath()));
			
			if (DEBUG) {
				Log.d(TAG, "decode time " + (System.currentTimeMillis() - decodeStartTime));
			}
			
			if (currentBmp.getHeight() != nextBmp.getHeight()) {
				
				if (DEBUG) {
					Log.d(TAG, "Height different");
				}
				
				return null;
			}
			
			// First, find the max different region of the two bitmaps
			
			long headStartTime = System.currentTimeMillis();
			
			// Go through from the first line
			int start = -1;
			for (int j = mStatusBarHeight + 1; j < currentBmp.getHeight(); j++) {
				if (compareLines(currentBmp, nextBmp, j)) {
					start = j;
					break;
				}
			}
			
			if (start == -1) {
				
				if (DEBUG) {
					Log.d(TAG, "start line not found");
				}
				
				return null;
			}
			
			// Go through from the last line
			int end = -1;
			for (int j = currentBmp.getHeight() - 1; j > start; j--) {
				if (compareLines(currentBmp, nextBmp, j)) {
					end = j;
					break;
				}
			}
			
			if (end == -1) {
				
				if (DEBUG) {
					Log.d(TAG, "End line not found");
				}
				
				return null;
			}
			
			if (DEBUG) {
				Log.d(TAG, "head match time " + (System.currentTimeMillis() - headStartTime));
			}
			
			if (i == 0) {
				Region region = new Region();
				region.startLine = start;
				region.endLine = end;
				regions[0] = region;
				fullHeight += currentBmp.getHeight();
			}
			
			// Second, find out the max common region
			
			// Generate a hash string of the bitmaps
			long regionStartTime = System.currentTimeMillis();
			Integer[] hashCurrent = buildHashOfRegion(currentBmp, start + mShadowHeight, end);
			Integer[] hashNext = buildHashOfRegion(nextBmp, start + mShadowHeight, end);
			
			if (DEBUG) {
				Log.d(TAG, "region build time " + (System.currentTimeMillis() - regionStartTime));
			}
			
			/*if (DEBUG) {
				Log.d(TAG, "hashCurrent = " + hashCurrent);
				Log.d(TAG, "hashNext = " + hashNext);
			}*/
			
			int length = end - start - mShadowHeight - 1;
			
			/*String matchSub = null;*/
			List<Long> matchSub = null;
			int matchLength = arrayHeadTailMatch(hashNext, hashCurrent, length, mThreshold);
			
			/*for (int j = length; j > 0; j--) {
				//List<Long> hashSubNext = buildHashOfSubregion(hashNext, j);
				//List<Long> hashSubCur = buildHashOfSubregionFromBottom(hashCurrent, j);
				
				//int result = arrayContainsEx(hashCurrent, hashSub, mThreshold);
				
			//	if (arrayCompareEx(hashNext, hashCurrent, 0, hashCurrent.size() - j - 1, j, mThreshold)) {
					//matchSub = hashSubNext;
			//		matchLength = j;
			//		break;
			//	}
				
				/*String hashSub = buildHashStringOfSubregion(hashNext, j);
				
				if (hashCurrent.contains(hashSub)) {
					matchSub = hashSub;
					matchLength = j;
					break;
				}*/
			//}
			
			/*if (matchSub == null) {
				return null;
			}*/
			
			//int index = arrayContainsEx(hashNext, matchSub, 0.0f);
			
			start = start/* + index*/ + matchLength + mShadowHeight + 1;
			
			if (DEBUG) {
				Log.d(TAG, "Different region between " + i + " and " + (i + 1));
				Log.d(TAG, start + "-" + end);
			}
			
			Region region = new Region();
			region.startLine = start;
			region.endLine = end;
			regions[i + 1] = region;
			fullHeight += end - start;
			
			if (DEBUG) {
				Log.d(TAG, i + " total time " + (System.currentTimeMillis() - thisStartTime));
			}
			
			currentBmp.recycle();
			currentBmp = nextBmp;
			nextBmp = null;
		}
		
		
		currentBmp.recycle();
		currentBmp = null;
		
		/*currentBmp = BitmapFactory.decodeFile(images[0].getAbsolutePath());
		nextBmp = BitmapFactory.decodeFile(images[1].getAbsolutePath());
		
		if (DEBUG) {
			Log.d(TAG, "hash1: " + getHashOfLine(currentBmp, 150));
			Log.d(TAG, "hash2: " + getHashOfLine(nextBmp, 150));
		}*/
		
		if (DEBUG) {
			Log.d(TAG, "fullHeight = " + fullHeight);
		}
		
		if (listener != null) {
			listener.onComposingImage();
		}
		
		//Bitmap out = Bitmap.createBitmap(fullWidth, fullHeight, Bitmap.Config.ARGB_8888);
		//Canvas canvas = new Canvas(out);
		FastBitmapWriter writer = new FastBitmapWriter(fullWidth, fullHeight);
		int totalHeight = 0;
		FastBitmapReader bmp;
		BitmapRegionDecoder decoder;
		for (int i = 0; i < images.length; i++) {
			//bmp = new FastBitmapReader(BitmapFactory.decodeFile(images[i].getAbsolutePath()));
			try {
				decoder = BitmapRegionDecoder.newInstance(images[i].getAbsolutePath(), false);
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
			
			Region region = regions[i];
			//int height = currentBmp.getHeight();
			
			//Rect rect = new Rect(0, 0, fullWidth, fullHeight);
			
			/*Rect src = new Rect();
			Rect dst = new Rect();
			src.left = 0;
			src.right = fullWidth;
			dst.left = 0;
			dst.right = fullWidth;*/
			if (i == 0) {
				/*src.top = 0;
				src.bottom = region.endLine;
				dst.top = 0;
				dst.bottom = src.bottom;*/
				
				bmp = new FastBitmapReader(decoder.decodeRegion(new Rect(0, 0, fullWidth, region.endLine), null));
				writer.writeBitmapRegion(bmp, 0, 0, region.endLine);
				bmp.recycle();
				
				//canvas.drawBitmap(bmp, src, dst, null);
				
				/*src.top = region.endLine;
				src.bottom = bmp.getHeight();
				dst.bottom = fullHeight;
				dst.top = dst.bottom - (src.bottom - src.top);*/
				
				int bmpHeight = decoder.getHeight();
				
				if (region.endLine < bmpHeight - 1) {
					// Dirty Fix: Ignore any NPE here.
					try {
						bmp = new FastBitmapReader(decoder.decodeRegion(new Rect(0, region.endLine, fullWidth, bmpHeight), null));
						writer.writeBitmapRegion(bmp, 0, fullHeight - (bmpHeight - region.endLine), bmpHeight - region.endLine);
						bmp.recycle();
					} catch (NullPointerException npe) {
						
					}
				}
				
				//canvas.drawBitmap(bmp, src, dst, null);
				
				totalHeight += region.endLine;
				
			} else {
				/*src.top = region.startLine;
				src.bottom = region.endLine;
				dst.top = totalHeight;
				dst.bottom = dst.top + (src.bottom - src.top);*/
				
				//canvas.drawBitmap(bmp, src, dst, null);
				
				bmp = new FastBitmapReader(decoder.decodeRegion(new Rect(0, region.startLine, fullWidth, region.endLine), null));
				writer.writeBitmapRegion(bmp, 0, totalHeight, region.endLine - region.startLine);
				bmp.recycle();
				
				totalHeight += (region.endLine - region.startLine);
			}
			
			decoder.recycle();
		}
		
		// Write
		File outFile = new File(mOutDir + "/Panoramic-" + System.currentTimeMillis() + ".png");
		if (outFile.exists()) {
			outFile.delete();
		}
		
		try {
			outFile.createNewFile();
		} catch (IOException e) {
			
		}
		
		Bitmap out = writer.getBitmap();
		writer.recycle();
		
		FileOutputStream opt = null;
		try {
			opt = new FileOutputStream(outFile);
		} catch (IOException e) {
			
		}
		
		if (opt != null) {
			out.compress(Bitmap.CompressFormat.PNG, 100, opt);
		}
		
		try {
			opt.close();
		} catch (IOException e) {
			
		}
		
		out.recycle();
		
		return outFile.getAbsolutePath();
	}
	
	// True = different
	private boolean compareLines(FastBitmapReader bmp1, FastBitmapReader bmp2, int line) {
		int diff = 0;
		for (int i = 0; i < bmp1.getWidth(); i++) {
			if (bmp1.getPixel(i, line) != bmp2.getPixel(i, line)) {
				diff++;
			}
		}
		
		return diff > bmp1.getWidth() / 10 * mThreshold;
	}
	
	private int getHashOfLine(FastBitmapReader bmp, int line) {
		int start = line * bmp.getWidth();
		int end = start + bmp.getWidth();
		int[] pixels = Arrays.copyOfRange(bmp.getPixels(), start, end);
		
		return Arrays.hashCode(pixels);
	}
	
	private Integer[] buildHashOfRegion(FastBitmapReader bmp, final int start, final int end) {
		//List<Long> list = new ArrayList<Long>();
		Integer[] array = new Integer[end - start];
		
		/*for (int i = start; i < end; i++) {
			array[i - start] = getHashOfLine(bmp, i);
		}*/
		
		return new MultiThreadTask<FastBitmapReader, Integer>(bmp, array) {
			@Override
			protected void doExecute(FastBitmapReader arg, int taskStart, int taskLength) {
				for (int i = 0; i < taskLength; i++) {
					int hash = getHashOfLine(arg, start + taskStart + i);
					
					setResult(taskStart + i, hash);
				}
			}
		}.execute(Runtime.getRuntime().availableProcessors()); // CPU cores as threads
		
		//return array;
	}
	
}
