/*
 * Androzic - android navigation client that uses OziExplorer maps (ozf2, ozfx3).
 * Copyright (C) 2010-2014 Andrey Novikov <http://andreynovikov.info/>
 * 
 * This file is part of Androzic application.
 * 
 * Androzic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Androzic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Androzic. If not, see <http://www.gnu.org/licenses/>.
 */

package com.androzic;

import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.location.Location;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.internal.view.SupportMenuInflater;
import android.support.v7.internal.view.menu.MenuBuilder;
import android.support.v7.internal.view.menu.MenuPopupHelper;
import android.support.v7.internal.view.menu.MenuPresenter;
import android.text.Html;
import android.text.format.DateUtils;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.AnimationSet;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.androzic.data.Route;
import com.androzic.data.Waypoint;
import com.androzic.location.LocationService;
import com.androzic.map.BaseMap;
import com.androzic.navigation.NavigationService;
import com.androzic.route.OnRouteActionListener;
import com.androzic.route.RouteEdit;
import com.androzic.ui.TooltipManager;
import com.androzic.util.Astro;
import com.androzic.util.Clipboard;
import com.androzic.util.CoordinateParser;
import com.androzic.util.StringFormatter;
import com.androzic.waypoint.OnWaypointActionListener;

public class MapFragment extends Fragment implements MapHolder, OnSharedPreferenceChangeListener, View.OnClickListener, View.OnTouchListener, MenuBuilder.Callback, MenuPresenter.Callback
{
	public static final String TAG = "MapFragment";
	
	private OnWaypointActionListener waypointActionsCallback;
	private OnRouteActionListener routeActionsCallback;

	// Settings
	/**
	 * UI (map data) update interval in milliseconds
	 */
	private long updatePeriod;
	private boolean following;
	private boolean followOnLocation;
	private boolean keepScreenOn;
	private int showDistance;
	private boolean autoDim;
	private int dimInterval;
	private int dimValue;
	private boolean mapButtonsVisible;
	private int forceZoomMap;

	private int mapInfoHideDelay = Integer.MAX_VALUE; // never
	private int satInfoHideDelay = Integer.MAX_VALUE; // never
	private int navInfoHideDelay = Integer.MAX_VALUE; // never

	// Views
	private MapView map;

	private TextView coordinates;
	private TextView satellites;
	private TextView currentFile;
	private TextView mapLicense;
	private TextView mapZoom;

	private TextView waypointName;
	private TextView waypointExtra;
	private TextView routeName;
	private TextView routeExtra;

	private TextView distanceValue;
	private TextView distanceUnit;
	private TextView bearingValue;
	private TextView bearingUnit;
	private TextView turnValue;
	private TextView turnUnit;

	private TextView speedValue;
	private TextView speedUnit;
	private TextView trackValue;
	private TextView trackUnit;
	private TextView elevationValue;
	private TextView elevationUnit;
	private TextView xtkValue;
	private TextView xtkUnit;

	private TextView waitBar;

	private View mapInfo;
	private View satInfo;
	private View navInfo;
	private View mapButtons;
	private ViewGroup dimView;
	private View anchor;

	Androzic application;

	private ExecutorService executorThread = Executors.newSingleThreadExecutor();
	private FinishHandler finishHandler;
	private Handler updateCallback = new Handler();
	private Handler tooltipCallback = new Handler();

	private int waypointSelected = -1;
	private long mapObjectSelected = -1;

	protected long lastDim = 0;
	private boolean lastGeoid = true;
	private int zoom100X = 0;
	private int zoom100Y = 0;

	private boolean animationSet;

	public MapFragment()
	{
		application = Androzic.getApplication();
	}

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		Log.e(TAG, "onCreate()");

		super.onCreate(savedInstanceState);
		setRetainInstance(true);
		setHasOptionsMenu(true);

		finishHandler = new FinishHandler(this);
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
	{
		Log.e(TAG, "onCreateView()");

		View view = inflater.inflate(R.layout.fragment_map, container, false);

		coordinates = (TextView) view.findViewById(R.id.coordinates);
		satellites = (TextView) view.findViewById(R.id.sats);
		currentFile = (TextView) view.findViewById(R.id.currentfile);
		mapLicense = (TextView) view.findViewById(R.id.maplicense);
		mapLicense.setClickable(true);
		mapLicense.setMovementMethod(LinkMovementMethod.getInstance());
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
		{
			mapLicense.setRotation(-90);
			mapLicense.setPivotX(0);
			mapLicense.setPivotY(0);
		}
		mapZoom = (TextView) view.findViewById(R.id.currentzoom);
		waypointName = (TextView) view.findViewById(R.id.waypointname);
		waypointExtra = (TextView) view.findViewById(R.id.waypointextra);
		routeName = (TextView) view.findViewById(R.id.routename);
		routeExtra = (TextView) view.findViewById(R.id.routeextra);
		speedValue = (TextView) view.findViewById(R.id.speed);
		speedUnit = (TextView) view.findViewById(R.id.speedunit);
		trackValue = (TextView) view.findViewById(R.id.track);
		trackUnit = (TextView) view.findViewById(R.id.trackunit);
		elevationValue = (TextView) view.findViewById(R.id.elevation);
		elevationUnit = (TextView) view.findViewById(R.id.elevationunit);
		distanceValue = (TextView) view.findViewById(R.id.distance);
		distanceUnit = (TextView) view.findViewById(R.id.distanceunit);
		xtkValue = (TextView) view.findViewById(R.id.xtk);
		xtkUnit = (TextView) view.findViewById(R.id.xtkunit);
		bearingValue = (TextView) view.findViewById(R.id.bearing);
		bearingUnit = (TextView) view.findViewById(R.id.bearingunit);
		turnValue = (TextView) view.findViewById(R.id.turn);
		turnUnit = (TextView) view.findViewById(R.id.turnunit);
		// trackBar = (SeekBar) findViewById(R.id.trackbar);
		mapInfo = view.findViewById(R.id.mapinfo);
		satInfo = view.findViewById(R.id.satinfo);
		navInfo = view.findViewById(R.id.navinfo);
		mapButtons = view.findViewById(R.id.mapbuttons);
		waitBar = (TextView) view.findViewById(R.id.waitbar);
		anchor = view.findViewById(R.id.anchor);
		map = (MapView) view.findViewById(R.id.mapview);
		map.initialize(application, this);

		view.findViewById(R.id.zoomin).setOnClickListener(this);
		view.findViewById(R.id.zoomin).setOnTouchListener(this);
		view.findViewById(R.id.zoomout).setOnClickListener(this);
		view.findViewById(R.id.nextmap).setOnClickListener(this);
		view.findViewById(R.id.prevmap).setOnClickListener(this);
		coordinates.setOnClickListener(this);
		satellites.setOnClickListener(this);
		currentFile.setOnClickListener(this);
		mapZoom.setOnClickListener(this);
		waypointName.setOnClickListener(this);

		// set route edit button actions
		view.findViewById(R.id.finishrouteedit).setOnClickListener(this);
		view.findViewById(R.id.addpoint).setOnClickListener(this);
		view.findViewById(R.id.insertpoint).setOnClickListener(this);
		view.findViewById(R.id.removepoint).setOnClickListener(this);
		view.findViewById(R.id.orderpoints).setOnClickListener(this);

		application.setMapHolder(this);

		return view;
	}

	@Override
	public void onAttach(Activity activity)
	{
		super.onAttach(activity);
		Log.e(TAG, "onAttach()");
		
		dimView = new RelativeLayout(getActivity());

		// This makes sure that the container activity has implemented
		// the callback interface. If not, it throws an exception
		try
		{
			waypointActionsCallback = (OnWaypointActionListener) activity;
		}
		catch (ClassCastException e)
		{
			throw new ClassCastException(activity.toString() + " must implement OnWaypointActionListener");
		}
		try
		{
			routeActionsCallback = (OnRouteActionListener) activity;
		}
		catch (ClassCastException e)
		{
			throw new ClassCastException(activity.toString() + " must implement OnRouteActionListener");
		}
	}

	@Override
	public void onStart()
	{
		super.onStart();
		Log.e(TAG, "onStart()");
		((ViewGroup) getActivity().getWindow().getDecorView()).addView(dimView);
	}

	@Override
	public void onResume()
	{
		super.onResume();
		Log.e(TAG, "onResume()");

		SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity());

		onSharedPreferenceChanged(settings, getString(R.string.pref_maprenderinterval));
		onSharedPreferenceChanged(settings, getString(R.string.pref_mapfollowonloc));
		onSharedPreferenceChanged(settings, getString(R.string.pref_wakelock));
		onSharedPreferenceChanged(settings, getString(R.string.pref_showdistance_int));
		onSharedPreferenceChanged(settings, getString(R.string.pref_hidemapinfo));
		onSharedPreferenceChanged(settings, getString(R.string.pref_hidesatinfo));
		onSharedPreferenceChanged(settings, getString(R.string.pref_hidenavinfo));
		
		onSharedPreferenceChanged(settings, getString(R.string.pref_mapdiminterval));
		onSharedPreferenceChanged(settings, getString(R.string.pref_mapdimvalue));
		onSharedPreferenceChanged(settings, getString(R.string.pref_mapdim));
		onSharedPreferenceChanged(settings, getString(R.string.pref_unfollowontap));
		onSharedPreferenceChanged(settings, getString(R.string.pref_lookahead));
		onSharedPreferenceChanged(settings, getString(R.string.pref_mapbest));
		onSharedPreferenceChanged(settings, getString(R.string.pref_mapbestinterval));
		onSharedPreferenceChanged(settings, getString(R.string.pref_mapforcezoom));
		onSharedPreferenceChanged(settings, getString(R.string.pref_maprotation));
		onSharedPreferenceChanged(settings, getString(R.string.pref_scalebarbg));
		onSharedPreferenceChanged(settings, getString(R.string.pref_scalebarcolor));
		onSharedPreferenceChanged(settings, getString(R.string.pref_scalebarbgcolor));
		onSharedPreferenceChanged(settings, getString(R.string.pref_hidemapcross));
		onSharedPreferenceChanged(settings, getString(R.string.pref_mapcrosscolor));
		onSharedPreferenceChanged(settings, getString(R.string.pref_cursorvector));
		onSharedPreferenceChanged(settings, getString(R.string.pref_cursorcolor));
		onSharedPreferenceChanged(settings, getString(R.string.pref_navigation_proximity));

		onSharedPreferenceChanged(settings, getString(R.string.pref_unitprecision));
		onSharedPreferenceChanged(settings, getString(R.string.pref_unitspeed));
		onSharedPreferenceChanged(settings, getString(R.string.pref_unitelevation));
		onSharedPreferenceChanged(settings, getString(R.string.pref_unitangle));

		PreferenceManager.getDefaultSharedPreferences(application).registerOnSharedPreferenceChangeListener(this);

		updateEditStatus();
		updateGPSStatus();
		onUpdateNavigationState();
		onUpdateNavigationStatus();
		
		application.registerReceiver(broadcastReceiver, new IntentFilter(NavigationService.BROADCAST_NAVIGATION_STATUS));
		application.registerReceiver(broadcastReceiver, new IntentFilter(NavigationService.BROADCAST_NAVIGATION_STATE));
		application.registerReceiver(broadcastReceiver, new IntentFilter(LocationService.BROADCAST_LOCATING_STATUS));
		application.registerReceiver(broadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
		application.registerReceiver(broadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));

		map.setKeepScreenOn(keepScreenOn);
		map.setFollowing(following);

		mapButtonsVisible = settings.getBoolean(getString(R.string.ui_mapbuttons_shown), true);
		mapButtons.setVisibility(mapButtonsVisible ? View.VISIBLE : View.GONE);

		updateMapViewArea();

		// Start updating UI
		map.resume();
		map.updateMapInfo();
		map.updateMapCenter();
		map.requestFocus();
		updateCallback.post(updateUI);
		tooltipCallback.postDelayed(showTooltip, TooltipManager.TOOLTIP_DELAY);
	}

	@Override
	public void onPause()
	{
		super.onPause();
		Log.e(TAG, "onPause()");

		application.unregisterReceiver(broadcastReceiver);
		
		// Stop updating UI
		map.pause();
		updateCallback.removeCallbacks(updateUI);

		// Stop showing tooltips
		tooltipCallback.removeCallbacks(showTooltip);
		TooltipManager.dismiss();

		PreferenceManager.getDefaultSharedPreferences(application).unregisterOnSharedPreferenceChangeListener(this);
	}

	@Override
	public void onStop()
	{
		super.onStop();
		Log.e(TAG, "onStop()");
		((ViewGroup) getActivity().getWindow().getDecorView()).removeView(dimView);
	}

	@Override
	public void onDestroyView()
	{
		super.onDestroyView();
		Log.e(TAG, "onDestroyView()");

		map.pause();
		map = null;
		coordinates = null;
		satellites = null;
		currentFile = null;
		mapLicense = null;
		mapZoom = null;
		waypointName = null;
		waypointExtra = null;
		routeName = null;
		routeExtra = null;
		speedValue = null;
		speedUnit = null;
		trackValue = null;
		elevationValue = null;
		elevationUnit = null;
		distanceValue = null;
		distanceUnit = null;
		xtkValue = null;
		xtkUnit = null;
		bearingValue = null;
		turnValue = null;
		// trackBar = null;
	}

	@Override
	public void onDestroy()
	{
		super.onDestroy();
		Log.e(TAG, "onDestroy()");

		application = null;
	}

	@Override
	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
	{
		inflater.inflate(R.menu.map_menu, menu);
		super.onCreateOptionsMenu(menu, inflater);
	}

	@Override
	public void onPrepareOptionsMenu(final Menu menu)
	{
		boolean fixed = map != null && map.isFixed();
		
		MenuItem follow = menu.findItem(R.id.action_follow);
		if (!fixed || following && map != null && ! map.getStrictUnfollow())
		{
			follow.setVisible(false);
		}
		else if (following)
		{
			follow.setIcon(R.drawable.ic_lock_outline_white_24dp);
			follow.setTitle(R.string.action_unfollow);
		}
		else
		{
			follow.setVisible(true);
			follow.setIcon(R.drawable.ic_lock_open_white_24dp);
			follow.setTitle(R.string.action_follow);
		}

		menu.findItem(R.id.action_locate).setVisible(!fixed);

		menu.findItem(R.id.action_locating).setChecked(application.isLocating());
		menu.findItem(R.id.action_tracking).setChecked(application.isTracking());
		
		boolean navigating = application.isNavigating();
		boolean viaRoute = application.isNavigatingViaRoute();

		menu.findItem(R.id.action_stop_navigation).setVisible(navigating);
		menu.findItem(R.id.action_next_nav_point).setVisible(viaRoute);
		menu.findItem(R.id.action_prev_nav_point).setVisible(viaRoute);
		if (viaRoute)
		{
			menu.findItem(R.id.action_next_nav_point).setEnabled(application.navigationService.hasNextRouteWaypoint());
			menu.findItem(R.id.action_prev_nav_point).setEnabled(application.navigationService.hasPrevRouteWaypoint());
		}
	}

	final private Runnable showTooltip = new Runnable() {
		@Override
		public void run()
		{
			long tooltip = TooltipManager.getTooltip(TAG);
			if (tooltip == 0L)
				return;
			if (tooltip == TooltipManager.TOOLTIP_MAP_BUTTONS)
				TooltipManager.showTooltip(tooltip, mapZoom);
			else if (tooltip == TooltipManager.TOOLTIP_SWITCH_MAP)
				TooltipManager.showTooltip(tooltip, currentFile);
			else if (tooltip == TooltipManager.TOOLTIP_QUICK_ZOOM && application.getZoom() != 1. && mapButtonsVisible)
				TooltipManager.showTooltip(tooltip, mapButtons.findViewById(R.id.zoomin));
			else if (tooltip == TooltipManager.TOOLTIP_CURRENT_LOCATION)
				TooltipManager.showTooltip(tooltip, coordinates);
			tooltipCallback.postDelayed(this, TooltipManager.TOOLTIP_PERIOD);
		}
	};

	final private Runnable updateUI = new Runnable() {
		public void run()
		{
			updateCallback.postDelayed(this, updatePeriod);

			if (application.gpsEnabled)
			{
				if (!map.isFixed())
				{
					satellites.setText(R.string.sat_start);
					satellites.setTextColor(getResources().getColor(R.color.gpsenabled));
					// Mock provider hack
					if (application.gpsContinous)
					{
						map.setMoving(true);
						map.setFixed(true);
						getActivity().supportInvalidateOptionsMenu();
					}
				}
				switch (application.gpsStatus)
				{
					case LocationService.GPS_OK:
						satellites.setTextColor(getResources().getColor(R.color.gpsworking));
						satellites.setText(String.valueOf(application.gpsFSats) + "/" + String.valueOf(application.gpsTSats));
						if (!map.isFixed())
						{
							map.setMoving(true);
							map.setFixed(true);
							getActivity().supportInvalidateOptionsMenu();
						}
						break;
					case LocationService.GPS_SEARCHING:
						satellites.setTextColor(getResources().getColor(R.color.gpsenabled));
						satellites.setText(String.valueOf(application.gpsFSats) + "/" + String.valueOf(application.gpsTSats));
						if (map.isFixed())
						{
							map.setFixed(false);
							getActivity().supportInvalidateOptionsMenu();
						}
						break;
				}
			}
			else
			{
				satellites.setText(R.string.sat_stop);
				satellites.setTextColor(getResources().getColor(R.color.gpsdisabled));
				if (map.isMoving())
				{
					map.setMoving(false);
					map.setFixed(false);
					getActivity().supportInvalidateOptionsMenu();
				}
			}

			updatePanels();

			if (application.lastKnownLocation == null)
				return;

			map.setLocation(application.lastKnownLocation);

			double track = application.fixDeclination(application.lastKnownLocation.getBearing());
			speedValue.setText(StringFormatter.speedC(application.lastKnownLocation.getSpeed()));
			trackValue.setText(StringFormatter.angleC(track));
			elevationValue.setText(StringFormatter.elevationC(application.lastKnownLocation.getAltitude()));
			// TODO set separate color
			if (application.gpsGeoid != lastGeoid)
			{
				int color = application.gpsGeoid ? 0xffffffff : getResources().getColor(R.color.gpsenabled);
				elevationValue.setTextColor(color);
				elevationUnit.setTextColor(color);
				((TextView) getView().findViewById(R.id.elevationname)).setTextColor(color);
				lastGeoid = application.gpsGeoid;
			}

			if (application.shouldEnableFollowing && map.isFixed())
			{
				application.shouldEnableFollowing = false;
				if (followOnLocation)
					setFollowing(true);
			}

			updateGPSStatus();

			if (autoDim && dimInterval > 0 && application.lastKnownLocation.getTime() - lastDim >= dimInterval)
			{
				dimScreen();
				lastDim = application.lastKnownLocation.getTime();
			}
		}
	};

	private void updateMapViewArea()
	{
		final ViewTreeObserver vto = map.getViewTreeObserver();
		vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
			@SuppressLint("NewApi")
			@SuppressWarnings("deprecation")
			public void onGlobalLayout()
			{
				View root = getView();
				Rect area = new Rect();
				map.getLocalVisibleRect(area);
				View v = root.findViewById(R.id.topbar);
				if (v != null)
					area.top = v.getBottom();
				v = root.findViewById(R.id.bottombar);
				if (v != null)
					area.bottom = v.getTop();
				if (mapLicense.isShown())
				{
					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && mapLicense.getRotation() != 0f)
						area.left = mapLicense.getHeight(); // rotated view does not correctly report it's position
					else
						area.bottom = mapLicense.getTop();
				}
				v = root.findViewById(R.id.rightbar);
				if (v != null)
					area.right = v.getLeft();
				if (mapButtons.isShown())
				{
					// Landscape mode
					if (v != null)
						area.bottom = mapButtons.getTop();
					else
						area.right = mapButtons.getLeft();
				}
				if (!area.isEmpty())
					map.updateViewArea(area);
				ViewTreeObserver ob;
				if (vto.isAlive())
					ob = vto;
				else
					ob = map.getViewTreeObserver();

				if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
				{
					ob.removeGlobalOnLayoutListener(this);
				}
				else
				{
					ob.removeOnGlobalLayoutListener(this);
				}
			}
		});
	}

	private void onUpdateNavigationState()
	{
		boolean isNavigating = application.navigationService != null && application.navigationService.isNavigating();
		boolean isNavigatingViaRoute = isNavigating && application.navigationService.isNavigatingViaRoute();

		View rootView = getView();

		// waypoint panel
		rootView.findViewById(R.id.waypointinfo).setVisibility(isNavigating ? View.VISIBLE : View.GONE);
		// route panel
		rootView.findViewById(R.id.routeinfo).setVisibility(isNavigatingViaRoute ? View.VISIBLE : View.GONE);
		// distance
		distanceValue.setVisibility(isNavigating ? View.VISIBLE : View.GONE);
		rootView.findViewById(R.id.distancelt).setVisibility(isNavigating ? View.VISIBLE : View.GONE);
		// bearing
		bearingValue.setVisibility(isNavigating ? View.VISIBLE : View.GONE);
		rootView.findViewById(R.id.bearinglt).setVisibility(isNavigating ? View.VISIBLE : View.GONE);
		// turn
		turnValue.setVisibility(isNavigating ? View.VISIBLE : View.GONE);
		rootView.findViewById(R.id.turnlt).setVisibility(isNavigating ? View.VISIBLE : View.GONE);
		// xtk
		xtkValue.setVisibility(isNavigatingViaRoute ? View.VISIBLE : View.GONE);
		rootView.findViewById(R.id.xtklt).setVisibility(isNavigatingViaRoute ? View.VISIBLE : View.GONE);

		// we hide elevation in portrait mode due to lack of space
		if (getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE)
		{
			if (isNavigatingViaRoute && elevationValue.getVisibility() == View.VISIBLE)
			{
				elevationValue.setVisibility(View.GONE);
				rootView.findViewById(R.id.elevationlt).setVisibility(View.GONE);

				ViewGroup row = (ViewGroup) rootView.findViewById(R.id.movingrow);
				int pos = row.indexOfChild(elevationValue);
				View xtklt = rootView.findViewById(R.id.xtklt);
				row.removeView(xtkValue);
				row.removeView(xtklt);
				row.addView(xtklt, pos);
				row.addView(xtkValue, pos);
				row.getParent().requestLayout();
			}
			else if (!isNavigatingViaRoute && elevationValue.getVisibility() == View.GONE)
			{
				elevationValue.setVisibility(View.VISIBLE);
				rootView.findViewById(R.id.elevationlt).setVisibility(View.VISIBLE);

				ViewGroup row = (ViewGroup) rootView.findViewById(R.id.movingrow);
				int pos = row.indexOfChild(xtkValue);
				View elevationlt = rootView.findViewById(R.id.elevationlt);
				row.removeView(elevationValue);
				row.removeView(elevationlt);
				row.addView(elevationlt, pos);
				row.addView(elevationValue, pos);
				row.getParent().requestLayout();
			}
		}

		if (isNavigatingViaRoute)
		{
			routeName.setText("\u21d2 " + application.navigationService.navRoute.name);
		}
		if (isNavigating)
		{
			waypointName.setText("\u2192 " + application.navigationService.navWaypoint.name);
		}

		updateMapViewArea();
		map.refreshMap();
	}

	private void onUpdateNavigationStatus()
	{
		if (!application.isNavigating())
			return;

		if (!map.isFollowing())
			map.refreshMap();

		long now = System.currentTimeMillis();
		
		double distance = application.navigationService.navDistance;
		double bearing = application.navigationService.navBearing;
		long turn = application.navigationService.navTurn;
		double vmg = application.navigationService.navVMG;
		int ete = application.navigationService.navETE;

		String[] dist = StringFormatter.distanceC(distance, StringFormatter.precisionFormat);
		String eteString = (ete == Integer.MAX_VALUE) ? getString(R.string.never) : (String) DateUtils.getRelativeTimeSpanString(now + (ete + 1) * 60000, now, DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
		String extra = StringFormatter.speedH(vmg) + " | " + eteString;

		String trnsym = "";
		if (turn > 0)
		{
			trnsym = "R";
		}
		else if (turn < 0)
		{
			trnsym = "L";
			turn = -turn;
		}

		bearing = application.fixDeclination(bearing);
		distanceValue.setText(dist[0]);
		distanceUnit.setText(dist[1]);
		bearingValue.setText(StringFormatter.angleC(bearing));
		turnValue.setText(StringFormatter.angleC(turn) + trnsym);
		waypointExtra.setText(extra);

		if (application.navigationService.isNavigatingViaRoute())
		{
			View rootView = getView();
			boolean hasNext = application.navigationService.hasNextRouteWaypoint();
			if (distance < application.navigationService.navProximity * 3 && !animationSet)
			{
				AnimationSet animation = new AnimationSet(true);
				animation.addAnimation(new AlphaAnimation(1.0f, 0.3f));
				animation.addAnimation(new AlphaAnimation(0.3f, 1.0f));
				animation.setDuration(500);
				animation.setRepeatCount(10);
				rootView.findViewById(R.id.waypointinfo).startAnimation(animation);
				if (!hasNext)
				{
					rootView.findViewById(R.id.routeinfo).startAnimation(animation);
				}
				animationSet = true;
			}
			else if (animationSet)
			{
				rootView.findViewById(R.id.waypointinfo).setAnimation(null);
				if (!hasNext)
				{
					rootView.findViewById(R.id.routeinfo).setAnimation(null);
				}
				animationSet = false;
			}

			if (application.navigationService.navXTK == Double.NEGATIVE_INFINITY)
			{
				xtkValue.setText("--");
				xtkUnit.setText("--");
			}
			else
			{
				String xtksym = application.navigationService.navXTK == 0 ? "" : application.navigationService.navXTK > 0 ? "R" : "L";
				String[] xtks = StringFormatter.distanceC(Math.abs(application.navigationService.navXTK));
				xtkValue.setText(xtks[0] + xtksym);
				xtkUnit.setText(xtks[1]);
			}

			double navDistance = application.navigationService.navRouteDistanceLeft();
			int eta = application.navigationService.navRouteETE(navDistance);
			if (eta < Integer.MAX_VALUE)
				eta += application.navigationService.navETE;
			String etaString = (eta == Integer.MAX_VALUE) ? getString(R.string.never) : (String) DateUtils.getRelativeTimeSpanString(now + (eta + 1) * 60000, now, DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
			extra = StringFormatter.distanceH(navDistance + distance, 1000) + " | " + etaString;
			routeExtra.setText(extra);
		}
	}

	@Override
	public void mapTapped()
	{
		updatePanels();
	}

	@Override
	public boolean waypointTapped(Waypoint waypoint, int x, int y)
	{
		try
		{
			if (application.editingRoute != null)
			{
				waypointSelected = application.getWaypointIndex(waypoint);
				showPopupMenu(R.menu.routeeditwaypoint_menu, x, y);
				return true;
			}
			else
			{
				waypointActionsCallback.onWaypointShow(waypoint);
				return true;
			}
		}
		catch (Exception e)
		{
			return false;
		}
	}

	@Override
	public boolean routeWaypointTapped(Route route, int index, int x, int y)
	{
		if (route == application.editingRoute)
		{
			routeActionsCallback.onRouteWaypointEdit(route, route.getWaypoint(index));
		}
		else if (application.isNavigatingViaRoute() && application.navigationService.navRoute == route)
		{
			waypointSelected = index;
			showPopupMenu(R.menu.routewaypointnavigationonmap_menu, x, y);
		}
		else
		{
			routeActionsCallback.onRouteDetails(route);
		}
		return true;
	}

	@Override
	public boolean mapObjectTapped(long id, int x, int y)
	{
		mapObjectSelected = id;
		showPopupMenu(R.menu.mapobject_menu, x, y);
		return false;
	}

	@Override
	public void setFollowing(boolean follow)
	{
		if (follow && ! map.isFixed())
			return;

		followOnLocation = false;
		if (application.editingRoute == null && application.editingTrack == null)
		{
			if (showDistance > 0 && application.overlayManager.distanceOverlay != null)
			{
				if (showDistance == 2 && !follow)
				{
					application.overlayManager.distanceOverlay.setAncor(application.getLocation());
					application.overlayManager.distanceOverlay.setEnabled(true);
				}
				else
				{
					application.overlayManager.distanceOverlay.setEnabled(false);
				}
			}
			following = follow;
			if (map != null)
				map.setFollowing(following);
		}
		getActivity().supportInvalidateOptionsMenu();
	}

	@Override
	public void zoomMap(final float factor)
	{
		wait(new Waitable()
		{
			@Override
			public void waitFor()
			{
				application.zoomBy(factor);
			}
		});
	}

	@Override
	public void onTileObtained()
	{
		refreshMap();
	}

	@Override
	public void refreshMap()
	{
		if (map == null)
			return;
		map.refreshMap();
	}

	@Override
	public synchronized void conditionsChanged()
	{
		if (map == null)
			return;
		map.updateMapInfo();
		map.updateMapCenter();
	}

	@Override
	public synchronized void mapChanged(boolean forced)
	{
		if (map == null)
			return;
		if (forced)
			map.suspendBestMap();
		map.updateMapInfo();
		map.updateMapCenter();
		BaseMap newMap = application.getCurrentMap();
		if (newMap.getMPP() != newMap.getAbsoluteMPP())
		{
			boolean resetZoom = false;
			switch (forceZoomMap)
			{
				case 0:
					application.setZoom(1.);
					break;
				case 1:
					resetZoom = true;
				case 2:
					int text = resetZoom ? R.string.gently_reset_zoom : R.string.gently_keep_zoom;
					final boolean finalResetZoom = resetZoom;
					final Snackbar snackbar = Snackbar.make(getView(), text, Snackbar.LENGTH_LONG);
					snackbar.setAction(R.string.undo, new View.OnClickListener()
					{
						@Override
						public void onClick(View v)
						{
							// We need just a button
						}
					});
					snackbar.setCallback(new Snackbar.Callback()
					{
						@Override
						public void onDismissed(Snackbar snackbar, int event)
						{
							super.onDismissed(snackbar, event);
							if (event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)
								return;
							boolean setZoom = finalResetZoom;
							if (event == Snackbar.Callback.DISMISS_EVENT_ACTION)
								setZoom = !setZoom;
							if (setZoom)
								application.setZoom(1.);
						}
					});
					snackbar.show();
					break;
				case 3:
					break;
			}
		}
	}

	private void updatePanels()
	{
		long now = SystemClock.uptimeMillis();
		boolean changed = false;

		if ((now < map.lastDragTime + mapInfoHideDelay))
		{
			if (mapInfo.getVisibility() != View.VISIBLE)
			{
				mapInfo.setVisibility(View.VISIBLE);
				if (mapButtonsVisible)
					mapButtons.setVisibility(View.VISIBLE);
				changed = true;
			}
		}
		else
		{
			if (mapInfo.getVisibility() != View.GONE)
			{
				mapInfo.setVisibility(View.GONE);
				mapButtons.setVisibility(View.GONE);
				changed = true;
			}
		}
		if ((now < map.lastDragTime + satInfoHideDelay))
		{
			if (satInfo.getVisibility() != View.VISIBLE)
			{
				satInfo.setVisibility(View.VISIBLE);
				changed = true;
			}
		}
		else
		{
			if (satInfo.getVisibility() != View.GONE)
			{
				satInfo.setVisibility(View.GONE);
				changed = true;
			}
		}
		if ((now < map.lastDragTime + navInfoHideDelay))
		{
			if (navInfo.getVisibility() != View.VISIBLE)
			{
				navInfo.setVisibility(View.VISIBLE);
				changed = true;
			}
		}
		else
		{
			if (navInfo.getVisibility() != View.GONE)
			{
				navInfo.setVisibility(View.GONE);
				changed = true;
			}
		}

		if (changed)
			updateMapViewArea();
	}

	private void updateEditStatus()
	{
		if (application.editingRoute != null)
		{
			getView().findViewById(R.id.editroute).setVisibility(View.VISIBLE);
		//if (showDistance > 0)
		//	application.overlayManager.distanceOverlay.setEnabled(false);
			setFollowing(false);		
		}
	}

	private void updateGPSStatus()
	{
		int v = map.isMoving() && application.editingRoute == null && application.editingTrack == null ? View.VISIBLE : View.GONE;
		View view = getView().findViewById(R.id.movinginfo);
		if (view.getVisibility() != v)
		{
			view.setVisibility(v);
			updateMapViewArea();
		}
	}

	@Override
	public void updateCoordinates(double[] latlon)
	{
		// TODO strange situation, needs investigation
		if (application != null)
		{
			final String pos = StringFormatter.coordinates(" ", latlon[0], latlon[1]);
			getActivity().runOnUiThread(new Runnable() {

				@Override
				public void run()
				{
					coordinates.setText(pos);
				}
			});
		}
	}

	@Override
	public void updateFileInfo()
	{
		final String title = application.getMapTitle();
		final String license = application.getMapLicense();
		getActivity().runOnUiThread(new Runnable()
		{

			@Override
			public void run()
			{
				if (title != null)
				{
					currentFile.setText(title);
				}
				else
				{
					currentFile.setText("-no map-");
				}
				if (license != null)
				{
					mapLicense.setText(Html.fromHtml(license));
					mapLicense.setVisibility(View.VISIBLE);
				}
				else
				{
					mapLicense.setText(null);
					mapLicense.setVisibility(View.GONE);
				}
				updateMapViewArea();

				double zoom = application.getZoom() * 100;

				if (zoom == 0.0)
				{
					mapZoom.setText("---%");
				}
				else
				{
					int rz = (int) Math.floor(zoom);
					String zoomStr = zoom - rz != 0.0 ? String.format(Locale.getDefault(), "%.1f", zoom) : String.valueOf(rz);
					mapZoom.setText(zoomStr + "%");
				}

				// ImageButton zoomin = (ImageButton) findViewById(R.id.zoomin);
				// ImageButton zoomout = (ImageButton) findViewById(R.id.zoomout);
				// zoomin.setEnabled(application.getNextZoom() != 0.0);
				// zoomout.setEnabled(application.getPrevZoom() != 0.0);

				// LightingColorFilter disable = new LightingColorFilter(0xFFFFFFFF, 0xFF444444);

				// zoomin.setColorFilter(zoomin.isEnabled() ? null : disable);
				// zoomout.setColorFilter(zoomout.isEnabled() ? null : disable);
			}
		});
	}

	private void dimScreen()
	{
		int color = Color.TRANSPARENT;
		if (autoDim && application.lastKnownLocation != null)
		{
			Calendar now = GregorianCalendar.getInstance(TimeZone.getDefault());
			if (!Astro.isDaytime(application.getZenith(), application.lastKnownLocation, now))
				color = dimValue << 57; // value * 2 and shifted to transparency octet
		}
		dimView.setBackgroundColor(color);
	}

	@Override
	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
	{
		Resources resources = getResources();
		if (getString(R.string.pref_wakelock).equals(key))
		{
			keepScreenOn = sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_wakelock));
			map.setKeepScreenOn(keepScreenOn);
		}
		else if (getString(R.string.pref_showdistance_int).equals(key))
		{
			showDistance = Integer.parseInt(sharedPreferences.getString(key, getString(R.string.def_showdistance)));
		}
		else if (getString(R.string.pref_hidemapinfo).equals(key))
		{
			mapInfoHideDelay = Integer.parseInt(sharedPreferences.getString(key, "2147483647"));
		}
		else if (getString(R.string.pref_hidesatinfo).equals(key))
		{
			satInfoHideDelay = Integer.parseInt(sharedPreferences.getString(key, "2147483647"));
		}
		else if (getString(R.string.pref_hidenavinfo).equals(key))
		{
			navInfoHideDelay = Integer.parseInt(sharedPreferences.getString(key, "2147483647"));
		}
		else if (getString(R.string.pref_maprenderinterval).equals(key))
		{
			updatePeriod = sharedPreferences.getInt(key, resources.getInteger(R.integer.def_maprenderinterval)) * 100;
		}
		else if (getString(R.string.pref_mapfollowonloc).equals(key))
		{
			followOnLocation = sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_mapfollowonloc));
		}
		else if (getString(R.string.pref_mapdiminterval).equals(key))
		{
			dimInterval = sharedPreferences.getInt(key, resources.getInteger(R.integer.def_mapdiminterval)) * 1000;
		}
		else if (getString(R.string.pref_mapdimvalue).equals(key))
		{
			dimValue = sharedPreferences.getInt(key, resources.getInteger(R.integer.def_mapdimvalue));
		}		
		else if (getString(R.string.pref_mapdim).equals(key))
		{
			autoDim = sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_mapdim));
			dimScreen();
		}
		else if (getString(R.string.pref_unfollowontap).equals(key))
		{
			map.setStrictUnfollow(!sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_unfollowontap)));
		}
		else if (getString(R.string.pref_lookahead).equals(key))
		{
			map.setLookAhead(sharedPreferences.getInt(key, resources.getInteger(R.integer.def_lookahead)));
		}
		else if (getString(R.string.pref_mapbest).equals(key))
		{
			map.setBestMapEnabled(sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_mapbest)));
		}
		else if (getString(R.string.pref_mapbestinterval).equals(key))
		{
			map.setBestMapInterval(sharedPreferences.getInt(key, resources.getInteger(R.integer.def_mapbestinterval)) * 1000);
		}
		else if (getString(R.string.pref_mapforcezoom).equals(key))
		{
			forceZoomMap = Integer.parseInt(sharedPreferences.getString(key, "1"));
		}
		else if (getString(R.string.pref_maprotation).equals(key))
		{
			int rotation = Integer.parseInt(sharedPreferences.getString(key, resources.getString(R.string.def_maprotation)));
			map.setMapRotation(rotation);
		}
		else if (getString(R.string.pref_scalebarbg).equals(key))
		{
			map.setDrawScaleBarBackground(sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_scalebarbg)));
		}
		else if (getString(R.string.pref_scalebarcolor).equals(key))
		{
			map.setScaleBarColor(sharedPreferences.getInt(key, resources.getColor(R.color.scalebar)));
		}
		else if (getString(R.string.pref_scalebarbgcolor).equals(key))
		{
			map.setScaleBarBackgroundColor(sharedPreferences.getInt(key, resources.getColor(R.color.scalebarbg)));
		}
		else if (getString(R.string.pref_hidemapcross).equals(key))
		{
			int delay = Integer.parseInt(sharedPreferences.getString(key, "5"));
			map.setCrossCursorHideDelay(delay);
		}
		else if (getString(R.string.pref_mapcrosscolor).equals(key))
		{
			map.setCrossColor(sharedPreferences.getInt(key, resources.getColor(R.color.mapcross)));
		}
		else if (getString(R.string.pref_cursorvector).equals(key) || getString(R.string.pref_cursorvectormlpr).equals(key))
		{
			map.setCursorVector(Integer.parseInt(sharedPreferences.getString(getString(R.string.pref_cursorvector), getString(R.string.def_cursorvector))),
					sharedPreferences.getInt(getString(R.string.pref_cursorvectormlpr), resources.getInteger(R.integer.def_cursorvectormlpr)));
		}
		else if (getString(R.string.pref_cursorcolor).equals(key))
		{
			map.setCursorColor(sharedPreferences.getInt(key, resources.getColor(R.color.cursor)));
		}
		else if (getString(R.string.pref_navigation_proximity).equals(key))
		{
			map.setProximity(Integer.parseInt(sharedPreferences.getString(key, getString(R.string.def_navigation_proximity))));
		}
		else if (getString(R.string.pref_unitspeed).equals(key))
		{
			speedUnit.setText(StringFormatter.speedAbbr);
		}
		else if (getString(R.string.pref_unitelevation).equals(key))
		{
			elevationUnit.setText(StringFormatter.elevationAbbr);
		}
		else if (getString(R.string.pref_unitangle).equals(key))
		{
			trackUnit.setText(StringFormatter.angleAbbr);
			bearingUnit.setText(StringFormatter.angleAbbr);
			turnUnit.setText(StringFormatter.angleAbbr);
		}
	}

	@Override
	public void onClick(View v)
	{
		switch (v.getId())
		{
			case R.id.zoomin:
				// TODO Show toast here
				if (application.getNextZoom() == 0.0)
					return;
				wait(new Waitable() {
					@Override
					public void waitFor()
					{
						application.zoomIn();
					}
				});
				break;
			case R.id.zoomout:
				if (application.getPrevZoom() == 0.0)
					return;
				wait(new Waitable() {
					@Override
					public void waitFor()
					{
						application.zoomOut();
					}
				});
				break;
			case R.id.prevmap:
				wait(new Waitable() {
					@Override
					public void waitFor()
					{
						application.prevMap();
					}
				});
				break;
			case R.id.nextmap:
				wait(new Waitable() {
					@Override
					public void waitFor()
					{
						application.nextMap();
					}
				});
				break;
			case R.id.coordinates:
			{
				showPopupMenu(R.menu.location_menu, v);
				break;
			}
			case R.id.sats:
			{
				if (application.gpsEnabled)
				{
					GPSInfo dialog = new GPSInfo();
					dialog.show(getFragmentManager(), "dialog");
				}
				break;
			}
			case R.id.currentfile:
			{
				SuitableMapsList dialog = new SuitableMapsList();
				dialog.show(getFragmentManager(), "dialog");
				break;
			}
			case R.id.currentzoom:
			{
				mapButtonsVisible = ! mapButtonsVisible;
				mapButtons.setVisibility(mapButtonsVisible ? View.VISIBLE : View.GONE);
				updateMapViewArea();
				// save panel state
				Editor editor = PreferenceManager.getDefaultSharedPreferences(application).edit();
				editor.putBoolean(getString(R.string.ui_mapbuttons_shown), mapButtonsVisible);
				editor.commit();
				break;
			}
			case R.id.waypointname:
			{
				if (application.isNavigatingViaRoute())
				{
					routeActionsCallback.onRouteDetails(application.navigationService.navRoute);
				}
				break;
			}
			case R.id.addpoint:
			{
				double[] aloc = application.getMapCenter();
				application.routeEditingWaypoints.push(application.editingRoute.addWaypoint("RWPT" + application.editingRoute.length(), aloc[0], aloc[1]));
				refreshMap();
				break;
			}
			case R.id.insertpoint:
			{
				double[] iloc = application.getMapCenter();
				application.routeEditingWaypoints.push(application.editingRoute.insertWaypoint("RWPT" + application.editingRoute.length(), iloc[0], iloc[1]));
				refreshMap();
				break;
			}
			case R.id.removepoint:
			{
				if (!application.routeEditingWaypoints.empty())
				{
					application.editingRoute.removeWaypoint(application.routeEditingWaypoints.pop());
					refreshMap();
				}
				break;
			}
			case R.id.orderpoints:
			{
				RouteEdit dialog = new RouteEdit(application.editingRoute);
				dialog.show(getFragmentManager(), "dialog");
				break;
			}
			case R.id.finishrouteedit:
			{
				if ("New route".equals(application.editingRoute.name))
				{
					SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault());
					application.editingRoute.name = formatter.format(new Date());
				}
				application.editingRoute.editing = false;
				application.dispatchRoutePropertiesChanged(application.editingRoute);
				application.editingRoute = null;
				application.routeEditingWaypoints = null;
				getView().findViewById(R.id.editroute).setVisibility(View.GONE);
				updateGPSStatus();
				if (showDistance == 2)
				{
					application.overlayManager.distanceOverlay.setEnabled(true);
				}
				updateMapViewArea();
				refreshMap();
				map.requestFocus();
				break;
			}
		}
	}

	private final Handler zoomHandler = new Handler();

	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event)
	{
		switch (event.getAction())
		{
			case MotionEvent.ACTION_DOWN:
				zoom100X = (int) event.getRawX();
				zoom100Y = (int) event.getRawY();
				zoomHandler.postDelayed(new Runnable() {
					@Override
					public void run()
					{
						zoom100X = 0;
						zoom100Y = 0;
					}
				}, 2000);
				break;
			case MotionEvent.ACTION_UP:
				int dx = Math.abs((int)event.getRawX() - zoom100X);
				int dy = (int)event.getRawY() - zoom100Y;
				int h = v.getHeight();
				int w = v.getWidth() >> 1;
				if (dy > h * 0.8 && dy < h * 2 && dx < w)
				{
					wait(new Waitable() {
						@Override
						public void waitFor()
						{
							application.setZoom(1.);
						}});
					zoom100X = 0;
					zoom100Y = 0;
				}
				break;
		}
		return false;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		switch (item.getItemId())
		{
			case R.id.action_add_waypoint:
				double[] loc = application.getMapCenter();
				Waypoint waypoint = new Waypoint("", "", loc[0], loc[1]);
				waypoint.date = Calendar.getInstance().getTime();
				int wpt = application.addWaypoint(waypoint);
				waypoint.name = "WPT" + wpt;
				application.saveDefaultWaypoints();
				refreshMap();
				return true;
			case R.id.action_follow:
				setFollowing(!following);
				return true;
			case R.id.action_next_nav_point:
				application.navigationService.nextRouteWaypoint();
				return true;
			case R.id.action_prev_nav_point:
				application.navigationService.prevRouteWaypoint();
				return true;
			case R.id.action_stop_navigation:
				application.stopNavigation();
				return true;
			case R.id.action_search:
				getActivity().onSearchRequested();
				return true;
			case R.id.action_locate:
			{
				final Location l = application.getLastKnownSystemLocation();
				final long now = System.currentTimeMillis();
				final long fixed = l != null ? l.getTime() : 0L;
				if ((now - fixed) < 1000 * 60 * 60 * 12) // we do not take into account locations older then 12 hours
				{
					wait(new Waitable() {
						@Override
						public void waitFor()
						{
							application.ensureVisible(l.getLatitude(), l.getLongitude());
							getActivity().runOnUiThread(new Runnable() {
								@Override
								public void run()
								{
									Toast.makeText(application, DateUtils.getRelativeTimeSpanString(fixed, now, DateUtils.SECOND_IN_MILLIS), Toast.LENGTH_SHORT).show();
								}
							});
						}
					});
				}
				else
				{
					Toast.makeText(application, getString(R.string.msg_nolastknownlocation), Toast.LENGTH_LONG).show();
				}
				return true;
			}
			case R.id.action_locating:
				application.enableLocating(!application.isLocating());
				return true;
			case R.id.action_tracking:
				application.enableTracking(!application.isTracking());
				return true;
			default:
				return super.onOptionsItemSelected(item);
		}
	}

	@Override
	public boolean onMenuItemSelected(MenuBuilder builder, MenuItem item)
	{
		switch (item.getItemId())
		{
			case R.id.action_information:
			{
				FragmentManager manager = getFragmentManager();
				LocationInfo dialog = new LocationInfo(application.getMapCenter());
				dialog.show(manager, "dialog");
				return true;
			}
			case R.id.action_share:
			{
				Intent i = new Intent(android.content.Intent.ACTION_SEND);
				i.setType("text/plain");
				i.putExtra(Intent.EXTRA_SUBJECT, R.string.currentloc);
				double[] loc = application.getMapCenter();
				String spos = StringFormatter.coordinates(" ", loc[0], loc[1]);
				i.putExtra(Intent.EXTRA_TEXT, spos);
				startActivity(Intent.createChooser(i, getString(R.string.menu_share)));
				return true;
			}
			case R.id.action_view_elsewhere:
			{
				double[] sloc = application.getMapCenter();
				String geoUri = "geo:" + Double.toString(sloc[0]) + "," + Double.toString(sloc[1]);
				Intent intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(geoUri));
				startActivity(intent);
				return true;
			}
			case R.id.action_copy_location:
			{
				double[] cloc = application.getMapCenter();
				String cpos = StringFormatter.coordinates(" ", cloc[0], cloc[1]);
				Clipboard.copy(getActivity(), cpos);
				return true;
			}
			case R.id.action_paste_location:
			{
				String text = Clipboard.paste(getActivity());
				try
				{
					double c[] = CoordinateParser.parse(text);
					if (!Double.isNaN(c[0]) && !Double.isNaN(c[1]))
					{
						boolean mapChanged = application.setMapCenter(c[0], c[1], true, true, false);
						if (mapChanged)
							map.updateMapInfo();
						map.updateMapCenter();
						following = false;
						map.setFollowing(false);
					}
				}
				catch (IllegalArgumentException e)
				{
				}
				return true;
			}
			case R.id.action_add_to_route:
			{
				Waypoint wpt = application.getWaypoint(waypointSelected);
				application.routeEditingWaypoints.push(application.editingRoute.addWaypoint(wpt.name, wpt.latitude, wpt.longitude));
				refreshMap();
				return true;
			}
			case R.id.action_navigate:
			{
				application.navigationService.setRouteWaypoint(waypointSelected);
				return true;				
			}
			case R.id.action_mapobject_navigate:
			{
				application.startNavigation(application.getMapObject(mapObjectSelected));				
				return true;				
			}
		}
		return false;
	}

	@Override
	public void onMenuModeChange(MenuBuilder builder)
	{
	}

	@Override
	public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing)
	{
		anchor.setVisibility(View.GONE);
		waypointSelected = -1;
		mapObjectSelected = -1;		
	}

	@Override
	public boolean onOpenSubMenu(MenuBuilder menu)
	{
		return false;
	}

	@SuppressLint("NewApi")
	private void showPopupMenu(int resId, int x, int y)
	{
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
		{
			anchor.setX(x);
			anchor.setY(y);
		}
		else
		{
			//TODO Test it!
			RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
			params.leftMargin = x;
			params.topMargin = y;
			anchor.setLayoutParams(params);
		}
		anchor.setVisibility(View.VISIBLE);
		showPopupMenu(resId, anchor);
	}

	private void showPopupMenu(int resId, View v)
	{
		// https://gist.github.com/mediavrog/9345938#file-iconizedmenu-java-L55
		MenuBuilder menu;
		MenuPopupHelper popup;
		Activity activity = getActivity();
		menu = new MenuBuilder(activity);
		menu.setCallback(this);
		popup = new MenuPopupHelper(activity, menu, v);
		popup.setCallback(this);
		popup.setForceShowIcon(true);
		new SupportMenuInflater(activity).inflate(resId, menu);
		popup.show();
	}

	private void wait(final Waitable w)
	{
		waitBar.setVisibility(View.VISIBLE);
		waitBar.setText(R.string.msg_wait);
		executorThread.execute(new Runnable() {
			public void run()
			{
				w.waitFor();
				finishHandler.sendEmptyMessage(0);
			}
		});
	}

	private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent)
		{
			String action = intent.getAction();
			if (action.equals(NavigationService.BROADCAST_NAVIGATION_STATE))
			{
				onUpdateNavigationState();
				getActivity().supportInvalidateOptionsMenu();
			}
			else if (action.equals(NavigationService.BROADCAST_NAVIGATION_STATUS))
			{
				onUpdateNavigationStatus();
				getActivity().supportInvalidateOptionsMenu();
			}
			else if (action.equals(LocationService.BROADCAST_LOCATING_STATUS))
			{
				if (!application.isLocating())
					map.clearLocation();
			}
			// In fact this is not needed on modern devices through activity is always
			// paused when the screen is turned off. But we will keep it, may be there
			// exist some devices (ROMs) that do not pause activities.
			else if (action.equals(Intent.ACTION_SCREEN_OFF))
			{
				map.pause();
			}
			else if (action.equals(Intent.ACTION_SCREEN_ON))
			{
				map.resume();
			}
		}
	};

	@SuppressLint("HandlerLeak")
	private class FinishHandler extends Handler
	{
		private final WeakReference<MapFragment> target;

		FinishHandler(MapFragment fragment)
		{
			this.target = new WeakReference<MapFragment>(fragment);
		}

		@Override
		public void handleMessage(Message msg)
		{
			MapFragment mapFragment = target.get();
			if (mapFragment != null)
			{
				mapFragment.waitBar.setVisibility(View.INVISIBLE);
				mapFragment.waitBar.setText("");
			}
		}
	}
	
	private interface Waitable
	{
		public void waitFor();
	}
}