/*
 * Copyright 2010 Google 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.google.android.apps.mytracks.io.file.exporter;

import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorData;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;

import android.content.Context;
import android.location.Location;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.Locale;

/**
 * Write track as CSV to a file. See RFC 4180 for info on CSV. Output three
 * tables.<br>
 * The first table contains the track info. Its columns are:<br>
 * "Track name","Activity type","Track description" <br>
 * <br>
 * The second table contains the markers. Its columns are:<br>
 * "Marker name","Marker type","Marker description","Latitude (deg)","Longitude
 * (deg)","Altitude (m)","Bearing (deg)","Accuracy (m)","Speed (m/s)","Time"<br>
 * <br>
 * The thrid table contains the points. Its columns are:<br>
 * "Segment","Point","Latitude (deg)","Longitude (deg)","Altitude (m)","Bearing
 * (deg)","Accuracy (m)","Speed (m/s)","Time","Power (W)","Cadence (rpm)","Heart
 * rate (bpm)","Battery level (%)"<br>
 * 
 * @author Rodrigo Damazio
 */
public class CsvTrackWriter implements TrackWriter {

  private static final NumberFormat SHORT_FORMAT = NumberFormat.getInstance(Locale.US);

  static {
    SHORT_FORMAT.setMaximumFractionDigits(4);
  }

  private final Context context;
  private PrintWriter printWriter;
  private int segmentIndex;
  private int pointIndex;

  public CsvTrackWriter(Context context) {
    this.context = context;
  }

  @Override
  public String getExtension() {
    return TrackFileFormat.CSV.getExtension();
  }

  @Override
  public void prepare(OutputStream outputStream) {
    printWriter = new PrintWriter(outputStream);
    segmentIndex = 0;
    pointIndex = 0;
  }

  @Override
  public void close() {
    if (printWriter != null) {
      printWriter.flush();
      printWriter = null;
    }
  }

  @Override
  public void writeHeader(Track[] tracks) {
    writeCommaSeparatedLine(context.getString(R.string.generic_name),
        context.getString(R.string.track_edit_activity_type_hint),
        context.getString(R.string.generic_description));
    Track track = tracks[0];
    writeCommaSeparatedLine(track.getName(), track.getCategory(), track.getDescription());
    writeCommaSeparatedLine();
  }

  @Override
  public void writeFooter() {
    // Do nothing
  }

  @Override
  public void writeBeginWaypoints(Track track) {
    writeCommaSeparatedLine(context.getString(R.string.generic_name),
        context.getString(R.string.marker_edit_marker_type_hint),
        context.getString(R.string.generic_description),
        context.getString(R.string.description_location_latitude),
        context.getString(R.string.description_location_longitude),
        context.getString(R.string.description_location_altitude),
        context.getString(R.string.description_location_bearing),
        context.getString(R.string.description_location_accuracy),
        context.getString(R.string.description_location_speed),
        context.getString(R.string.description_time));
  }

  @Override
  public void writeEndWaypoints() {
    writeCommaSeparatedLine();
  }

  @Override
  public void writeWaypoint(Waypoint waypoint) {
    Location location = waypoint.getLocation();
    writeCommaSeparatedLine(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(),
        Double.toString(location.getLatitude()), Double.toString(location.getLongitude()),
        getAltitude(location), getBearing(location), getAccuracy(location), getSpeed(location),
        StringUtils.formatDateTimeIso8601(location.getTime()));
  }

  @Override
  public void writeBeginTracks() {
    // Do nothing
  }

  @Override
  public void writeEndTracks() {
    // Do nothing
  }

  @Override
  public void writeBeginTrack(Track track, Location startLocation) {
    writeCommaSeparatedLine(context.getString(R.string.description_track_segment),
        context.getString(R.string.description_track_point),
        context.getString(R.string.description_location_latitude),
        context.getString(R.string.description_location_longitude),
        context.getString(R.string.description_location_altitude),
        context.getString(R.string.description_location_bearing),
        context.getString(R.string.description_location_accuracy),
        context.getString(R.string.description_location_speed),
        context.getString(R.string.description_time),
        context.getString(R.string.description_sensor_power),
        context.getString(R.string.description_sensor_cadence),
        context.getString(R.string.description_sensor_heart_rate));
  }

  @Override
  public void writeEndTrack(Track track, Location endLocation) {
    // Do nothing
  }

  @Override
  public void writeOpenSegment() {
    segmentIndex++;
    pointIndex = 0;
  }

  @Override
  public void writeCloseSegment() {
    // Do nothing
  }

  @Override
  public void writeLocation(Location location) {
    String power = null;
    String cadence = null;
    String heartRate = null;
    if (location instanceof MyTracksLocation) {
      SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();

      if (sensorDataSet != null) {
        if (sensorDataSet.hasPower()) {
          SensorData sensorData = sensorDataSet.getPower();
          if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
            power = Double.toString(sensorData.getValue());
          }
        }
        if (sensorDataSet.hasCadence()) {
          SensorData sensorData = sensorDataSet.getCadence();
          if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
            cadence = Double.toString(sensorData.getValue());
          }
        }
        if (sensorDataSet.hasHeartRate()) {
          SensorData sensorData = sensorDataSet.getHeartRate();
          if (sensorData.hasValue() && sensorData.getState() == Sensor.SensorState.SENDING) {
            heartRate = Double.toString(sensorData.getValue());
          }
        }
      }
    }
    pointIndex++;
    writeCommaSeparatedLine(Integer.toString(segmentIndex), Integer.toString(pointIndex),
        Double.toString(location.getLatitude()), Double.toString(location.getLongitude()),
        getAltitude(location), getBearing(location), getAccuracy(location), getSpeed(location),
        StringUtils.formatDateTimeIso8601(location.getTime()), power, cadence, heartRate);
  }

  private String getAltitude(Location location) {
    return location.hasAltitude() ? Double.toString(location.getAltitude()) : null;
  }

  private String getBearing(Location location) {
    return location.hasBearing() ? Double.toString(location.getBearing()) : null;
  }

  private String getAccuracy(Location location) {
    return location.hasAccuracy() ? SHORT_FORMAT.format(location.getAccuracy()) : null;
  }

  private String getSpeed(Location location) {
    return location.hasSpeed() ? SHORT_FORMAT.format(location.getSpeed()) : null;
  }

  /**
   * Writes a single line of a CSV file.
   * 
   * @param values the values to be written as CSV
   */
  private void writeCommaSeparatedLine(String... values) {
    StringBuilder builder = new StringBuilder();
    boolean isFirst = true;
    for (String value : values) {
      if (!isFirst) {
        builder.append(',');
      }
      isFirst = false;

      builder.append('"');
      if (value != null) {
        builder.append(value.replaceAll("\"", "\"\""));
      }
      builder.append('"');
    }
    printWriter.println(builder.toString());
  }
}