public class MainActivity extends Activity implements OnClickListener {
  private static final Log log = Logs.getLog(MainActivity.class);

  private Calendar mDate;

  private PresenceManager mPresenceManager;

  private LocationManager mLocationManager;

  private LocationChecker locationChecker = new LocationChecker();

  private Handler mHandler;

  private Geocoder mGeocoder;

  private List<LocationListener> locationListeners = new ArrayList<LocationListener>();

  private class MyLocationListener implements LocationListener {
    @Override
    public void onLocationChanged(Location location) {
      if (!locationChecker.isFineLocation(location)) {
        log.debug("SKIP BAD " + location);
        return;
      }

      GPSHelper.addLocation(location, mGeocoder);
      updateList();
    }

    @Override
    public void onProviderDisabled(String provider) {}

    @Override
    public void onProviderEnabled(String provider) {}

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

  private static class MyAdapter extends BaseAdapter {
    private Context context;
    private List<TrackingData> items;

    public MyAdapter(Context context, List<TrackingData> items) {
      this.context = context;
      this.items = items;
    }

    public void setItems(List<TrackingData> items) {
      this.items = items;
    }

    @Override
    public int getCount() {
      return items.size();
    }

    @Override
    public Object getItem(int position) {
      return items.get(getCount() - 1 - position);
    }

    @Override
    public long getItemId(int position) {
      return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      TwoLineListItem twoLineListItem;

      if (convertView == null) {
        LayoutInflater inflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        twoLineListItem =
            (TwoLineListItem) inflater.inflate(android.R.layout.simple_list_item_2, null);
        twoLineListItem.getText1().setTextSize(14);
        twoLineListItem.getText2().setTextSize(12);
      } else {
        twoLineListItem = (TwoLineListItem) convertView;
      }

      TextView text1 = twoLineListItem.getText1();
      TextView text2 = twoLineListItem.getText2();

      TrackingData td = (TrackingData) getItem(position);
      text1.setText(td.toMain());
      text2.setText(td.toSub());

      return twoLineListItem;
    }
  }

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mDate = Calendar.getInstance();

    findViewById(R.id.btnStart).setOnClickListener(this);
    findViewById(R.id.btnStop).setOnClickListener(this);
    TextView txtDate = (TextView) findViewById(R.id.txtDate);
    txtDate.setText(DateTimes.dateFormat().format(mDate));
    txtDate.setOnClickListener(this);

    buttonVisible(false);

    mHandler = new Handler(getMainLooper());

    mGeocoder = new Geocoder(this, Locale.getDefault());

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

    //		// Criteriaオブジェクトを生成
    //		Criteria criteria = new Criteria();
    //		criteria.setAccuracy(Criteria.ACCURACY_FINE);
    //		criteria.setPowerRequirement(Criteria.POWER_LOW);
    //		locationProvider = mLocationManager.getBestProvider(criteria, true);
    //		log.debug("Location Provider: " + locationProvider);
    //		setTitle(getTitle() + " - " + locationProvider);

    mPresenceManager = PresenceManager.getInstance(getApplicationContext());

    MyAdapter adapter = new MyAdapter(this, GPSHelper.getTrackings());

    ListView listView = (ListView) findViewById(R.id.listTracking);
    listView.setAdapter(adapter);

    listView.setOnItemClickListener(
        new AdapterView.OnItemClickListener() {
          @Override
          public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            // ListView listView = (ListView)parent;

            // TrackingData item = (TrackingData)listView.getItemAtPosition(position);

            if (position % 2 == 0) {
              startActivity(new Intent(MainActivity.this, MapLineActivity.class));
            } else {
              startActivity(new Intent(MainActivity.this, MapCircleActivity.class));
            }
          }
        });

    mHandler.postDelayed(
        new Runnable() {
          @Override
          public void run() {
            GPSHelper.loadTrackings(mDate);
            updateList();
          }
        },
        100);
  }

  public void onStart() {
    super.onStart();
  }

  private static BroadcastReceiver mIspReceiver = new PresenceReceiver();

  public void onResume() {
    super.onResume();
    buttonVisible(isPresenceServiceRunning());
  }

  public void onDestory() {
    super.onDestroy();
  }

  public void onStop() {
    super.onStop();
  }

  public void onPause() {
    super.onPause();
  }

  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.btnStart:
        startTrackingService();
        break;
      case R.id.btnStop:
        stopTrackingService();
        break;
      case R.id.txtDate:
        showDatepicker();
        break;
    }
  }

  private void showDatepicker() {
    if (!locationListeners.isEmpty()) {
      return;
    }

    final DatePickerDialog datePickerDialog =
        new DatePickerDialog(
            this,
            new DatePickerDialog.OnDateSetListener() {
              @Override
              public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
                Calendar c = Calendar.getInstance();
                c.set(year, monthOfYear, dayOfMonth);

                if (DateTimes.isSameDay(mDate, c)) {
                  return;
                }

                mDate.set(year, monthOfYear, dayOfMonth);

                final TextView txtDate = (TextView) findViewById(R.id.txtDate);
                txtDate.setText(DateTimes.dateFormat().format(mDate));

                GPSHelper.loadTrackings(mDate);
                updateList();
              }
            },
            mDate.get(Calendar.YEAR),
            mDate.get(Calendar.MONTH),
            mDate.get(Calendar.DAY_OF_MONTH));

    datePickerDialog.show();
  }

  private void stopTrackingService() {
    getApplicationContext().unregisterReceiver(mIspReceiver);
    mPresenceManager.stopPresence();
    for (LocationListener ll : locationListeners) {
      mLocationManager.removeUpdates(ll);
    }
    locationListeners.clear();
    buttonVisible(false);
  }

  private void startTrackingService() {
    if (!DateTimes.isSameDay(mDate, Calendar.getInstance())) {
      mDate = Calendar.getInstance();

      TextView txtDate = (TextView) findViewById(R.id.txtDate);
      txtDate.setText(DateTimes.dateFormat().format(mDate));

      GPSHelper.loadTrackings(mDate);
      updateList();
    }

    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(PresenceManagerUtil.CHANGE_STATUS);
    getApplicationContext().registerReceiver(mIspReceiver, intentFilter);

    mPresenceManager.startPresence();
    for (String p : mLocationManager.getAllProviders()) {
      LocationListener ll = new MyLocationListener();
      mLocationManager.requestLocationUpdates(
          p, 60000, // 通知のための最小時間間隔(ミリ秒)
          30, // 通知のための最小距離間隔(メートル)
          ll);
      locationListeners.add(ll);
    }
    buttonVisible(true);
  }

  private void buttonVisible(boolean stop) {
    findViewById(R.id.btnStart).setEnabled(!stop);
    findViewById(R.id.btnStop).setEnabled(stop);
  }

  private void updateList() {
    ListView listView = (ListView) findViewById(R.id.listTracking);
    ((MyAdapter) listView.getAdapter()).notifyDataSetChanged();
  }

  private boolean isPresenceServiceRunning() {
    ActivityManager am =
        (ActivityManager) getApplicationContext().getSystemService(Service.ACTIVITY_SERVICE);
    List<ActivityManager.RunningServiceInfo> runningServiceInfo =
        am.getRunningServices(Integer.MAX_VALUE);
    int serviceNum = runningServiceInfo.size();
    for (int i = 0; i < serviceNum; i++) {
      if (runningServiceInfo
          .get(i)
          .service
          .getClassName()
          .equals(PresenceService.class.getName())) {
        return true;
      }
    }
    return false;
  }
}
public class GPSHelper {
  private static final Log log = Logs.getLog(GPSHelper.class);

  private static List<TrackingData> trackings = new ArrayList<TrackingData>();

  @SuppressLint("UseSparseArrays")
  private static Map<Integer, Long> states = new HashMap<Integer, Long>();

  private static int lastState;
  private static long lastStart;

  public static List<TrackingData> getTrackings() {
    return trackings;
  }

  public static String getTrackingFile(Calendar c) {
    String fn =
        GPSHelper.class.getPackage().getName()
            + "/gpstracking."
            + DateTimes.dateLogFormat().format(c)
            + ".txt";
    return FileNames.concat(Environment.getExternalStorageDirectory().getAbsolutePath(), fn);
  }

  public static void loadTrackings(Calendar c) {
    trackings.clear();

    File file = new File(getTrackingFile(c));
    if (!file.exists()) {
      log.warn(file + "does not exist");
      return;
    }

    log.info("Loading " + file);

    float[] results = new float[1];
    LineIterator li = null;
    try {
      li = Files.lineIterator(file);
      while (li.hasNext()) {
        String line = li.next();
        if (Strings.isEmpty(line)) {
          continue;
        }

        TrackingData td = Jsons.fromJson(line, TrackingData.class);
        TrackingData ltd = getLastLocation();
        if (ltd != null) {
          Location.distanceBetween(
              ltd.getLatitude(), ltd.getLongitude(), td.getLatitude(), td.getLongitude(), results);
          td.setDistance(results[0]);
          td.setSpeed(td.getDistance() / DateTimes.subSeconds(td.getDate(), ltd.getDate()));
        }
        trackings.add(td);
      }
    } catch (IOException e) {
      log.error(e);
    } finally {
      Streams.safeClose(li);
    }
  }

  public static TrackingData getLastLocation() {
    if (Collections.isNotEmpty(trackings)) {
      return trackings.get(trackings.size() - 1);
    }
    return null;
  }

  public static TrackingData getFirstLocation() {
    if (Collections.isNotEmpty(trackings)) {
      return trackings.get(0);
    }
    return null;
  }

  public static boolean addLocation(Location location, Geocoder geocoder) {
    TrackingData ltd = getLastLocation();

    float distance = 0.0f;
    float speed = 0.0f;

    if (ltd != null) {
      float[] results = new float[1];
      Location.distanceBetween(
          ltd.getLatitude(),
          ltd.getLongitude(),
          location.getLatitude(),
          location.getLongitude(),
          results);

      distance = results[0];
      if (distance < 100) {
        log.debug("SKIP SAME " + location + ": " + distance);
        return false;
      }

      long delta = DateTimes.subSeconds(new Date(location.getTime()), ltd.getDate());
      // less than 30 minutes
      if (delta < 30 * 60) {
        speed = distance / delta;
        //				if (lastState == DetectedActivity.STILL) {
        //
        //				}
        // great than 120km/h
        if (speed >= 33) {
          log.debug("SKIP FAST " + location + ": " + distance);
          return false;
        }
      }
    }

    String address = "";
    try {
      List<Address> addresses =
          geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);
      if (Collections.isNotEmpty(addresses)) {
        Address a = addresses.get(0);
        address = toAddress(a);
      }

      // if (ltd != null && Strings.isNotEmpty(address)) {
      // if (ltd.getAddress().equals(address)) {
      // log.debug("Skip (" + location + "): " + address);
      // return;
      // }
      // }
    } catch (Exception e) {
      // Catch network or other I/O problems.
      log.error(
          "Failed to get address of (" + location.getLatitude() + ", " + location.getLongitude(),
          e);
    }

    TrackingData td = new TrackingData();
    td.setDate(new Date(location.getTime()));
    td.setState(getBestState());
    td.setLatitude(location.getLatitude());
    td.setLongitude(location.getLongitude());
    td.setAddress(address);

    if (ltd != null) {
      float[] results = new float[1];
      Location.distanceBetween(
          ltd.getLatitude(), ltd.getLongitude(), td.getLatitude(), td.getLongitude(), results);
      td.setDistance(results[0]);
      td.setSpeed(td.getDistance() / DateTimes.subSeconds(td.getDate(), ltd.getDate()));
    }

    trackings.add(td);
    saveTrackingData(td);

    return true;
  }

  private static void saveTrackingData(TrackingData td) {
    String s = Jsons.toJson(td);

    Writer r = null;
    try {
      log.info("ADD: " + s);

      File file = new File(getTrackingFile(Calendar.getInstance()));
      r = new OutputStreamWriter(new FileOutputStream(file, true), Charsets.UTF_8);
      r.append(s);
      r.append(Streams.LINE_SEPARATOR);
    } catch (IOException e) {
      log.error("Failed to save " + s, e);
    } finally {
      Streams.safeClose(r);
    }
  }

  private static int getBestState() {
    if (Collections.isEmpty(states)) {
      return DetectedActivity.UNKNOWN;
    }

    long max = 0;
    for (Long time : states.values()) {
      if (time > max) {
        max = time;
      }
    }

    int state = DetectedActivity.UNKNOWN;
    for (Entry<Integer, Long> en : states.entrySet()) {
      if (max == en.getValue()) {
        state = en.getKey();
        break;
      }
    }

    states.clear();

    states.put(lastState, 0L);
    lastStart = System.currentTimeMillis();

    return state;
  }

  public static void setState(ActivityRecognitionResult result) {
    DetectedActivity da = result.getMostProbableActivity();

    int state = da.getType();

    log.debug(
        "DetectedActivity: "
            + DateTimes.timeFormat().format(result.getTime())
            + " - "
            + getStateText(state)
            + ", "
            + da.getConfidence()
            + ", "
            + DateTimes.timeFormat().format(result.getElapsedRealtimeMillis()));

    if (Collections.isEmpty(states)) {
      states.put(state, 0L);
      lastState = state;
      lastStart = System.currentTimeMillis();
      return;
    }

    Long ltime = states.get(lastState);
    if (ltime != null) {
      ltime += (System.currentTimeMillis() - lastStart);
      states.put(lastState, ltime);
    }

    lastState = state;
    lastStart = System.currentTimeMillis();

    if (!states.containsKey(state)) {
      states.put(state, 0L);
    }
  }

  public static String toAddress(Address a) {
    StringBuilder sb = new StringBuilder();
    // if (Strings.isNotEmpty(a.getPostalCode())) {
    // sb.append(a.getPostalCode()).append(' ');
    // }
    // if (Strings.isNotEmpty(a.getCountryName())) {
    // sb.append(a.getCountryName()).append(' ');
    // }
    // if (Strings.isNotEmpty(a.getAdminArea())) {
    // sb.append(a.getAdminArea()).append(' ');
    // }
    // if (Strings.isNotEmpty(a.getSubAdminArea())) {
    // sb.append(a.getSubAdminArea()).append(' ');
    // }
    // if (Strings.isNotEmpty(a.getLocality())) {
    // sb.append(a.getLocality()).append(' ');
    // }
    // if (Strings.isNotEmpty(a.getSubLocality())) {
    // sb.append(a.getSubLocality()).append(' ');
    // }
    // if (Strings.isNotEmpty(a.getThoroughfare())) {
    // sb.append(a.getThoroughfare()).append(' ');
    // }
    for (int i = 0; i <= a.getMaxAddressLineIndex(); i++) {
      String line = a.getAddressLine(i);
      if (Strings.isEmpty(line)) {
        continue;
      }
      sb.append(line).append(' ');
    }
    return sb.toString().trim();
  }

  public static String getStateText(int status) {
    switch (status) {
      case DetectedActivity.IN_VEHICLE:
        return "in_vehicle";
      case DetectedActivity.ON_BICYCLE:
        return "on_bicycle";
      case DetectedActivity.RUNNING:
        return "running";
      case DetectedActivity.ON_FOOT:
        return "on_foot";
      case DetectedActivity.TILTING:
        return "tilting";
      case DetectedActivity.STILL:
        return "still";
      case DetectedActivity.UNKNOWN:
        return "unknown";
      default:
        return "unknown(" + status + ")";
    }
  }

  public static int getStateColor(int status) {
    switch (status) {
      case DetectedActivity.TILTING:
        return Color.LTGRAY;
      case DetectedActivity.STILL:
        return Color.GRAY;
      case DetectedActivity.UNKNOWN:
        return Color.DKGRAY;
      case DetectedActivity.ON_FOOT:
        return Color.GREEN;
      case DetectedActivity.RUNNING:
        return Color.BLUE;
      case DetectedActivity.ON_BICYCLE:
        return Color.YELLOW;
      case DetectedActivity.IN_VEHICLE:
        return Color.MAGENTA;
      default:
        return Color.WHITE;
    }
  }

  public static BitmapDescriptor getStateIcon(int status) {
    switch (status) {
      case DetectedActivity.STILL:
        return BitmapDescriptorFactory.fromResource(R.drawable.rest);
      case DetectedActivity.TILTING:
        return BitmapDescriptorFactory.fromResource(R.drawable.rest);
      case DetectedActivity.UNKNOWN:
        return BitmapDescriptorFactory.fromResource(R.drawable.stop);
      case DetectedActivity.ON_FOOT:
        return BitmapDescriptorFactory.fromResource(R.drawable.walk);
      case DetectedActivity.RUNNING:
        return BitmapDescriptorFactory.fromResource(R.drawable.run);
      case DetectedActivity.ON_BICYCLE:
        return BitmapDescriptorFactory.fromResource(R.drawable.run);
      case DetectedActivity.IN_VEHICLE:
        return BitmapDescriptorFactory.fromResource(R.drawable.vehicle);
      default:
        return BitmapDescriptorFactory.fromResource(R.drawable.stop);
    }
  }
}