package com.manning.aip.brewmap;

import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * Klasa pomocnicza z kodem potrzebnym do ustalania obecnego położenia przy użyciu
 * dostawcy typu FINE (GPS-a). 
 * <p/>
 * Jeśli dostępne jest stosunkowo niedawne położenie dla dostawcy FINE 
 * (z okresu FIX_RECENT_BUFFER_TIME, ustawionego tu na 30 sekund), aplikacja zwraca je 
 * w komunikacie do jednostki wywołującej . 
 * <p/>
 * Jeśli niedawne położenie jest niedostępne lub jest zbyt dawne, na określony czas
 * uruchamiany jest odbiornik LocationListener. Kiedy odbiornik LocationListener 
 * ustalił nowe położenie lub upłynął czas, do jednostki wywołującej przesyłany 
 * jest komunikat.
 * <p/>
 * Przykład ilustrujący stosowanie klasy w aktywności:
 * <p/>
 * <pre>
 *     
 *     Handler handler = new Handler() {
 *        public void handleMessage(Message m) {
 *           Log.d(LOG_TAG, "Zwrócono komunikat: " + m.toString());
 *           if (m.what == LocationHelper.MESSAGE_CODE_LOCATION_FOUND) {
 *              Toast.makeText(Activity.this, "KOMPONENT OBSŁUGI - wys.:" + m.arg1 + " dl.:" + m.arg2, Toast.LENGTH_SHORT)
 *                       .show();
 *           } else if (m.what == LocationHelper.MESSAGE_CODE_LOCATION_NULL) {
 *              Toast.makeText(Activity.this, "KOMPONENT OBSŁUGI - nie można ustalić położenia", Toast.LENGTH_SHORT).show();
 *           } else if (m.what == LocationHelper.MESSAGE_CODE_PROVIDER_NOT_PRESENT) {
 *              Toast.makeText(Activity.this, "KOMPONENT OBSŁUGI - brak dostawcy", Toast.LENGTH_SHORT).show();
 *           }
 *        }
 *     };
 *     
 *     LocationHelper helper = new LocationHelper(locationManager, handler, LOG_TAG);
 *     helper.getCurrentLocation(handler); 
 * </pre> 
 * 
 * @author ccollins
 */
public class LocationHelper {

   public static final int MESSAGE_CODE_LOCATION_FOUND = 1;
   public static final int MESSAGE_CODE_LOCATION_NULL = 2;
   public static final int MESSAGE_CODE_PROVIDER_NOT_PRESENT = 3;   

   private static final int FIX_RECENT_BUFFER_TIME = 30000;

   private LocationManager locationMgr;
   private LocationListener locationListener;
   private Handler handler;
   private Runnable handlerCallback;
   private String providerName;
   private String logTag;
   
   /**
    * W konstruktorze używany jest obiekt klasy LocationManager i obiekt 
    * klasy Handler do zwracania komunikatów.
    * 
    * @param locationMgr
    * @param handler
    */
   public LocationHelper(LocationManager locationMgr, Handler handler, String logTag) {
      this.locationMgr = locationMgr;
      this.locationListener = new LocationListenerImpl();
      this.handler = handler;      
      this.handlerCallback = new Thread() {
         public void run() {
            endListenForLocation(null);
         }
      };

      Criteria criteria = new Criteria();
      // Klasa Criteria pozwala uzyskać dostawcę. Można użyć dostawcy typu COARSE,
      // jednak nie działa on w emulatorze. Przy ustawieniu FINE używane są dane z sieci
      // lub GPS w zależności od tego, który działa lepiej w danym miejscu 
      // (w emulatorze trzeba ustawić GPS).
      // UWAGA, określanie położenia za pomocą sieci trzeba włączyć: 
      // Settings/Location & Security Settings/Use wireless networks)
      criteria.setAccuracy(Criteria.ACCURACY_FINE);
      this.providerName = locationMgr.getBestProvider(criteria, true);
      
      this.logTag = logTag;
   }

   /**
    * Uruchamia określanie położenia. Obiekt klasy Handler przekazany w 
    * konstruktorze zwraca komunikat z położeniem.
    * 
    * @param durationSeconds Czas oczekiwania na aktualizację położenia
    */
   public void getCurrentLocation(int durationSeconds) {

      if (this.providerName == null) {
    	// Zwraca 2/0/0, jeśli dostawca jest niedostępny.
          Log.d(logTag, "provideName=null. Dostawca jest wyłączony lub nie istnieje.");
         sendLocationToHandler(MESSAGE_CODE_PROVIDER_NOT_PRESENT, 0, 0);
         return;
      }

      // Najpierw należy sprawdzić ostatnie ZNANE położenie. Jeśli jest niedawne, należy  
      // go użyć. UWAGA: NIE DZIAŁA w emulatorze. Jeśli za pomocą DDMS-a wyślesz
      // ręcznie czas lub położenie, otrzymasz odpowiednią datę, jednak czas ustalenia
      // położenia zostanie ustawiony na 00:00 i będzie zwiększany o 1 sekundę przy
      // każdym przesłaniu nowego położenia. 
      // Aby przetestować ten fragment (getLastLocation zwraca świeże dane), 
      // trzeba użyć fizycznego urządzenia.
      Location lastKnown = locationMgr.getLastKnownLocation(providerName);
      if (lastKnown != null && lastKnown.getTime() >= (System.currentTimeMillis() - FIX_RECENT_BUFFER_TIME)) {
         Log.d(logTag, "Ostatnie znane położenie jest świeże. Można go użyć: " + lastKnown.toString());
         // Zwraca ostatnie znane położenie w komunikacie od obiektu klasy Handler
         sendLocationToHandler(MESSAGE_CODE_LOCATION_FOUND, (int) (lastKnown.getLatitude() * 1e6),
                  (int) (lastKnown.getLongitude() * 1e6));
      } else {
         // Ostatnie znane położenie jest stosunkowo dawne lub nie istnieje. Należy 
     	 // użyć odbiornika LocationListener i poczekać X sekund na aktualizację.
         Log.d(logTag, "Ostatnie położenie NIE jest świeże. Używanie odbiornika do zaktualizowania położenia.");
         listenForLocation(providerName, durationSeconds);
      }
   }

   private void sendLocationToHandler(int msgId, int lat, int lon) {
      Message msg = Message.obtain(handler, msgId, lat, lon);
      handler.sendMessage(msg);
   }

   private void listenForLocation(String providerName, int durationSeconds) {
      locationMgr.requestLocationUpdates(providerName, 0, 0, locationListener);
      handler.postDelayed(handlerCallback, durationSeconds * 1000);
   }

   private void endListenForLocation(Location loc) {
      locationMgr.removeUpdates(locationListener);
      handler.removeCallbacks(handlerCallback);
      if (loc != null) {
         sendLocationToHandler(MESSAGE_CODE_LOCATION_FOUND, (int) (loc.getLatitude() * 1e6), (int) (loc.getLongitude() * 1e6));
      } else {
         sendLocationToHandler(MESSAGE_CODE_LOCATION_NULL, 0, 0);
      }
   }

   private class LocationListenerImpl implements LocationListener {
      @Override
      public void onStatusChanged(String provider, int status, Bundle extras) {
         Log.d(logTag, "Zmiana statusu na:" + status);
         switch (status) {
            case LocationProvider.AVAILABLE:
               break;
            case LocationProvider.TEMPORARILY_UNAVAILABLE:
               break;
            case LocationProvider.OUT_OF_SERVICE:
               endListenForLocation(null);
         }
      }

      @Override
      public void onLocationChanged(Location loc) {         
         if (loc == null) {
            return;
         }
         Log.d(logTag, "Zmiana położenia:" + loc.toString());
         endListenForLocation(loc);
      }

      @Override
      public void onProviderDisabled(String provider) {
         endListenForLocation(null);
      }

      @Override
      public void onProviderEnabled(String provider) {
      }
   }
}