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
	
	//Handle XML
	private XmlSerializer xmlSerializer = Xml.newSerializer(); // handle XML tracking
	private StringWriter stringWriter = new StringWriter(); // used with XmlSerializer
	
	//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;
		}
	}

	/**
	 * generates the path of an view element
	 * 
	 * @param view
	 * @return path to the element
	 */
	private String getViewPath(View view) {
		return getViewPath(view, null);
	}

	/**
	 * generates the path of an view element
	 * 
	 * @param view
	 * @param path
	 * @return path to the element
	 */
	private String getViewPath(View view, String path) {
		if (path == null) {
			path = "";
		} else {
			path = view.getClass().getSimpleName() + "/" + path;
		}
		if (view.getParent() != null && (view.getParent() instanceof ViewGroup)) {
			return getViewPath((View) view.getParent(), path);
		} else {
			return path;
		}
	}

	/**
	 * replace the listener of each view with a composite listener which
	 * collects several listeners for one view.
	 * 
	 * @param view
	 */
	public void addLogListenerToView(View view) {
		// 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);
			}
		}

		// 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) {

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

					XmlSerializer serializer = Xml.newSerializer(); // handle
																	// XML
																	// tracking
					StringWriter writer = new StringWriter();

					try {
						serializer.setOutput(writer);
						serializer.startTag("", "event");
						serializer.attribute("", "id", viewId);
						serializer.startTag("", "param");
						serializer.attribute("", "value", x);
						serializer.attribute("", "name", "X");
						serializer.endTag("", "param");
						serializer.startTag("", "param");
						serializer.attribute("", "value", y);
						serializer.attribute("", "name", "Y");
						serializer.endTag("", "param");
						serializer.startTag("", "param");
						serializer.attribute("", "value", v.getClass()
								.getSimpleName());
						serializer.attribute("", "name", v.getClass()
								.getSimpleName());
						serializer.endTag("", "param");
						serializer.endTag("", "event");
						serializer.endDocument();
						Log.d("xml", writer.toString());
					} catch (Exception e) {
						throw new RuntimeException(e);
					}

				}
			});
			// 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");
	}
	

}
