package com.algobase.gpx;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;

import java.text.SimpleDateFormat;
import java.text.ParsePosition;

import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.SimpleTimeZone;

import android.util.Log;

import com.garmin.fit.*;

public class FitWriter {

  //static int TIME_OFFSET =  631065600; /* DateTime.OFFSET/1000 */

  int deg2semi(double d) { return (int)((1L << 31)*d/180.0); }

private class Lap {

  int    num_points;
  long   start_time;
  long   end_time;
  double start_lat; 
  double end_lat;
  double start_lon;
  double end_lon;
  long   total_time;
  long   total_breaks;
  long   elapsed_time; 
  float  total_dist; 
  float  avg_speed;
  float  max_speed;
  float  avg_hrate;
  float  max_hrate;
  float  avg_cadence;
  float  max_cadence;
  float  avg_power;
  float  max_power;
  int    total_ascent;
  int    total_descent;
  int    calories;

  public void add_break(long sec) { 
    // t: seconds
    total_breaks += sec; 
  }


  public void add_stop_event()
  { EventMesg msg = new EventMesg();
    msg.setTimestamp(new DateTime(end_time));
    msg.setEvent(Event.TIMER);
    msg.setEventType(EventType.STOP_ALL);
    msg.setEventGroup((short)0);
  }
  
  public void add_start_event(long t)
  { EventMesg msg = new EventMesg();
    msg.setTimestamp(new DateTime(end_time + t));
    msg.setEvent(Event.TIMER);
    msg.setEventType(EventType.START);
    msg.setEventGroup((short)0);
  }
  

  public void add_point(long t, double lon, double lat, float alt, 
                                                        float dist,
                                                        float speed,
                                                        int ascent,
                                                        int hrate,
                                                        int power,
                                                        int cad,
                                                        int tmp)
  {  
    // time: sec (-offset)

    if (num_points == 0)
    { start_time = t;
      start_lon = lon;
      start_lat = lat;
     }

    end_lon = lon;
    end_lat = lat;
    end_time = t; 

    elapsed_time = end_time - start_time;
    total_time = elapsed_time - total_breaks;
  
    total_dist = dist;
    total_ascent = ascent;
  
    if (speed > max_speed) max_speed = speed;
    if (hrate > max_hrate) max_hrate = hrate;
    if (power > max_power) max_power = power;
    if (cad > max_cadence) max_cadence = cad;
  
    avg_speed = dist/(t-start_time);
    avg_hrate = (num_points * avg_hrate + hrate)/(num_points+1);
    avg_power = (num_points * avg_power + power)/(num_points+1);
    avg_cadence = (num_points * avg_cadence + cad)/(num_points+1);
  
    num_points++;
  }

  LapMesg lap_message()
  {
    LapMesg lapMesg = new LapMesg();

    lapMesg.setMessageIndex(0); // ?

    lapMesg.setStartTime(new DateTime(start_time));
    lapMesg.setTimestamp(new DateTime(end_time));
    lapMesg.setStartPositionLat(deg2semi(start_lat));
    lapMesg.setStartPositionLong(deg2semi(start_lon));
    lapMesg.setEndPositionLat(deg2semi(end_lat));
    lapMesg.setEndPositionLong(deg2semi(end_lon));
    lapMesg.setTotalTimerTime((float)total_time);
    lapMesg.setTotalElapsedTime((float)elapsed_time);
    lapMesg.setTotalDistance(total_dist);
    lapMesg.setAvgSpeed(avg_speed);
    lapMesg.setMaxSpeed(max_speed);
    lapMesg.setAvgHeartRate((short)avg_hrate);
    lapMesg.setMaxHeartRate((short)max_hrate);
    lapMesg.setAvgCadence((short)avg_cadence);
    lapMesg.setMaxCadence((short)max_cadence);
    lapMesg.setAvgPower((int)avg_power);
    lapMesg.setMaxPower((int)max_power);
    lapMesg.setTotalAscent(total_ascent);
    lapMesg.setTotalDescent(total_descent);
    lapMesg.setTotalCalories(calories);
  
    lapMesg.setEvent(Event.LAP);
    lapMesg.setEventType(EventType.STOP);

  //lapMesg.setLapTrigger(LapTrigger.MANUAL);
    lapMesg.setLapTrigger(LapTrigger.SESSION_END); // 7
  
    lapMesg.setSport(Sport.CYCLING);
  
    return lapMesg;
   }

   SessionMesg session_message()
   {
     SessionMesg msg = new SessionMesg();

     msg.setMessageIndex(0); // ?

     msg.setStartTime(new DateTime(start_time));
     msg.setTimestamp(new DateTime(end_time));
     msg.setStartPositionLat(deg2semi(start_lat));
     msg.setStartPositionLong(deg2semi(start_lon));
     msg.setTotalTimerTime((float)total_time);
     msg.setTotalElapsedTime((float)elapsed_time);
     msg.setTotalDistance(total_dist);
     msg.setAvgSpeed(avg_speed);
     msg.setMaxSpeed(max_speed);
     msg.setAvgHeartRate((short)avg_hrate);
     msg.setMaxHeartRate((short)max_hrate);
     msg.setAvgCadence((short)avg_cadence);
     msg.setMaxCadence((short)max_cadence);
     msg.setAvgPower((int)avg_power);
     msg.setMaxPower((int)max_power);
     msg.setTotalAscent(total_ascent);
     msg.setTotalDescent(total_descent);
     msg.setTotalCalories(calories);

     msg.setNumLaps(lap_count);
     msg.setFirstLapIndex(current_first_lap);
   
     //msg.setTotalFatColaries(calories);
     msg.setTotalCycles(5000L);
   
     msg.setSport(Sport.CYCLING);
     msg.setSubSport(SubSport.GENERIC);
   
     msg.setEvent(Event.LAP); // 9
     msg.setEventType(EventType.STOP); // 1

   
    return msg;
   }
    
  
}; 

  File fit_file;

  FileEncoder encoder;

  String course_name = null;

  Lap current_lap;
  Lap current_trk;

  int lap_count;
  int trk_count;
  int current_first_lap;

  long   activity_total_time;


private String format(String pattern, Object... args)
{ return String.format(Locale.US,pattern,args); }


private void log(String msg) { Log.v("sTracksLog",msg); }

public FitWriter(java.io.File file)
{ fit_file = file;
  try {
  //encoder = new FileEncoder(file);
  encoder = new FileEncoder(file,Fit.ProtocolVersion.V2_0);
  } catch (FitRuntimeException e) { encoder = null; }

  lap_count = 0;
  trk_count = 0;
}


public void setCourseName(String name) {
  course_name = name;
}


/*
public void add_waypoint(double lon, double lat, double alt, 
                         long t, String name)
{
}
*/
  

public void begin_track(String trk_name)
{  
  // trk_name  yyyy-mm-dd-hh-mm

  current_trk = new Lap();
  current_lap = new Lap();

  current_first_lap = lap_count;

  if (trk_name.startsWith("20") && trk_name.length() >= 16)
  { String str = trk_name.substring(0,10) + "T" + 
                 trk_name.substring(11,13) + ":" +
                 trk_name.substring(14,16) + "Z";

    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
    //df.setTimeZone(new SimpleTimeZone(0,"UTC"));
    df.setTimeZone(TimeZone.getDefault());
    Date date = df.parse(str,new ParsePosition(0));
    if (date != null)
       current_trk.start_time = (date.getTime() - DateTime.OFFSET)/1000;
   }
  else
    current_trk.start_time = (new Date().getTime() - DateTime.OFFSET)/1000;


  // FileIdMessage

  long t = ((new Date()).getTime() - DateTime.OFFSET)/1000;

  FileIdMesg fileIdMesg = new FileIdMesg(); 

  if (course_name == null)
    fileIdMesg.setType(com.garmin.fit.File.ACTIVITY);
  else
    fileIdMesg.setType(com.garmin.fit.File.COURSE);

/*
  fileIdMesg.setManufacturer(9999);
  fileIdMesg.setProduct(9999);
*/

  // fake Edge-1000 (leaves elevation data and total ascent untouched)

  fileIdMesg.setManufacturer(com.garmin.fit.Manufacturer.GARMIN);
  fileIdMesg.setProduct(com.garmin.fit.GarminProduct.EDGE1000);

  fileIdMesg.setSerialNumber(12345L);
  fileIdMesg.setTimeCreated(new DateTime(t));

  encoder.write(fileIdMesg);


  // FileCreatorMessage

  FileCreatorMesg fileCreatorMesg = new FileCreatorMesg();
  fileCreatorMesg.setSoftwareVersion(1440);
  fileCreatorMesg.setHardwareVersion((short)255);

  encoder.write(fileCreatorMesg);
  

/*
  DeviceInfoMesg deviceInfoMesg = new DeviceInfoMesg();
  //deviceInfoMesg.setTimeStamp(new DateTime(t));
  deviceInfoMesg.setDeviceIndex((short)2);
  deviceInfoMesg.setDeviceType((short)12);
  deviceInfoMesg.setManufacturer(1);
  deviceInfoMesg.setProduct(0);
  deviceInfoMesg.setSerialNumber((long)12345);
  deviceInfoMesg.setSoftwareVersion(1.0f);
  deviceInfoMesg.setHardwareVersion((short)255);

  encoder.write(deviceInfoMesg);
*/



/*
  // UserProfileMesg
  UserProfileMesg userProfileMesg = new UserProfileMesg();
  userProfileMesg.setGender(Gender.FEMALE);
  userProfileMesg.setWeight(63.1F);
  userProfileMesg.setAge((short)99);
  userProfileMesg.setFriendlyName("User");

  encoder.write(userProfileMesg); // Encode the UserProfileMesg
*/

}



public void add_break(long duration)
{ // duration: msec
  long sec = duration/1000;
  current_lap.add_stop_event();
  current_lap.add_start_event(sec);
  current_lap.add_break(sec);
  current_trk.add_break(sec);
}


public void add_point(long t, double lon, double lat, float alt, 
                                                      float dist,
                                                      float speed,
                                                      int ascent,
                                                      int hrate,
                                                      int power,
                                                      int cad,
                                                      int tmp)
{ // t: msec
  t = (t - DateTime.OFFSET)/1000;

  if (current_trk.num_points == 0)
    t = current_trk.start_time;
  else
   if (t < current_trk.start_time) t = current_trk.start_time;

  current_lap.add_point(t,lon,lat,alt,dist,speed,ascent,hrate,power,cad,tmp);
  current_trk.add_point(t,lon,lat,alt,dist,speed,ascent,hrate,power,cad,tmp);
 
  // write track point (record message)
  RecordMesg msg = new RecordMesg();
  msg.setTimestamp(new DateTime(t));
  msg.setPositionLong(deg2semi(lon));
  msg.setPositionLat(deg2semi(lat));
  msg.setAltitude(alt);
  msg.setDistance(dist);
  msg.setSpeed(speed);
  msg.setHeartRate((short)hrate);
  msg.setPower(power);
  msg.setCadence((short)cad);
  msg.setTemperature((byte)tmp);

  encoder.write(msg);

}


public void add_lap()
{ LapMesg msg = current_lap.lap_message();
  encoder.write(msg);
  current_lap = new Lap();
  lap_count++;
}



public void finish_track()
{ add_lap();
  activity_total_time += current_trk.total_time;
  SessionMesg msg = current_trk.session_message();
  encoder.write(msg);
  trk_count++;
 } 

public void close()
{
  long t = ((new Date()).getTime() - DateTime.OFFSET)/1000;

  if (course_name == null)
  { ActivityMesg msg = new ActivityMesg();
    msg.setTimestamp(new DateTime(t));
    msg.setNumSessions(trk_count);
    msg.setTotalTimerTime((float)activity_total_time);
  
    //msg.setType(Activity.AUTO_MULTI_SPORT);
    msg.setType(Activity.MANUAL); // 0

    msg.setEvent(Event.TIMER);
    msg.setEventType(EventType.STOP);
    encoder.write(msg);
  }
  else
  { CourseMesg msg = new CourseMesg();
    msg.setName(course_name);
    encoder.write(msg);
   }

  encoder.close();
 }

}


