/*
 * Professional Android, 4th Edition
 * Reto Meier and Ian Lake
 * Copyright 2018 John Wiley Wiley & Sons, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.professionalandroid.apps.myapplication;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingClient;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResponse;
import com.google.android.gms.location.LocationSettingsStates;
import com.google.android.gms.location.LocationSettingsStatusCodes;
import com.google.android.gms.location.SettingsClient;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;

import java.io.IOException;
import java.util.List;
import java.util.Locale;

import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.support.v4.content.PermissionChecker.PERMISSION_GRANTED;

public class LocationActivity extends AppCompatActivity {

  private static final int LOCATION_PERMISSION_REQUEST = 1;
  private static final int REQUEST_CHECK_SETTINGS = 2;
  private static final String TAG = "CHAPTER15_SNIPPETS";

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_location);

    // Listing 15-2: Checking if Google Play services is available
    GoogleApiAvailability availability = GoogleApiAvailability.getInstance();
    int result = availability.isGooglePlayServicesAvailable(this);
    if (result != ConnectionResult.SUCCESS) {
      if (!availability.isUserResolvableError(result)) {
        // TODO: Google Play services not available.
      }
    }

    int permission = ActivityCompat.checkSelfPermission(this,
      ACCESS_FINE_LOCATION);
    if (permission == PERMISSION_GRANTED) {
      // TODO Access the location-based services.

    } else {
      // Request fine location permission.
      if (ActivityCompat.shouldShowRequestPermissionRationale(
        this, ACCESS_FINE_LOCATION)) {
        // TODO Display additional rationale for the requested permission.
      }

      ActivityCompat.requestPermissions(this,
        new String[]{ACCESS_FINE_LOCATION},
        LOCATION_PERMISSION_REQUEST);
    }
  }

  private void listing15_4() {
    // Listing 15-4: Accessing the Fused Location Provider
    FusedLocationProviderClient fusedLocationClient;
    fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
  }

  private void listing15_5() {
    int permission = ActivityCompat.checkSelfPermission(this,
      ACCESS_FINE_LOCATION);
    if (permission == PERMISSION_GRANTED) {

      // LISTING 15-5: Obtaining the last known device Location
      FusedLocationProviderClient fusedLocationClient;
      fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
      fusedLocationClient.getLastLocation()
        .addOnSuccessListener(this, new OnSuccessListener<Location>() {
          @Override
          public void onSuccess(Location location) {
            // In some rare situations this can be null.
            if (location != null) {
              // TODO Do something with the returned location.
            }
          }
        });
    }
  }

  /*
   * Listing 15-6: Requesting location updates using a Location Request
   */
  LocationCallback mLocationCallback = new LocationCallback() {
    @Override
    public void onLocationResult(LocationResult locationResult) {
      for (Location location : locationResult.getLocations()) {
        // TODO React to newly received locations.
      }
    }
  };

  private void startTrackingLocation() {
    if (
      ActivityCompat
        .checkSelfPermission(this, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED ||
        ActivityCompat
          .checkSelfPermission(this, ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED) {

      FusedLocationProviderClient locationClient = LocationServices.getFusedLocationProviderClient(this);
      LocationRequest request =
        new LocationRequest()
          .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
          .setInterval(5000); // Update every 5 seconds.

      locationClient.requestLocationUpdates(request, mLocationCallback, null);
    }
  }

  /*
   * Listing 15-7: Cancelling location updates
   */
  @Override
  protected void onStop() {
    super.onStop();

    FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
    fusedLocationClient.removeLocationUpdates(mLocationCallback);
  }

  private void listing15_8() {
    if (
      ActivityCompat
        .checkSelfPermission(this, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED ||
        ActivityCompat
          .checkSelfPermission(this, ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED) {

      // Listing 15-8: Requesting location updates using a Pending Intent
      FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

      LocationRequest request = new LocationRequest()
                                  .setInterval(60000 * 10) // Update every 10 minutes.
                                  .setPriority(LocationRequest.PRIORITY_NO_POWER);

      final int locationUpdateRC = 0;
      int flags = PendingIntent.FLAG_UPDATE_CURRENT;

      Intent intent = new Intent(this, MyLocationUpdateReceiver.class);
      PendingIntent pendingIntent = PendingIntent.getBroadcast(this, locationUpdateRC, intent, flags);

      fusedLocationClient.requestLocationUpdates(request, pendingIntent);
    }
  }

  private void listing15_10_11_12() {
    LocationRequest request =
      new LocationRequest()
        .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
        .setInterval(5000); // Update every 5 seconds.

    // Listing 15-10: Check if the current Location Settings satisfy your requirements
    // Get the settings client.
    SettingsClient client = LocationServices.getSettingsClient(this);

    // Create a new Location Settings Request, adding our Location Requests
    LocationSettingsRequest.Builder builder =
      new LocationSettingsRequest.Builder().addLocationRequest(request);

    // Check if the Location Settings satisfy our requirements.
    Task<LocationSettingsResponse> task =
      client.checkLocationSettings(builder.build());

    // Listing 15-11: Create a handler for when Location Settings satisfy your requirements
    task.addOnSuccessListener(this,
      new OnSuccessListener<LocationSettingsResponse>() {
        @Override
        public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
          // Location settings satisfy the requirements of the Location Request
          startTrackingLocation();
        }
      });

    // Listing 15-12: Request user changes to location settings
    task.addOnFailureListener(this, new OnFailureListener() {
      @Override
      public void onFailure(@NonNull Exception e) {
        // Extract the status code for the failure from within the Exception.
        int statusCode = ((ApiException) e).getStatusCode();
        switch (statusCode) {
          case CommonStatusCodes.RESOLUTION_REQUIRED:
            // Location settings don't satisfy the requirements of the
            // Location Request, but they could be resolved through user
            // selection within a Dialog.
            try {
              // Display a user dialog to resolve the location settings issue.
              ResolvableApiException resolvable = (ResolvableApiException) e;
              resolvable.startResolutionForResult(LocationActivity.this, REQUEST_CHECK_SETTINGS);
            } catch (IntentSender.SendIntentException sendEx) {
              Log.e(TAG, "Location Settings resolution failed.", sendEx);
            }
            break;
          case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
            // Location settings don't satisfy the requirements of the
            // Location Request, however it can't be resolved with a user
            // dialog.
            // TODO Start monitoring location updates anyway, or abort.
            break;
          default: break;
        }
      }
    });
  }

  /*
   * Listing 15-13: Handling the user’s response to our request to change location settings
   */
  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data){

    final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);

    if (requestCode == REQUEST_CHECK_SETTINGS) {
      switch (resultCode) {
        case Activity.RESULT_OK:
          // TODO Changes were applied.
          break;
        case Activity.RESULT_CANCELED:
          // TODO Changes were not applied.
          // TODO Check states to confirm if we can attempt
          // TODO to request location updates anyway.
          break;
        default: break;
      }
    }
  }

  private void listing15_14_15_16_17(Location location, String id) {
    if (
      ActivityCompat
        .checkSelfPermission(this, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED ||
      ActivityCompat
        .checkSelfPermission(this, ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED) {

        Intent intent = new Intent(this, GeofenceBroadcastReceiver.class);
        PendingIntent geofenceIntent = PendingIntent.getBroadcast(this, -1, intent, 0);

        // Listing 15-14: Accessing the Geofencing Client
        GeofencingClient geofencingClient =
          LocationServices.getGeofencingClient(this);

        // Listing 15-15: Defining a Geofence
        Geofence newGeofence
          = new Geofence.Builder()
              .setRequestId(id) // unique name of geofence
              .setCircularRegion(location.getLatitude(),
                location.getLongitude(),
                30) // 30 meter radius.
              .setExpirationDuration(Geofence.NEVER_EXPIRE) // Or expiration time in ms
              .setLoiteringDelay(10 * 1000)                   // Dwell after 10 seconds
              .setNotificationResponsiveness(10 * 1000)       // Notify within 10 seconds
              .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_DWELL)
              .build();

        // Listing 15-16: Creating a Geofencing Request
        GeofencingRequest geofencingRequest
          = new GeofencingRequest.Builder()
              .addGeofence(newGeofence)
              .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_DWELL)
              .build();

        // Listing 15-17: Initiating a Geofencing Request
        geofencingClient.addGeofences(geofencingRequest, geofenceIntent)
          .addOnSuccessListener(this, new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
              // TODO Geofence added.
            }
          })
          .addOnFailureListener(this, new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
              Log.d(TAG, "Adding Geofence failed", e);
              // TODO Geofence failed to add.
            }
          });
      }
  }

  /*
   * Listing 15-19: Reverse-geocoding a given location
   */
  private void reverseGeocode(Location location) {
    double latitude = location.getLatitude();
    double longitude = location.getLongitude();
    List<Address> addresses = null;
    Geocoder gc = new Geocoder(this, Locale.getDefault());
    try {
      addresses = gc.getFromLocation(latitude, longitude, 10);
    } catch (IOException e) {
      Log.e(TAG, "Geocoder I/O Exception", e);
    }
  }

  private void listing15_20() {
    // Listing 15-20: Geocoding an address
    Geocoder geocoder = new Geocoder(this, Locale.US);
    String streetAddress = "160 Riverside Drive, New York, New York";
    List<Address> locations = null;
    try {
      locations = geocoder.getFromLocationName(streetAddress, 5);
    } catch (IOException e) {
      Log.e(TAG, "Geocoder I/O Exception", e);
    }
  }
}