package de.ugoe.cs.autoquest.androidmonitor;

import java.io.File;
import java.io.FileOutputStream;
import java.io.StringWriter;
import java.lang.reflect.Field;

import org.xmlpull.v1.XmlSerializer;

import de.ugoe.cs.autoquest.androidmonitor.AndroidmonitorCompositeOnClickListener;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.util.Log;
import android.util.Xml;
import android.view.View;
import android.view.ViewGroup;

public class Androidmonitor {

	String activityName; // name of the activity Class that starts a Tracker

	// Log file
	private AndroidmonitorLogFile logFile;

	/**
	 * constructor method to get a monitor object
	 * 
	 * @return monitor
	 */
	public static Androidmonitor getInstanceOfAndroidmonitor() {
		Androidmonitor monitor = new Androidmonitor();
		return monitor;
	}

	/**
	 * starts tracking an activity
	 * 
	 * @param activity
	 */
	public void startMonitor(Activity activity) {
		activityName = activity.getClass().getSimpleName();

		logFile = new AndroidmonitorLogFile(getAppLable(activity),
				activity.getFilesDir());

		addLogListenerToView(getRootView(activity));

		// tbd
		// listen to changes and update own listener
		// find out if it is possible to directly call addLogListenerToView
		// again
		// activity.onContentChanged();

		// write backPresss as event to xml file
		// activity.onBackPressed();

		// handle onStop() method of the activity
		// add a function that end up tracking if onStop() is given otherwise
		// create onStop()
		// http://developer.android.com/training/basics/activity-lifecycle/stopping.html
	}

	/**
	 * get the root view of an activity
	 * 
	 * @param activity
	 * @return
	 */
	public View getRootView(Activity activity) {
		// get root view of the activity as start point
		View view = activity.getWindow().getDecorView().getRootView();
		// try out if the given node is the upper one in the tree and return the
		// first node of the tree
		// The root of the decorView could be embedded into another layout
		// element.
		return findFirstView(view);
	}

	/**
	 * returns first view element of the tree
	 * 
	 * @param view
	 * @return
	 */
	private View findFirstView(View view) {
		if (view.getParent() != null && (view.getParent() instanceof ViewGroup)) {
			return findFirstView((View) view.getParent());
		} else {
			return view;
		}
	}

	/**
	 * replace the listener of each view with a composite listener which
	 * collects several listeners for one view.
	 * 
	 * @param view
	 *            to be monitored
	 */
	public void addLogListenerToView(View view) {
		addLogListenerToView(view, 0);
	}

	/**
	 * replace the listener of each view with a composite listener which
	 * collects several listeners for one view.
	 * 
	 * @param view
	 *            to be monitored
	 * @param parentHash
	 *            hash of the parent view element
	 */
	private void addLogListenerToView(View view, int parentHash) {

		logFile.addComponent(view, parentHash, activityName);

		// hash code of the actual view element. Problem in using
		// view.getParent().hashCode() is described in
		// de.ugoe.cs.autoquest.androidmonitor addComponent()
		parentHash = view.hashCode();
		// traverse all views of the activity
		if (view instanceof ViewGroup) {
			ViewGroup group = (ViewGroup) view;
			for (int i = 0; i < group.getChildCount(); i++) {
				View child = group.getChildAt(i);
				addLogListenerToView(child, parentHash);
			}
		}

		// save original listener to add it later on to the groupLisatener
		View.OnClickListener listener = getOnClickListener(view);

		if (listener != null) {
			// create new compositeOnClickListener to handle multiple listeners
			// for one view
			AndroidmonitorCompositeOnClickListener groupListener = new AndroidmonitorCompositeOnClickListener();
			// replace the original onClickListener with the
			// compositeOnClickListener
			view.setOnClickListener(groupListener);
			// add the tracking part as a several listener
			groupListener.addOnClickListener(new View.OnClickListener() {
				public void onClick(View v) {

					logFile.addEvent(v, "onClick");

					// track information ...
					// Log.d("MyLog",
					// "activity:" + activityName + " id:" + v.getId()
					// + " element:"
					// + v.getClass().getSimpleName() + " x:" + v.getX() + " y:"
					// + v.getY() + " time:"
					// + System.currentTimeMillis());

				}
			});
			// add original onClick listener to groupListener of the view
			groupListener.addOnClickListener(listener);
		}
	}

	/**
	 * finds out if a listener exists
	 * 
	 * @param view
	 * @return the listener of the view or null if no listener exists
	 */
	public View.OnClickListener getOnClickListener(View view) {
		// http://stackoverflow.com/questions/11186960/getonclicklistener-in-android-views
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
			return getOnClickListenerV14(view);
		} else {
			return getOnClickListenerV(view);
		}
	}

	// Used for APIs lower than ICS (API 14)
	private View.OnClickListener getOnClickListenerV(View view) {
		View.OnClickListener retrievedListener = null;
		String viewStr = "android.view.View";
		Field field;

		try {
			field = Class.forName(viewStr).getDeclaredField("mOnClickListener");
			retrievedListener = (View.OnClickListener) field.get(view);
		} catch (NoSuchFieldException ex) {
			Log.e("Reflection", "No Such Field.");
		} catch (IllegalAccessException ex) {
			Log.e("Reflection", "Illegal Access.");
		} catch (ClassNotFoundException ex) {
			Log.e("Reflection", "Class Not Found.");
		}

		return retrievedListener;
	}

	// Used for new ListenerInfo class structure used beginning with API 14
	// (ICS)
	private View.OnClickListener getOnClickListenerV14(View view) {
		View.OnClickListener retrievedListener = null;
		String viewStr = "android.view.View";
		String lInfoStr = "android.view.View$ListenerInfo";

		try {
			Field listenerField = Class.forName(viewStr).getDeclaredField(
					"mListenerInfo");
			Object listenerInfo = null;

			if (listenerField != null) {
				listenerField.setAccessible(true);
				listenerInfo = listenerField.get(view);
			}

			Field clickListenerField = Class.forName(lInfoStr)
					.getDeclaredField("mOnClickListener");

			if (clickListenerField != null && listenerInfo != null) {
				retrievedListener = (View.OnClickListener) clickListenerField
						.get(listenerInfo);
			}
		} catch (NoSuchFieldException ex) {
			Log.e("Reflection", "No Such Field.");
		} catch (IllegalAccessException ex) {
			Log.e("Reflection", "Illegal Access.");
		} catch (ClassNotFoundException ex) {
			Log.e("Reflection", "Class Not Found.");
		}

		return retrievedListener;
	}

	/**
	 * get application name as defined in Package Name
	 * 
	 * @param pContext
	 *            package context; could also be an activity
	 * @return app name
	 */
	public String getAppLable(Context pContext) {
		// source (2014-09-04):
		// http://stackoverflow.com/questions/11229219/android-get-application-name-not-package-name
		PackageManager lPackageManager = pContext.getPackageManager();
		ApplicationInfo lApplicationInfo = null;
		try {
			lApplicationInfo = lPackageManager.getApplicationInfo(
					pContext.getApplicationInfo().packageName, 0);
		} catch (final NameNotFoundException e) {
		}
		return (String) (lApplicationInfo != null ? lPackageManager
				.getApplicationLabel(lApplicationInfo) : "Unknown");
	}

}
