package de.tudarmstadt.informatik.hostage.ui2.fragment;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.graphics.Color;
import android.location.Location;
import android.os.Bundle;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import de.tudarmstadt.informatik.hostage.R;
import de.tudarmstadt.informatik.hostage.logging.Record;
import de.tudarmstadt.informatik.hostage.logging.UglyDbHelper;
import de.tudarmstadt.informatik.hostage.ui.LogFilter;
import de.tudarmstadt.informatik.hostage.ui2.activity.MainActivity;

import static com.google.android.gms.common.GooglePlayServicesUtil.*;

/**
 * ThreatMapFragment
 *
 * Created by Fabio Arnold on 10.02.14.
 */
public class ThreatMapFragment extends Fragment implements GoogleMap.OnInfoWindowClickListener,
		GooglePlayServicesClient.ConnectionCallbacks,
		GooglePlayServicesClient.OnConnectionFailedListener,
		LocationListener {
	private static GoogleMap sMap = null;
	private static View sView = null;

	private static HashMap<String, String> sMarkerIDToSSID = new HashMap<String, String>();

	private LocationClient mLocationClient;
	private static final LocationRequest REQUEST = LocationRequest.create()
			.setExpirationDuration(5000) // 5 seconds
			.setInterval(5000)           // 5 seconds
			.setFastestInterval(16)      // 16ms = 60fps
			.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

	/**
	 * if google play services aren't available an error notification will be displayed
	 *
	 * @return true if the google play services are available
	 */
	private boolean isGooglePlay() {
		int status = isGooglePlayServicesAvailable(getActivity());
		boolean result = status == ConnectionResult.SUCCESS;
		if (!result) {
			getErrorDialog(status, getActivity(), 10).show();
		}
		return result;
	}

	/**
	 * callback for when the info window of a marker gets clicked
	 * open the RecordOverviewFragment and display all records belonging to an SSID
	 *
	 * @param marker this info window belongs to
	 */
	@Override
	public void onInfoWindowClick(Marker marker) {
		//MainActivity.getInstance().displayView(MainActivity.MainMenuItem.RECORDS.getValue());
		//RecordOverviewFragment recordOverviewFragment = (RecordOverviewFragment)MainActivity.getInstance().getCurrentFragment();
		//if (recordOverviewFragment != null) {
		String ssid = sMarkerIDToSSID.get(marker.getId());

		ArrayList<String> ssids = new ArrayList<String>();
		ssids.add(ssid);

		LogFilter filter = new LogFilter();
		filter.setESSIDs(ssids);

		RecordOverviewFragment recordOverviewFragment = new RecordOverviewFragment();
		recordOverviewFragment.setFilter(filter);
		recordOverviewFragment.setGroupKey("ESSID");

		MainActivity.getInstance().injectFragment(recordOverviewFragment, false);
			//recordOverviewFragment.showDetailsForSSID(getActivity(), ssid);
		//}
	}

	/**
	 * callbacks from LocationClient
	 */
	@Override
	public void onConnected(Bundle bundle) {
		mLocationClient.requestLocationUpdates(REQUEST, this);
	}

	@Override
	public void onDisconnected() {

	}

	@Override
	public void onConnectionFailed(ConnectionResult connectionResult) {

	}

	@Override
	public void onLocationChanged(Location location) {
		sMap.animateCamera(CameraUpdateFactory.newLatLng(
				new LatLng(location.getLatitude(), location.getLongitude())));
	}

	/**
	 * helper class
	 * easier to use than LatLng
	 */
	private class Point {
		public double x, y;
		public Point(double sx, double sy) {
			x = sx;
			y = sy;
		}
	}

	/**
	 * helper class
	 * contains heuristic to split SSIDs by location
	 * see MAX_DISTANCE
	 */
	private class SSIDArea {
		private Point mMinimum, mMaximum;
		public int numPoints;

		public static final int MAX_NUM_ATTACKS = 20;
		public static final float MAX_DISTANCE = 1000.0f; // 1km

		public SSIDArea(LatLng initialLocation) {
			//mMinimum = new Point(360.0, 360.0);
			//mMaximum = new Point(-360.0, -360.0);
			mMinimum = new Point(initialLocation.latitude, initialLocation.longitude);
			mMaximum = new Point(initialLocation.latitude, initialLocation.longitude);
			numPoints = 1;
		}

		public boolean doesLocationBelongToArea(LatLng location) {
			LatLng center = calculateCenterLocation();
			float[] result = new float[1];
			Location.distanceBetween(center.latitude, center.longitude, location.latitude, location.longitude, result);
			return result[0] < MAX_DISTANCE;
		}

		public void addLocation(LatLng location) {
			Point point = new Point(location.latitude, location.longitude);
			if (point.x < mMinimum.x) mMinimum.x = point.x;
			if (point.x > mMaximum.x) mMaximum.x = point.x;
			if (point.y < mMinimum.y) mMinimum.y = point.y;
			if (point.y > mMaximum.y) mMaximum.y = point.y;
			numPoints++;
		}

		public LatLng calculateCenterLocation() {
			return new LatLng(0.5 * (mMinimum.x + mMaximum.x), 0.5 * (mMinimum.y + mMaximum.y));
		}

		public float calculateRadius() {
			float[] result = new float[1];
			Location.distanceBetween(mMinimum.x, mMinimum.y, mMaximum.x, mMaximum.y, result);
			return 0.5f * result[0];
		}

		public int calculateColor() {
			int threatLevel = numPoints;
			if (threatLevel > MAX_NUM_ATTACKS) threatLevel = MAX_NUM_ATTACKS;
			float alpha = 1.0f - (float)(threatLevel-1) / (float)(MAX_NUM_ATTACKS-1);
			return Color.argb(127, (int) (240.0 + 15.0 * alpha), (int) (80.0 + 175.0 * alpha), 60);
		}
	}

	/**
	 * fills the map with markers and circle representing SSIDs
	 */
	private void populateMap() {
		UglyDbHelper dbh = new UglyDbHelper(getActivity());
		ArrayList<Record> records = dbh.getAllRecords();

		HashMap<String, ArrayList<SSIDArea>> threadAreas = new HashMap<String, ArrayList<SSIDArea>>();

		for (Record record : records) {
			LatLng location = new LatLng(record.getLatitude(), record.getLongitude());
			ArrayList<SSIDArea> areas;
			if (threadAreas.containsKey(record.getSsid())) {
				areas = threadAreas.get(record.getSsid());
				boolean foundArea = false;
				for (SSIDArea area : areas) {
					if (area.doesLocationBelongToArea(location)) {
						area.addLocation(location);
						foundArea = true;
						break;
					}
				}
				if (!foundArea) {
					areas.add(new SSIDArea(location));
				}
			} else {
				areas = new ArrayList<SSIDArea>();
				areas.add(new SSIDArea(location));
				threadAreas.put(record.getSsid(), areas);
			}
		}

		CircleOptions circleOptions = new CircleOptions().radius(200.0).fillColor(Color.argb(127, 240, 80, 60)).strokeWidth(0.0f);
		for (Map.Entry<String, ArrayList<SSIDArea>> entry : threadAreas.entrySet()) {
			String ssid = entry.getKey();
			ArrayList<SSIDArea> areas = entry.getValue();

			for (SSIDArea area : areas) {
				int color = area.calculateColor();
				LatLng center = area.calculateCenterLocation();
				float radius = area.calculateRadius();

				sMap.addCircle(circleOptions.center(center).radius(100.0 + radius).fillColor(color));
				Marker marker = sMap.addMarker(new MarkerOptions()
						.title(ssid + ": " + area.numPoints + (area.numPoints == 1 ? " attack"
								: " attacks")).position(
								center));

				sMarkerIDToSSID.put(marker.getId(), ssid);
			}
		}

		sMap.setMyLocationEnabled(true);

		LatLng tudarmstadt = new LatLng(49.86923, 8.6632768); // default location
		sMap.moveCamera(CameraUpdateFactory.newLatLngZoom(tudarmstadt, 13));
	}

	/**
	 * performs initialization
	 * checks if google play services are supported
	 * view must be removed if this object has been created once before
	 * that is why view is static
	 *
	 * @param inflater the inflater
	 * @param container the container
	 * @param savedInstanceState the savedInstanceState
	 * @return the view
	 */
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
	                         Bundle savedInstanceState) {
		super.onCreateView(inflater, container, savedInstanceState);

		final Activity activity = getActivity();
		if (activity != null) {
			activity.setTitle(getResources().getString(R.string.drawer_threat_map));
		}

		if (sView != null) {
			ViewGroup parent = (ViewGroup) sView.getParent();
			if (parent != null)
				parent.removeView(sView);
		}
		
		try {
			sView = inflater.inflate(R.layout.fragment_threatmap, container, false);
			if (isGooglePlay()) {
				final FragmentManager fragmentManager = getFragmentManager();
				if (fragmentManager != null) {
					final MapFragment mapFragment = (MapFragment) getFragmentManager()
							.findFragmentById(R.id.threatmapfragment);
					if (mapFragment != null) {
						sMap = mapFragment.getMap();
						populateMap();
					}
				}
			}
		} catch (InflateException e) {
        	// map already exists
			//e.printStackTrace();
		}
		if (sMap != null) {
			sMap.setOnInfoWindowClickListener(this);
		}

		return sView;
	}

	@Override
	public void onResume() {
		super.onResume();
		if (mLocationClient == null) {
			mLocationClient = new LocationClient(MainActivity.getInstance().getApplicationContext(), this, this);
		}
		mLocationClient.connect();
	}

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