package com.ramos.munoz.tesis;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.TextView;

import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.InfoWindowAdapter;
import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener;
import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener;
import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.LocationSource.OnLocationChangedListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;

public class Mapa implements LocationListener, InfoWindowAdapter, OnInfoWindowClickListener, OnMarkerClickListener, OnMapLongClickListener {

    protected static long INTERVALO_ACTUALIZACION;
    private GoogleMap mapa = null;
    private LocationManager locationManager;
    private String proveedor;
    private SharedPreferences preferencias;
    private FragmentActivity contexto;
    private double latitud;
    private double longitud;

    private double latitudPunto;
    private double longitudPunto;
    private int numEstacionamientos = 0;

    private HashMap<Integer, Marker> estacionamientosVisibles = new HashMap<Integer, Marker>();
    private List<Estacionamiento> estacionamientos;
    private Estacionamiento estacionamientoItem;
    private PolylineOptions rutaDibujada;
    private Polyline linea;
    private Criteria criterio;
    private Circle circulo;
    private Boolean mostrandoRuta = false;
    private Marker centroCirculo;
    public boolean vistasIncluidas;
    private boolean mostrandoCirculo;
    private Handler buscarHandler;
    private static boolean primerLog = true;

    public Mapa(FragmentActivity contexto) {
	this.contexto = contexto;
	estacionamientos = new ArrayList<Estacionamiento>();
	buscarHandler = new Handler();

	iniciarBuscarTask();
    }

    public Marker addMarker(MarkerOptions options) {
	return mapa.addMarker(options);
    }

    public GoogleMap getGoogleMap() {
	return mapa;
    }

    public void getMapa() {
	String tag = "getMapa";
	Log.d(tag, "inicio");
	locationManager = (LocationManager) contexto.getSystemService(Context.LOCATION_SERVICE);
	if (mapa == null) {
	    Log.d(tag, "mapa es null");
	    mapa = ((SupportMapFragment) contexto.getSupportFragmentManager().findFragmentById(R.id.mapa)).getMap();
	    mapa.setMyLocationEnabled(true);
	    mapa.setOnMapLongClickListener(this);
	}
	preferencias = PreferenceManager.getDefaultSharedPreferences(contexto);

	setCriterio();
	proveedor = locationManager.getBestProvider(criterio, true);
	locationManager.requestLocationUpdates(proveedor, 10000, 10, this);
	mapa.setOnCameraChangeListener(getCameraChangeListener());
	Log.i(tag, "fin | Proveedor: " + proveedor.toString());

    }

    private void setCriterio() {
	criterio = new Criteria();
	criterio.setAccuracy(Criteria.ACCURACY_FINE);
	criterio.setAltitudeRequired(false);
	criterio.setBearingRequired(false);
	criterio.setCostAllowed(true);
	criterio.setPowerRequirement(Criteria.POWER_MEDIUM);
    }

    public void removerCambios() {
	if (locationManager != null) {
	    locationManager.removeUpdates(this);
	}
    }

    public void activarCambios() {
	if (locationManager != null) {
	    int tipoMapa = Integer.parseInt(preferencias.getString("listMapa", "1"));
	    Log.d("TIPOMAPA", "" + tipoMapa);
	    mapa.setMapType(tipoMapa);
	    mapa.setMyLocationEnabled(true);
	    actualizarIntervalo();
	}
    }

    public boolean estaGPSActivado() {
	boolean gpsActivado;

	if (locationManager != null) {
	    gpsActivado = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
	    if (!gpsActivado) {
		return false;
	    }
	}
	return true;
    }

    public void onLocationChanged(Location l) {
	latitud = l.getLatitude();
	longitud = l.getLongitude();
	if (mostrandoRuta || primerLog) {
	    LatLng latLng = new LatLng(l.getLatitude(), l.getLongitude());
	    CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, 15);
	    mapa.animateCamera(cameraUpdate);
	    primerLog = false;
	}
    }

    public void animarCamara(LatLng loc) {
	mapa.animateCamera(CameraUpdateFactory.newLatLngZoom(loc, 16));
    }

    public void onProviderDisabled(String arg0) {

    }

    public void onProviderEnabled(String arg0) {

    }

    public void onStatusChanged(String arg0, int arg1, Bundle arg2) {

    }

    public void onCameraChange(CameraPosition position) {

    }

    public void activate(OnLocationChangedListener listener) {

    }

    public void deactivate() {
    }

    public void onMapLongClick(LatLng punto) {

	this.latitudPunto = punto.latitude;
	this.longitudPunto = punto.longitude;
	Intent menu = new Intent(contexto, MapaTapMenu.class);
	contexto.startActivityForResult(menu, MapaActivity.RESPUESTA_RADIO);
    }

    public OnCameraChangeListener getCameraChangeListener() {
	return new OnCameraChangeListener() {
	    public void onCameraChange(CameraPosition position) {
		agregarEstacionamientos(estacionamientos);
	    }
	};
    }

    public void removerCirulo() {
	Log.d("removerCirulo", "inicio");
	if (mostrandoCirculo) {
	    this.centroCirculo.remove();
	    this.circulo.remove();
	    this.circulo = null;
	    mostrandoCirculo = false;
	}
    }

    public void agregarCirculo(CircleOptions circulo) {
	if (this.circulo != null) {
	    removerCirulo();

	}
	this.circulo = mapa.addCircle(circulo);
	MarkerOptions markerCentro = new MarkerOptions();
	markerCentro.position(circulo.getCenter());
	markerCentro.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE));

	this.centroCirculo = mapa.addMarker(markerCentro);
    }

    public void agregarEstacionamientos(List<Estacionamiento> items) {
	if (mapa != null) {
	    LatLngBounds bounds = mapa.getProjection().getVisibleRegion().latLngBounds;

	    for (Estacionamiento item : items) {
		estacionamientoItem = item;
		LatLng posicion = new LatLng(estacionamientoItem.getLatitud(), estacionamientoItem.getLongitud());
		if (bounds.contains(posicion)) {
		    if (!estacionamientosVisibles.containsKey(estacionamientoItem.getId())) {

			mapa.setInfoWindowAdapter(this);
			mapa.setOnInfoWindowClickListener(this);
			mapa.setOnMarkerClickListener(this);
			estacionamientosVisibles.put(estacionamientoItem.getId(), mapa.addMarker(estacionamientoItem.getMarcador()));
		    }
		} else {
		    if (estacionamientosVisibles.containsKey(item.getId())) {
			estacionamientosVisibles.get(item.getId()).remove();
			estacionamientosVisibles.remove(item.getId());
			if (estacionamientoItem.getDisponibilidad().equals("Si")) {
			    estacionamientoItem.setMarkerIcon(BitmapDescriptorFactory.fromResource(R.drawable.ic_est_disponible));

			} else {
			    estacionamientoItem.setMarkerIcon(BitmapDescriptorFactory.fromResource(R.drawable.ic_estacionamiento));
			}
		    }
		}
	    }
	}
    }

    public HashMap<Integer, Marker> getEstacionamientosVisibles() {
	return estacionamientosVisibles;
    }

    public void setEstacionamientosVisibles(HashMap<Integer, Marker> estacionamientosVisibles) {
	this.estacionamientosVisibles = estacionamientosVisibles;
    }

    public void onInfoWindowClick(final Marker marker) {
	Log.d("onInfoWindowClick", "inicio");
	String calcRuta = contexto.getString(R.string.calcularRuta);
	String verDetalle = contexto.getString(R.string.verDetalle);
	final String[] itemsMenu = { calcRuta, verDetalle };
	final String[] datosEstacionamiento = marker.getSnippet().split(";");
	AlertDialog.Builder menu = new AlertDialog.Builder(contexto);
	menu.setCancelable(true);
	menu.setTitle(marker.getTitle().toString());
	menu.setItems(itemsMenu, new DialogInterface.OnClickListener() {
	    public void onClick(DialogInterface dialog, int item) {
		switch (item) {
		    case 0: {

			calcularRuta(marker);
		    }
			break;
		    case 1: {
			Intent detalle = new Intent(contexto, MapaDetalleEstacionamiento.class);
			Bundle bolsa = new Bundle();
			bolsa.putInt("id", Integer.parseInt(datosEstacionamiento[3]));
			detalle.putExtras(bolsa);
			contexto.startActivity(detalle);
		    }
			break;
		}

	    }
	});
	menu.show();
    }

    public View getInfoWindow(Marker marker) {
	return null;
    }

    public void removerRuta() {
	if (rutaDibujada != null) {
	    rutaDibujada = null;
	    mostrandoRuta = false;
	    linea.remove();

	}
    }

    public void removerVistas() {
	if (vistasIncluidas) {
	    final FrameLayout fl = (FrameLayout) contexto.findViewById(R.id.flMapaActivity);
	    fl.removeViewAt(1);
	    vistasIncluidas = false;
	}
    }

    public boolean onMarkerClick(Marker marker) {
	if (!marker.equals(centroCirculo)) {
	    marker.showInfoWindow();

	}
	return true;
    }

    public View getInfoContents(Marker marker) {
	String[] snip = marker.getSnippet().split(";");
	View v = contexto.getLayoutInflater().inflate(R.layout.mapa_infoview, null);
	TextView nombre = (TextView) v.findViewById(R.id.txtNombre);
	TextView disponibilidad = (TextView) v.findViewById(R.id.txtDisponibilidad);
	TextView editado = (TextView) v.findViewById(R.id.txtFechaEdicion);

	nombre.setText(snip[0]);
	disponibilidad.setText(contexto.getString(R.string.disponibilidad) + " " + snip[1]);
	editado.setText(contexto.getString(R.string.fechaDisponibilidad) + " " + snip[2]);
	return v;
    }

    public void calcularRuta(Marker marker) {
	LatLng start = new LatLng(latitud, longitud);
	LatLng dest = marker.getPosition();
	Ruta ruta = new Ruta();
	Object[] datos = { start, dest, ruta };

	CalcularRutaAsincrona tareaCalcularRuta = new CalcularRutaAsincrona();
	tareaCalcularRuta.execute(datos);
    }

    public boolean isVistasIncluidas() {
	return vistasIncluidas;
    }

    public boolean isMostrandoCirculo() {
	return mostrandoCirculo;
    }

    public void setMostrandoCirculo(boolean mostrandoCirculo) {
	this.mostrandoCirculo = mostrandoCirculo;
    }

    public void setVistasIncluidas(boolean vistasIncluidas) {
	this.vistasIncluidas = vistasIncluidas;
    }

    public Boolean getMostrandoRuta() {
	return mostrandoRuta;
    }

    public double getLatitud() {
	return latitud;
    }

    public void setLatitud(double latitud) {
	this.latitud = latitud;
    }

    public double getLongitud() {
	return longitud;
    }

    public void setLongitud(double longitud) {
	this.longitud = longitud;
    }

    public double getLatitudPunto() {
	return latitudPunto;
    }

    public void setLatitudPunto(double latitudPunto) {
	this.latitudPunto = latitudPunto;
    }

    public double getLongitudPunto() {
	return longitudPunto;
    }

    public Location getUltimaUbicacion() {
	return locationManager.getLastKnownLocation(proveedor);
    }

    public void setLongitudPunto(double longitudPunto) {
	this.longitudPunto = longitudPunto;
    }

    private void buscarEst() {
	buscarEstacionamientosCercanosTask buscarTask = new buscarEstacionamientosCercanosTask();
	buscarTask.execute();
    }

    public Runnable buscarEstacionamientosHandler = new Runnable() {

	@Override
	public void run() {

	    buscarEst();
	    actualizarIntervalo();
	    buscarHandler.postDelayed(buscarEstacionamientosHandler, INTERVALO_ACTUALIZACION);
	}
    };

    public void iniciarBuscarTask() {
	buscarEstacionamientosHandler.run();
    }

    protected void actualizarIntervalo() {
	preferencias = PreferenceManager.getDefaultSharedPreferences(contexto);

	String def = String.valueOf(5 * 60 * 1000);
	long intervalo = Long.parseLong(preferencias.getString("listActualizacion", def));
	if (intervalo != INTERVALO_ACTUALIZACION) {
	    INTERVALO_ACTUALIZACION = intervalo;
	    detenerBuscarTask();
	    iniciarBuscarTask();
	}
    }

    public void detenerBuscarTask() {
	Log.d("detenerBuscarTask", "BuscarTask Stop");
	buscarHandler.removeCallbacks(buscarEstacionamientosHandler);
    }

    class buscarEstacionamientosCercanosTask extends AsyncTask<Object, Integer, ArrayList<Estacionamiento>> {

	private ProgressDialog barraEstado;

	@Override
	protected ArrayList<Estacionamiento> doInBackground(Object... params) {
	    Estacionamiento e = new Estacionamiento();
	    ArrayList<Estacionamiento> estacionamientos = e.buscarCercanos();
	    return estacionamientos;
	}

	@Override
	protected void onPostExecute(ArrayList<Estacionamiento> resp) {
	    estacionamientos = resp;
	    int i = estacionamientos.size();
	    Log.d("ESTACIONAMIENTOS", "N: " + estacionamientos.size());
	    if (numEstacionamientos > i) {
		mapa.clear();
		if (vistasIncluidas) {
		    removerVistas();
		}
		getMapa();
		estacionamientosVisibles = new HashMap<Integer, Marker>();
		agregarEstacionamientos(estacionamientos);

	    } else if (numEstacionamientos < i) {
		agregarEstacionamientos(estacionamientos);
	    } else {
		if (!vistasIncluidas) {
		    mapa.clear();
		    if (vistasIncluidas) {
			removerVistas();
		    }
		    getMapa();
		    estacionamientosVisibles = new HashMap<Integer, Marker>();
		    agregarEstacionamientos(estacionamientos);
		}
	    }

	    numEstacionamientos = estacionamientos.size();

	}

	@Override
	protected void onPreExecute() {
	    super.onPreExecute();
	}
    }

    public List<Estacionamiento> getEstacionamientos() {
	return estacionamientos;
    }

    public Estacionamiento getEstacionamiento(int id) {
	for (Estacionamiento e : estacionamientos) {
	    if (e.getId() == id) return e;
	}
	return null;
    }

    public void setEstacionamientos(List<Estacionamiento> estacionamientos) {
	this.estacionamientos = estacionamientos;
    }

    // CALCULAR RUTA ASINCRONA
    class CalcularRutaAsincrona extends AsyncTask<Object, Integer, Ruta> {
	private int distancia;
	private ProgressDialog barraEstado;

	@Override
	protected Ruta doInBackground(Object... params) {
	    LatLng inicio = (LatLng) params[0];
	    LatLng destino = (LatLng) params[1];
	    Ruta route = (Ruta) params[2];
	    String jsonURL = "http://maps.google.com/maps/api/directions/json?";
	    StringBuffer url = new StringBuffer(jsonURL);
	    url.append("origin=");
	    url.append(inicio.latitude);
	    url.append(',');
	    url.append(inicio.longitude);
	    url.append("&destination=");
	    url.append(destino.latitude);
	    url.append(',');
	    url.append(destino.longitude);
	    url.append("&sensor=true&mode=driving");
	    try {
		// se crea una nueva ruta
		route = new Ruta();
		// se crea el segmento
		Segmento segment = new Segmento();
		// respuesta json
		ConexionServidor con = new ConexionServidor();
		JSONObject respuesta = con.ejecutarHttpPost(new ArrayList<NameValuePair>(), url.toString());
		// se obtiene el arreglo routes que contiene el conjunto de
		// rutas
		// desde el inicio hasta el fin
		JSONObject jsonRoute = respuesta.getJSONArray("routes").getJSONObject(0);
		// se obtienen los tramos de la ruta anterior
		JSONObject leg = jsonRoute.getJSONArray("legs").getJSONObject(0);
		// por ultimo se obtienen los pasos de cada tramo
		JSONArray steps = leg.getJSONArray("steps");
		// se obtiene
		int numSteps = steps.length();
		// ya con estos datos podemos empezar a crear la ruta
		// se asigna el nombre de la ruta
		route.setNombre(leg.getString("start_address") + " to " + leg.getString("end_address"));
		// se asignan los derechos de Google
		route.setCopyright(jsonRoute.getString("copyrights"));
		// se asigna la distancia
		route.setLongitud(leg.getJSONObject("distance").getInt("value"));
		// se asignan los warningns si es que existieran
		if (!jsonRoute.getJSONArray("warnings").isNull(0)) {
		    route.setWarning(jsonRoute.getJSONArray("warnings").getString(0));
		}

		// se recorren los pasos para ir creando los segmentos
		for (int i = 0; i < numSteps; i++) {
		    // se obtiene el "paso" en detalle
		    JSONObject step = steps.getJSONObject(i);
		    // se obtiene el inicio y se asigna
		    JSONObject start = step.getJSONObject("start_location");
		    LatLng position = new LatLng(start.getDouble("lat"), start.getDouble("lng"));
		    segment.setInicio(position);
		    // se asigna la longitud del segmento en metros
		    int length = step.getJSONObject("distance").getInt("value");
		    distancia += length;
		    segment.setLongitud(length);
		    segment.setDistancia(distancia / 1000);
		    // se asignan las instrucciones de google
		    segment.setInstrucciones(step.getString("html_instructions").replaceAll("<(.*?)*>", ""));
		    // se genera una polyline y se agrega a la ruta
		    route.agregarPuntos(decodePolyLine(step.getJSONObject("polyline").getString("points")));
		    route.agregarSegmento(segment.getCopiaSegmento());
		}

	    } catch (IOException e) {
		e.printStackTrace();
		Log.e("CalcularRutaAsincrona", "ERROR 1: " + e.getMessage() + " : " + e.getCause());
	    } catch (JSONException e) {
		e.printStackTrace();
		Log.e("CalcularRutaAsincrona", "ERROR 2: " + e.getMessage() + " : " + e.getCause());
	    } catch (Exception e) {
		e.printStackTrace();
		Log.e("CalcularRutaAsincrona", "ERROR 3: " + e.getMessage() + " : " + e.getCause());
	    }
	    return route;
	}

	private List<LatLng> decodePolyLine(final String poly) {
	    int len = poly.length();
	    int index = 0;
	    List<LatLng> decoded = new LinkedList<LatLng>();
	    int lat = 0;
	    int lng = 0;

	    while (index < len) {
		int b;
		int shift = 0;
		int result = 0;
		do {
		    b = poly.charAt(index++) - 63;
		    result |= (b & 0x1f) << shift;
		    shift += 5;
		} while (b >= 0x20);
		int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
		lat += dlat;

		shift = 0;
		result = 0;
		do {
		    b = poly.charAt(index++) - 63;
		    result |= (b & 0x1f) << shift;
		    shift += 5;
		} while (b >= 0x20);
		int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
		lng += dlng;

		decoded.add(new LatLng((lat / 1E5), (lng / 1E5)));
	    }
	    publishProgress(100);
	    return decoded;
	}

	@Override
	protected void onPostExecute(Ruta ruta) {
	    super.onPostExecute(ruta);
	    List<LatLng> list = ruta.getPuntos();

	    rutaDibujada = new PolylineOptions();
	    rutaDibujada.addAll(list);
	    rutaDibujada.color(Color.rgb(0, 191, 255));
	    linea = mapa.addPolyline(rutaDibujada);

	    LayoutInflater layoutInflater = (LayoutInflater) contexto.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	    FrameLayout padre = (FrameLayout) contexto.findViewById(R.id.flMapaActivity);
	    View v = layoutInflater.inflate(R.layout.mapa_mensaje_dibujo_plantilla, null);

	    TextView txtMensaje = (TextView) v.findViewById(R.id.txtMensaje);
	    ImageButton btnX = (ImageButton) v.findViewById(R.id.btnCerrar);
	    String msge = contexto.getString(R.string.msgeMostrandoRuta);
	    msge = String.format(msge, ruta.getLongitud());
	    txtMensaje.setText(msge);
	    padre.addView(v, 1);

	    mostrandoRuta = true;
	    vistasIncluidas = true;

	    btnX.setOnClickListener(new OnClickListener() {

		public void onClick(View v) {
		    removerRuta();
		    removerVistas();
		}
	    });
	    if (barraEstado.isShowing()) {
		barraEstado.dismiss();
	    }
	}

	@Override
	protected void onPreExecute() {
	    if (getMostrandoRuta()) {
		removerRuta();
	    }
	    if (isMostrandoCirculo()) {
		removerCirulo();
	    }
	    if (isVistasIncluidas()) {
		removerVistas();
	    }
	    barraEstado = new ProgressDialog(contexto);
	    barraEstado.setProgressStyle(ProgressDialog.STYLE_SPINNER);
	    barraEstado.setMessage(contexto.getString(R.string.calculandoRuta));
	    barraEstado.setCanceledOnTouchOutside(false);
	    barraEstado.show();
	    super.onPreExecute();
	}
    }

}
