/*
 * Copyright (C) 2011 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.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

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;

/**
 * Version of the GeoPointMapActivity that uses the new Maps v2 API and Fragments to enable
 * specifying a location via placing a tracker on a map.
 *
 * @author guisalmon@gmail.com
 */
public class GeoPointMapActivity extends FragmentActivity implements LocationListener, OnMarkerDragListener,
    OnMapLongClickListener {

  private static final String LOCATION_COUNT = "locationCount";

  private GoogleMap mMap;
  private MarkerOptions mMarkerOption;
  private Marker mMarker;
  private LatLng mLatLng;

  private TextView mLocationStatus;

  private LocationManager mLocationManager;

  private Location mLocation;
  private Button mAcceptLocation;
  private Button mCancelLocation;
  private Button mReloadLocation;

  private boolean mCaptureLocation = true;
  private boolean mRefreshLocation = true;
  private boolean mIsDragged = false;
  private Button mShowLocation;

  private boolean mGPSOn = false;
  private boolean mNetworkOn = false;

  private double mLocationAccuracy;
  private int mLocationCount = 0;

  private boolean mZoomed = false;

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

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

    requestWindowFeature(Window.FEATURE_NO_TITLE);

    setContentView(R.layout.geopoint_layout);

    Intent intent = getIntent();

    mLocationAccuracy = GeoPointWidget.DEFAULT_LOCATION_ACCURACY;
    if (intent != null && intent.getExtras() != null) {
      if (intent.hasExtra(GeoPointWidget.LOCATION)) {
        double[] location = intent.getDoubleArrayExtra(GeoPointWidget.LOCATION);
        mLatLng = new LatLng(location[0], location[1]);
      }
      if (intent.hasExtra(GeoPointWidget.ACCURACY_THRESHOLD)) {
        mLocationAccuracy = intent.getDoubleExtra(GeoPointWidget.ACCURACY_THRESHOLD, GeoPointWidget
            .DEFAULT_LOCATION_ACCURACY);
      }
      mCaptureLocation = !intent.getBooleanExtra(GeoPointWidget.READ_ONLY, false);
      mRefreshLocation = mCaptureLocation;
    }

        /* Set up the map and the marker */
    mMarkerOption = new MarkerOptions();
    ((SupportMapFragment) getSupportFragmentManager()
        .findFragmentById(R.id.map)).getMapAsync(new OnMapReadyCallback() {

      public void onMapReady(GoogleMap map) {

      }

    });
    mMap.setOnMarkerDragListener(this);


    mLocationStatus = (TextView) findViewById(R.id.location_status);

		/*Zoom only if there's a previous location*/
    if (mLatLng != null) {
      mLocationStatus.setVisibility(View.GONE);
      mMarkerOption.position(mLatLng);
      mMarker = mMap.addMarker(mMarkerOption);
      mRefreshLocation = false; // just show this position; don't change it...
      mMarker.setDraggable(mCaptureLocation);
      mZoomed = true;
      mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mLatLng, 16));
    }

    mCancelLocation = (Button) findViewById(R.id.cancel_location);
    mCancelLocation.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View v) {
        Collect.getInstance().getActivityLogger().logInstanceAction(this, "cancelLocation", "cancel");
        finish();
      }
    });

    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("GeoPointMapActivity: " + System.currentTimeMillis() +
            " lastKnownLocation(GPS) lat: " +
            loc.getLatitude() + " long: " +
            loc.getLongitude() + " acc: " +
            loc.getAccuracy());
      } else {
        InfoLogger.geolog("GeoPointMapActivity: " + System.currentTimeMillis() +
            " lastKnownLocation(GPS) null location");
      }
    }

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


    mAcceptLocation = (Button) findViewById(R.id.accept_location);
    if (mCaptureLocation) {
      mAcceptLocation.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
          Collect.getInstance().getActivityLogger().logInstanceAction(this, "acceptLocation", "OK");
          returnLocation();
        }
      });
      mMap.setOnMapLongClickListener(this);
    } else {
      mAcceptLocation.setVisibility(View.GONE);
    }

    mReloadLocation = (Button) findViewById(R.id.reload_location);
    if (mCaptureLocation) {
      mReloadLocation.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
          mRefreshLocation = true;
          mReloadLocation.setVisibility(View.GONE);
          mLocationStatus.setVisibility(View.VISIBLE);
          if (mGPSOn) {
            mLocationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER, 0, 0, GeoPointMapActivity.this);
          }
          if (mNetworkOn) {
            mLocationManager.requestLocationUpdates(
                LocationManager.NETWORK_PROVIDER, 0, 0, GeoPointMapActivity.this);
          }
        }

      });
      mReloadLocation.setVisibility(!mRefreshLocation ? View.VISIBLE : View.GONE);
    } else {
      mReloadLocation.setVisibility(View.GONE);
    }

    // Focuses on marked location
    mShowLocation = ((Button) findViewById(R.id.show_location));
    mShowLocation.setVisibility(View.VISIBLE);
    mShowLocation.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        Collect.getInstance().getActivityLogger()
            .logInstanceAction(this, "showLocation", "onClick");
        mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mLatLng,
            16));
      }
    });
    mShowLocation.setClickable(mMarker != null);


  }

  private void stopGeolocating() {
    mRefreshLocation = false;
    mReloadLocation.setVisibility(View.VISIBLE);
    mLocationManager.removeUpdates(this);
    mMarker.setDraggable(true);
    mLocationStatus.setVisibility(View.GONE);
  }

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

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


  private void returnLocation() {
    if (mIsDragged) {
      Log.i(getClass().getName(), "IsDragged !!!");
      Intent i = new Intent();
      i.putExtra(
          FormEntryActivity.LOCATION_RESULT,
          mLatLng.latitude + " " + mLatLng.longitude + " "
              + 0 + " " + 0);
      setResult(RESULT_OK, i);
    } else if (mLocation != null) {
      Log.i(getClass().getName(), "IsNotDragged !!!");
      Intent i = new Intent();
      i.putExtra(
          FormEntryActivity.LOCATION_RESULT,
          mLocation.getLatitude() + " " + mLocation.getLongitude() + " "
              + mLocation.getAltitude() + " " + mLocation.getAccuracy());
      setResult(RESULT_OK, i);
    }
    finish();
  }


  private String truncateFloat(float f) {
    return new DecimalFormat("#.##").format(f);
  }


  @Override
  protected void onPause() {
    super.onPause();
    mLocationManager.removeUpdates(this);
  }


  @Override
  protected void onResume() {
    super.onResume();
    if (mRefreshLocation) {
      mLocationStatus.setVisibility(View.VISIBLE);
      if (mGPSOn) {
        mLocationManager.requestLocationUpdates(
            LocationManager.GPS_PROVIDER, 0, 0, this);
      }
      if (mNetworkOn) {
        mLocationManager.requestLocationUpdates(
            LocationManager.NETWORK_PROVIDER, 0, 0, this);
      }
    }
  }


  @Override
  public void onLocationChanged(Location location) {
    if (mRefreshLocation) {
      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("GeoPointMapActivity: " + System.currentTimeMillis() +
            " onLocationChanged(" + mLocationCount + ") lat: " +
            mLocation.getLatitude() + " long: " +
            mLocation.getLongitude() + " acc: " +
            mLocation.getAccuracy());

        if (mLocationCount > 1) {
          mLocationStatus.setText(getString(R.string.location_provider_accuracy,
              mLocation.getProvider(), truncateFloat(mLocation.getAccuracy())));
          mLatLng = new LatLng(mLocation.getLatitude(),
              mLocation.getLongitude());
          if (!mZoomed) {
            mZoomed = true;
            mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mLatLng, 16));
          } else {
            mMap.animateCamera(CameraUpdateFactory.newLatLng(mLatLng));
          }

          // create a marker on the map or move the existing marker to the
          // new location
          if (mMarker == null) {
            mMarkerOption.position(mLatLng);
            mMarker = mMap.addMarker(mMarkerOption);
            mShowLocation.setClickable(true);
          } else {
            mMarker.setPosition(mLatLng);
          }

          //If location is accurate enough, stop updating position and make the marker draggable
          if (mLocation.getAccuracy() <= mLocationAccuracy) {
            stopGeolocating();
          }


        }

      } else {
        InfoLogger.geolog("GeoPointMapActivity: " + System.currentTimeMillis() +
            " onLocationChanged(" + mLocationCount + ") null location");
      }
    }
  }


  @Override
  public void onProviderDisabled(String provider) {
  }


  @Override
  public void onProviderEnabled(String provider) {
  }


  @Override
  public void onStatusChanged(String provider, int status, Bundle extras) {
  }

  @Override
  public void onMarkerDrag(Marker arg0) {

  }

  @Override
  public void onMarkerDragEnd(Marker marker) {
    mLatLng = marker.getPosition();
    mAcceptLocation.setClickable(true);
    mIsDragged = true;
    mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mLatLng, mMap.getCameraPosition().zoom));
  }

  @Override
  public void onMarkerDragStart(Marker arg0) {
    stopGeolocating();
  }

  @Override
  public void onMapLongClick(LatLng latLng) {
    if (mMarker == null) {
      mMarkerOption.position(latLng);
      mMarker = mMap.addMarker(mMarkerOption);
      mShowLocation.setClickable(true);
    } else {
      mMarker.setPosition(latLng);
    }
    mLatLng = latLng;
    mIsDragged = true;
    stopGeolocating();
    mMarker.setDraggable(true);
  }


}
