/*
 * Copyright (C) 2009 University of Washington
 *
 * 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 org.odk.collect.android.activities;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.widget.Toast;

import org.odk.collect.android.R;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.utilities.InfoLogger;
import org.odk.collect.android.widgets.GeoPointWidget;

import java.text.DecimalFormat;
import java.util.List;

public class GeoPointActivity extends Activity implements LocationListener {

  private static final String LOCATION_COUNT = "locationCount";

  private ProgressDialog mLocationDialog;
  private LocationManager mLocationManager;
  private Location mLocation;
  private boolean mGPSOn = false;
  private boolean mNetworkOn = false;
  private double mLocationAccuracy;
  private int mLocationCount = 0;


  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (savedInstanceState != null) {
      mLocationCount = savedInstanceState.getInt(LOCATION_COUNT);
    }

    Intent intent = getIntent();

    mLocationAccuracy = GeoPointWidget.DEFAULT_LOCATION_ACCURACY;
    if (intent != null && intent.getExtras() != null) {
      if (intent.hasExtra(GeoPointWidget.ACCURACY_THRESHOLD)) {
        mLocationAccuracy = intent.getDoubleExtra(GeoPointWidget.ACCURACY_THRESHOLD, GeoPointWidget
            .DEFAULT_LOCATION_ACCURACY);
      }
    }

    setTitle(getString(R.string.app_name) + " > " + getString(R.string.get_location));

    mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

    // make sure we have a good location provider before continuing
    List<String> providers = mLocationManager.getProviders(true);
    for (String provider : providers) {
      if (provider.equalsIgnoreCase(LocationManager.GPS_PROVIDER)) {
        mGPSOn = true;
      }
      if (provider.equalsIgnoreCase(LocationManager.NETWORK_PROVIDER)) {
        mNetworkOn = true;
      }
    }
    if (!mGPSOn && !mNetworkOn) {
      Toast.makeText(getBaseContext(), getString(R.string.provider_disabled_error),
          Toast.LENGTH_SHORT).show();
      finish();
    }

    if (mGPSOn) {
      Location loc = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
      if (loc != null) {
        InfoLogger.geolog("GeoPointActivity: " + System.currentTimeMillis() +
            " lastKnownLocation(GPS) lat: " +
            loc.getLatitude() + " long: " +
            loc.getLongitude() + " acc: " +
            loc.getAccuracy());
      } else {
        InfoLogger.geolog("GeoPointActivity: " + System.currentTimeMillis() +
            " lastKnownLocation(GPS) null location");
      }
    }

    if (mNetworkOn) {
      Location loc = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
      if (loc != null) {
        InfoLogger.geolog("GeoPointActivity: " + System.currentTimeMillis() +
            " lastKnownLocation(Network) lat: " +
            loc.getLatitude() + " long: " +
            loc.getLongitude() + " acc: " +
            loc.getAccuracy());
      } else {
        InfoLogger.geolog("GeoPointActivity: " + System.currentTimeMillis() +
            " lastKnownLocation(Network) null location");
      }
    }

    setupLocationDialog();

  }

  @Override
  protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(LOCATION_COUNT, mLocationCount);
  }

  @Override
  protected void onPause() {
    super.onPause();

    // stops the GPS. Note that this will turn off the GPS if the screen goes to sleep.
    mLocationManager.removeUpdates(this);

    // We're not using managed dialogs, so we have to dismiss the dialog to prevent it from
    // leaking memory.
    if (mLocationDialog != null && mLocationDialog.isShowing()) {
      mLocationDialog.dismiss();
    }
  }


  @Override
  protected void onResume() {
    super.onResume();
    if (mGPSOn) {
      mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
    }
    if (mNetworkOn) {
      mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
    }
    mLocationDialog.show();
  }

  @Override
  protected void onStart() {
    super.onStart();
    Collect.getInstance().getActivityLogger().logOnStart(this);
  }

  @Override
  protected void onStop() {
    Collect.getInstance().getActivityLogger().logOnStop(this);
    super.onStop();
  }

  /**
   * Sets up the look and actions for the progress dialog while the GPS is searching.
   */
  private void setupLocationDialog() {
    Collect.getInstance().getActivityLogger().logInstanceAction(this, "setupLocationDialog", "show");
    // dialog displayed while fetching gps location
    mLocationDialog = new ProgressDialog(this);
    DialogInterface.OnClickListener geopointButtonListener =
        new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialog, int which) {
            switch (which) {
              case DialogInterface.BUTTON_POSITIVE:
                Collect.getInstance().getActivityLogger().logInstanceAction(this, "acceptLocation", "OK");
                returnLocation();
                break;
              case DialogInterface.BUTTON_NEGATIVE:
                Collect.getInstance().getActivityLogger().logInstanceAction(this, "cancelLocation", "cancel");
                mLocation = null;
                finish();
                break;
            }
          }
        };

    // back button doesn't cancel
    mLocationDialog.setCancelable(false);
    mLocationDialog.setIndeterminate(true);
    mLocationDialog.setTitle(getString(R.string.getting_location));
    mLocationDialog.setMessage(getString(R.string.please_wait_long));
    mLocationDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.accept_location),
        geopointButtonListener);
    mLocationDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel_location),
        geopointButtonListener);
  }


  private void returnLocation() {
    if (mLocation != null) {
      Intent i = new Intent();
      i.putExtra(
          FormEntryActivity.LOCATION_RESULT,
          mLocation.getLatitude() + " " + mLocation.getLongitude() + " "
              + mLocation.getAltitude() + " " + mLocation.getAccuracy());
      setResult(RESULT_OK, i);
    }
    finish();
  }


  @Override
  public void onLocationChanged(Location location) {
    mLocation = location;
    if (mLocation != null) {
      // Bug report: cached GeoPoint is being returned as the first value.
      // Wait for the 2nd value to be returned, which is hopefully not cached?
      ++mLocationCount;
      InfoLogger.geolog("GeoPointActivity: " + System.currentTimeMillis() +
          " onLocationChanged(" + mLocationCount + ") lat: " +
          mLocation.getLatitude() + " long: " +
          mLocation.getLongitude() + " acc: " +
          mLocation.getAccuracy());

      if (mLocationCount > 1) {
        mLocationDialog.setMessage(getString(R.string.location_provider_accuracy,
            mLocation.getProvider(), truncateDouble(mLocation.getAccuracy())));

        if (mLocation.getAccuracy() <= mLocationAccuracy) {
          returnLocation();
        }
      }
    } else {
      InfoLogger.geolog("GeoPointActivity: " + System.currentTimeMillis() +
          " onLocationChanged(" + mLocationCount + ") null location");
    }
  }


  private String truncateDouble(float number) {
    DecimalFormat df = new DecimalFormat("#.##");
    return df.format(number);
  }


  @Override
  public void onProviderDisabled(String provider) {

  }


  @Override
  public void onProviderEnabled(String provider) {

  }


  @Override
  public void onStatusChanged(String provider, int status, Bundle extras) {
    switch (status) {
      case LocationProvider.AVAILABLE:
        if (mLocation != null) {
          mLocationDialog.setMessage(getString(R.string.location_accuracy,
              mLocation.getAccuracy()));
        }
        break;
      case LocationProvider.OUT_OF_SERVICE:
        break;
      case LocationProvider.TEMPORARILY_UNAVAILABLE:
        break;
    }
  }

}
