package sliz.elektromery;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import org.opencv.objdetect.CascadeClassifier;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.Camera;
//import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import sliz.elektromery.R;

public class CamActivity extends Activity implements CvCameraViewListener2 {
	private static final String TAG = "CamActivity";

	private long timeLast = 0;
	
//	private static final int VIEW_MODE_DET_FRAME = 0;
//	private static final int VIEW_MODE_HAAR_FRAME = 1;

	// Default view mode
//	private int mViewMode = VIEW_MODE_DET_FRAME;
	
	// Matrixes to hold images
	private Mat mRgba;
	private Mat mGray;
	private Mat mIntermediateMat;

	// Menu variables
	private MenuItem mItemDetFrame;
	private MenuItem mItemHaarFrame;
	private List<Camera.Size> mResolutionList;
	private MenuItem[] mResolutionMenuItems;
	private SubMenu mResolutionMenu;
	
	// Cascade classifier
    private File                   mCascadeFile;
    private CascadeClassifier      mJavaDetector;
    
    private static final Scalar    FACE_RECT_COLOR     = new Scalar(0, 255, 0, 255);

	private CustomCameraView mOpenCvCameraView;
	
	// Frame mean list
	List<FrameParams> mListFrames;
	int mNearKoef = 15;
	int mFramesIter = 15;
	
	// Camere resolution
	Size mCamRes = new Size();
	
	// Messages
	private Timer myTimer;
	TextView textViewMessage;
	String message;
	
	String mFileName;

	private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
		@Override
		public void onManagerConnected(int status) {
			switch (status) {
			case LoaderCallbackInterface.SUCCESS: {
				Log.i(TAG, "OpenCV loaded successfully");

				// Load native library after(!) OpenCV initialization
				System.loadLibrary("native_part");
				
				// Load cascade classifier
				try {
                    // load cascade file from application resources
                    InputStream is = getResources().openRawResource(R.raw.elec_frame_haar30_10);
                    File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
                    mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
                    FileOutputStream os = new FileOutputStream(mCascadeFile);

                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = is.read(buffer)) != -1) {
                        os.write(buffer, 0, bytesRead);
                    }
                    is.close();
                    os.close();

                    mJavaDetector = new CascadeClassifier(mCascadeFile.getAbsolutePath());
                    if (mJavaDetector.empty()) {
                        Log.e(TAG, "Failed to load cascade classifier");
                        mJavaDetector = null;
                    } else
                        Log.i(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath());


                    cascadeDir.delete();

                } catch (IOException e) {
                    e.printStackTrace();
                    Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
                }

				mOpenCvCameraView.enableView();
			}
				break;
			default: {
				super.onManagerConnected(status);
			}
				break;
			}
		}
	};

	public CamActivity() {
		Log.i(TAG, "Instantiated new " + this.getClass());
	}

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		Log.i(TAG, "called onCreate");
		super.onCreate(savedInstanceState);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		
		//Remove title bar
		this.requestWindowFeature(Window.FEATURE_NO_TITLE);
		//Remove notification bar
		this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

		// Init frames
		mListFrames = new ArrayList<FrameParams>();
		
		setContentView(R.layout.camera_view);
		
		// Text init		
		textViewMessage = (TextView) findViewById(R.id.textViewMessage);
		textViewMessage.setTextColor(Color.GREEN);
		message = new String();
		
		scrCamViewEvents();

	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			backToMenu();
		}

		// Pass other events along their way up the chain
		return super.onKeyDown(keyCode, event);
	}

	private void backToMenu() {
		Intent intnscnd = new Intent(CamActivity.this, MenuActivity.class);
		startActivity(intnscnd);
		CamActivity.this.finish();
	}

	private void scrCamViewEvents() {
		mOpenCvCameraView = (CustomCameraView) findViewById(R.id.my_surface_view);
		mOpenCvCameraView.setCvCameraViewListener(this);

		Button btnBack = (Button) findViewById(R.id.btn_cam_back);
		btnBack.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				backToMenu();
			}
		});

		mOpenCvCameraView.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				// Focus camera

			}
		});
		
		myTimer = new Timer();
	    myTimer.schedule(new TimerTask() {          
	        @Override
	        public void run() {
	            TimerMethod();
	        }

	    }, 0, 1000);
	
	}
	
	private void TimerMethod()
	{
	    this.runOnUiThread(Timer_Tick);
	}


	private Runnable Timer_Tick = new Runnable() {
	    public void run() {

	    	textViewMessage.setText(message);
	    	
    		if(message.equals("Pořizuji foto, nehýbat!"))
    		{
    			
    			message = "Zpracovávám";
    			mFileName = takePicture("Frame");
    		}	
			if(message.equals("Zpracovávám"))
    		{
    			Drawable d = Drawable.createFromPath(mFileName); 
    			if(d == null)return;
    			Bitmap bitmapImg = ((BitmapDrawable) d).getBitmap();
    			
    			// Find best frame
    			int maxLife = 0;
    			int maxIndx = 0;
    			for(int i = 0 ; i < mListFrames.size() ; i++)
    			{
    				if(mListFrames.get(i).lifeCyc > maxLife)
    				{
    					maxLife = mListFrames.get(i).lifeCyc;
    					maxIndx = i;
    				}
    			}
    			
    			Rect bestFrame = mListFrames.get(maxIndx).rect;
    			
    			// Adjust frame
    			double scaleX = (double)bitmapImg.getWidth()/(double)mCamRes.width;
    			double scaleY = (double)bitmapImg.getHeight()/(double)mCamRes.height;
    			
				bestFrame.x *= scaleX;
				bestFrame.width *= scaleX;				
				bestFrame.y *= scaleY;
				bestFrame.height *= scaleY;
				
				MatOfRect rects = new MatOfRect(bestFrame);
				
				// Cut frame
				Mat img = new Mat();
				Utils.bitmapToMat(bitmapImg, img);
				AdjFrame(img.getNativeObjAddr(), rects.getNativeObjAddr());
				
				Bitmap bitmapFrame = Bitmap.createBitmap(img.cols(), img.rows(),Bitmap.Config.ARGB_8888);
				Utils.matToBitmap(img, bitmapFrame);
    			
				// Save frame
		        // Create imageDir
		        File mypath=new File(mFileName);

		        FileOutputStream fos = null;
		        try {           

		            fos = new FileOutputStream(mypath);

		       // Use the compress method on the BitMap object to write image to the OutputStream
		            bitmapFrame.compress(Bitmap.CompressFormat.PNG, 100, fos);
		            fos.close();
		        } catch (Exception e) {
		            e.printStackTrace();
		        }
				
    			mListFrames.clear();
    			message = "Hotovo!";
    			
    			// Start PicActivity
    			PicActivity.bFromMenu = false;
    			PicActivity.selectedImagePath = mFileName;
				Intent intnscnd = new Intent(CamActivity.this, PicActivity.class);
	            startActivity(intnscnd);
	            CamActivity.this.finish();
    		}

	    }
	};

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		Log.i(TAG, "called onCreateOptionsMenu");

		mResolutionMenu = menu.addSubMenu("Resolution");
		mResolutionList = mOpenCvCameraView.getResolutionList();
		mResolutionMenuItems = new MenuItem[mResolutionList.size()];

		ListIterator<Camera.Size> resolutionItr = mResolutionList.listIterator();
		int idx = 0;
		while (resolutionItr.hasNext()) {
			Camera.Size element = resolutionItr.next();
			mResolutionMenuItems[idx] = mResolutionMenu.add(1, idx, Menu.NONE,
					Integer.valueOf(element.width).toString() + "x"
							+ Integer.valueOf(element.height).toString());
			idx++;
		}

		mItemDetFrame = menu.add("Deterministic frame classifier");
		mItemHaarFrame = menu.add("Haar cascade classifier");

		return true;
	}

	@Override
	public void onPause() {
		super.onPause();
		if (mOpenCvCameraView != null)
			mOpenCvCameraView.disableView();
	}

	@Override
	public void onResume() {
		super.onResume();
		OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this,
				mLoaderCallback);
	}

	public void onDestroy() {
		super.onDestroy();
		if (mOpenCvCameraView != null)
			mOpenCvCameraView.disableView();
	}

	public void onCameraViewStarted(int width, int height) {
		mRgba = new Mat(height, width, CvType.CV_8UC4);
		mIntermediateMat = new Mat(height, width, CvType.CV_8UC4);
		mGray = new Mat(height, width, CvType.CV_8UC1);
	}

	public void onCameraViewStopped() {
		mRgba.release();
		mGray.release();
		mIntermediateMat.release();
	}

	public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
		
		// Actualize camera resolution
		mCamRes.width = inputFrame.gray().cols();
		mCamRes.height = inputFrame.gray().rows();
		
		// FPS evaluation
		double fps = 0;
		if(timeLast != 0)
		{
			long timeDif = System.currentTimeMillis() - timeLast;
			fps = (double)1000/(double)timeDif;
		}
		timeLast = System.currentTimeMillis();
		
		final int viewMode = appSettings.mFrameDetectMethod;
		
		Rect[] facesArray = null;
		MatOfRect frames = new MatOfRect();
		
		switch (viewMode) {
		
		case appSettings.FRAME_DETECT_DETERM:
			// input frame has RGBA format
			mRgba = inputFrame.rgba();
			mGray = inputFrame.gray();
			// Detect frame using deterministic classification			 
			FindFrame(mRgba.getNativeObjAddr(), frames.getNativeObjAddr());
			facesArray = frames.toArray();
			break;
		case appSettings.FRAME_DETECT_HAAR:
			// input frame has RGBA format
			mRgba = inputFrame.rgba();
			// Detect frame using haar cascade
			if (mJavaDetector != null)
                mJavaDetector.detectMultiScale(mGray, frames, 1.1, 2, 2, 
                        new Size(100, 20), new Size());
			facesArray = frames.toArray();	        
			break;
		}
		
		/// Process rectangles
		try
		{
			if(mListFrames.size() == 0)
			{
				for(int i = 0 ; i < facesArray.length ; i++)
				{
					FrameParams newFr = new FrameParams(facesArray[i]);				
					mListFrames.add(newFr);
				}				
			}
			else
			{
				// Check near rects if some found
				if(facesArray.length > 0)
				{
					for (int i = 0; i < facesArray.length; i++)
					{
						// If center near -> actualize mean values else add new FrameParams
						boolean bActualized = false;
						for(int j = 0; j < mListFrames.size(); j++)
						{
//							if(	Math.abs(facesArray[i].x - mListFrames.get(j).rect.x) < mNearKoef*6 &&
//								Math.abs(facesArray[i].y - mListFrames.get(j).rect.y) < mNearKoef)
							double overlap = rectsOverlap(mListFrames.get(j).rect, facesArray[i]); 
							if(overlap > 0.5)
							{
								bActualized = true;
								mListFrames.get(j).empthyNum = 0;
								mListFrames.get(i).IncLifeCyc();
								mListFrames.get(j).MeanParams(facesArray[i]);							
							}
						}
						if(!bActualized)
						{
							FrameParams newFr = new FrameParams(facesArray[i]);				
							mListFrames.add(newFr);
						}
					}
					
					// Delete dual frames
					for (int i = 0; i < mListFrames.size(); i++)
					{
						// If center near -> actualize mean values else add new FrameParams
						for(int j = 0; j < mListFrames.size(); j++)
						{
//							if(	Math.abs(mListFrames.get(i).rect.x - mListFrames.get(j).rect.x) < mNearKoef*6 &&
//								Math.abs(mListFrames.get(i).rect.y - mListFrames.get(j).rect.y) < mNearKoef &&
//								i != j)
							double overlap = rectsOverlap(mListFrames.get(i).rect, mListFrames.get(j).rect);
							if( overlap > 0.5 && i != j)
							{
								mListFrames.remove(j);
								j--;
							}
						}
					}
				}
				
				for(int i = 0; i < mListFrames.size(); i++)
				{				
					mListFrames.get(i).empthyNum++;
					// If too long no fix -> delete
					if(mListFrames.get(i).empthyNum > mFramesIter)
						mListFrames.remove(i);
				}
			}
			
			// Show mean rects
			for(int i = 0; i < mListFrames.size(); i++)
			{
				if(mListFrames.get(i).lifeCyc > 2)
					Core.rectangle(mRgba, mListFrames.get(i).rect.tl(), mListFrames.get(i).rect.br(),
						new Scalar(255*mListFrames.get(i).empthyNum/mFramesIter, 255 - 255*mListFrames.get(i).empthyNum/mFramesIter, 0), 3);
			}
		}
		catch(Exception e) 
		{
			Log.e(TAG, "Rectangles frames error. Exception thrown: " + e);
		}
		
//		for (int i = 0; i < facesArray.length; i++)
//            Core.rectangle(mRgba, facesArray[i].tl(), facesArray[i].br(), FACE_RECT_COLOR, 3);
		
		// Message
		String strMessage = new String();
		int lifeMax = 0;
		if(mListFrames.size() > 0)
		{
			
			for(int i = 0 ; i < mListFrames.size() ; i++)
				if(lifeMax < mListFrames.get(i).lifeCyc)lifeMax = mListFrames.get(i).lifeCyc;
		}
		
		if(lifeMax > 10)strMessage = "Pořizuji foto, nehýbat!";
		if(lifeMax < 10)strMessage = "Hledám číselník";
			
		if(strMessage != "")
			message = strMessage;
		
//		Core.putText(mRgba, strMessage + " " + lifeMax, new Point(10, 30), Core.FONT_HERSHEY_COMPLEX, 1, new Scalar(0, 0, 255));
		
		// Write FPS to frame
		Core.putText(mRgba, "FPS = " + String.format("%.2f", fps), new Point(
				10, mRgba.rows() - 30), Core.FONT_HERSHEY_COMPLEX, 1,
				new Scalar(0, 0, 255));
		
		return mRgba;
	}
	
	private String takePicture(String aFileName)
	{
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
        String currentDateandTime = sdf.format(new Date());
        String fileName = appSettings.mAppDir +
                               "/" + aFileName + "_" + currentDateandTime + ".jpg";
        
        // Set resolution
        mOpenCvCameraView.takePicture(fileName);
//        Toast.makeText(this, fileName + " saved", Toast.LENGTH_SHORT).show();
        return fileName;
	}
	
	private double rectsOverlap(Rect aRect1, Rect aRect2)
	{
		Point P1 = new Point();
		Point P2 = new Point();
		Point P3 = new Point();
		Point P4 = new Point();
		
		// init points
		if(aRect1.x <= aRect2.x )
		{
			P1 = new Point(aRect1.x, aRect1.y);
			P2 = new Point(aRect1.x+aRect1.width, aRect1.y+aRect1.height);
			P3 = new Point(aRect2.x, aRect2.y);
			P4 = new Point(aRect2.x+aRect1.width, aRect2.y+aRect2.height);
		}
		else
		{
			P3 = new Point(aRect1.x, aRect1.y);
			P4 = new Point(aRect1.x+aRect1.width, aRect1.y+aRect1.height);
			P1 = new Point(aRect2.x, aRect2.y);
			P2 = new Point(aRect2.x+aRect1.width, aRect2.y+aRect2.height);
		}
		
		// Ceck overlap
//		if((	aRect1.x <= aRect2.x && aRect1.x + aRect1.width >= aRect2.x &&
//				aRect1.y <= aRect2.y && aRect1.y + aRect1.height >= aRect2.y) ||
//			(	aRect2.x <= aRect1.x && aRect2.x + aRect2.width >= aRect1.x &&
//					aRect2.y <= aRect1.y && aRect2.y + aRect2.height >= aRect1.y)	)
		boolean ba = P1.x <= P3.x && P2.x >= P3.x;
		boolean bb = P1.y <= P3.y && P2.y >= P3.y;
//		(P1.x <= P3.x && P2.x >= P3.x && P1.y <= P3.y && P2.y >= P3.y)
		if( 	 ba && bb ||
				(P3.x <= P2.x && P4.x >= P2.x && P3.y <= P2.y && P4.y >= P2.y) ||
				(P3.x <= P2.x && P4.x >= P2.x && P3.y <= P2.y+P1.y && P4.y >= P2.y+P1.y))
		{
		
		// Create overlap rect
		Rect overlap = new Rect();
		// Find x
		if(aRect1.x < aRect2.x)overlap.x = aRect2.x;
		else overlap.x = aRect1.x;
		
		// Find y
		if(aRect1.y < aRect2.y)overlap.y = aRect2.y;
		else overlap.y = aRect1.y;
		
		// Find width
		if(aRect1.width > aRect2.width)overlap.width = aRect2.width;
		else overlap.width = aRect1.width;
		
		// Find height
		if(aRect1.height > aRect2.height)overlap.height = aRect2.height;
		else overlap.height = aRect1.height;
		
		double ret = overlap.area()/aRect1.area();
		return ret;
		}
		return 0;
	}

	public boolean onOptionsItemSelected(MenuItem item) {
		Log.i(TAG, "called onOptionsItemSelected; selected item: " + item);

		if (item == mItemDetFrame) {
			appSettings.mFrameDetectMethod = appSettings.FRAME_DETECT_DETERM;
//			mViewMode = VIEW_MODE_DET_FRAME;
		} else if (item == mItemHaarFrame) {
			appSettings.mFrameDetectMethod = appSettings.FRAME_DETECT_HAAR;
//			mViewMode = VIEW_MODE_HAAR_FRAME;
		} else if (item.getGroupId() == 1) {
			int id = item.getItemId();
			Camera.Size resolution = mResolutionList.get(id);
			mOpenCvCameraView.setResolution(resolution);
			resolution = mOpenCvCameraView.getResolution();
			String caption = Integer.valueOf(resolution.width).toString() + "x"
					+ Integer.valueOf(resolution.height).toString();
			Toast.makeText(this, caption, Toast.LENGTH_SHORT).show();
		}
		return true;
	}

	// Native methods declaration
	public native void FindFrame(long matAddrRgba, long rectAddr);
	public native void AdjFrame(long matAddrRgba, long rectAddr);
}
