package com.algobase.accounts;

import java.lang.StringBuilder;

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

import java.net.URL;
import java.net.HttpURLConnection;

import java.util.List;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Date;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.IOException;

import java.io.StringWriter;
import java.io.PrintWriter;

import java.net.URLEncoder;

import android.os.Handler;

import android.net.http.*;


import com.algobase.gpx.*;
import com.algobase.share.system.*;
import com.algobase.share.network.*;

import com.algobase.stracks.sTracksRoot;


public class Strava {

public static final String www = "www.strava.com";
public static final String api_url = "https://www.strava.com/api/v3";

  public void update(String access_token, 
                     String refresh_token, 
                     long expires_at,
                     boolean login) {} 

  public void writeLog(String s) {}
  public void showToast(String s) {}
  public void ack(String title, String s) {}


  String access_token;
  String refresh_token;
  long expiration;

  String errorText = "";
  String result = null;

  File file = null;

  File tmp_dir = null;

  int total_tracks = 0;
  int total_dist = 0;
  int total_ascent = 0;


  public Strava()
  { access_token = "";
    refresh_token = "";
    expiration = 0;
  }

  public void setTmpDir(File dir) { tmp_dir = dir; }

  public void setAccessToken(String token) { access_token = token; }
  public void setRefreshToken(String token) { refresh_token = token; }
  public void setExpiration(long t) { expiration = t; }

  public String getAccessToken() { return access_token; }
  public String getRefreshToken(){ return refresh_token; }
  public long   getExpiration()  { return expiration; }

  public String getError() { return errorText; }
  public String getResult() { return result; }

  public int getTotalTracks() { return total_tracks; }
  public int getTotalDistance() { return total_dist; }
  public int getTotalAscent() { return total_ascent; }


/*
  public void setFile(File f) { file = f; }
  public File   getFile() { return file; }
*/



   String encodeURL(String s)
   { try {
        s = URLEncoder.encode(s,"UTF-8");
     } catch(Exception ex) {}
     return s;
   }

   String unescapeUnicode(String s)
   { 
     if (s == null) return null;

     String txt = "";

     int p = 0;
     int q = s.indexOf("\\u"); // "\\uxxxx"
     while (q > 0 && s.charAt(q-1) == '\\') q = s.indexOf("\\u",q+1);

     while (q != -1)
     { txt += s.substring(p,q);
       int hex = Integer.parseInt(s.substring(q+2,q+6),16);
       txt += (char)hex;
       p = q+6;
       q = s.indexOf("\\u",p);
       while (q > 0 && s.charAt(q-1) == '\\') q = s.indexOf("\\u",q+1);
      }

     txt += s.substring(p);

     return txt;
   }


  void write_tmp_file(String fn, String txt)
  { 
    if (tmp_dir == null) return;

    //String fname = tmp_dir.getPath() + "/" + fn;
    File f = new File(tmp_dir,fn);

    try {
      //FileOutputStream out = new FileOutputStream(fname);
      FileOutputStream out = new FileOutputStream(f);
      out.write(txt.getBytes());
      out.write('\n');
      out.close();
     }  catch (IOException e) {}
  }

  private String jsonValue(String tag, int pos)
  { if (result == null) return null;

    int p = result.indexOf("\"" + tag + "\":",pos);
    if (p == -1) return null;

    p += tag.length() + 3;
    if (result.charAt(p) == '"') p++;
    int q = result.indexOf(",",p);
    if (q == -1) 
       q = result.length();
    else
    { if (result.charAt(q-1) == '}') q--;
      if (result.charAt(q-1) == '"') q--;
     }
    return result.substring(p,q);
  }



String readStream(InputStream in) 
{
  BufferedReader reader = null;
  StringBuilder builder = new StringBuilder();

  try {
      reader = new BufferedReader(new InputStreamReader(in));
      String line = "";
      while ((line = reader.readLine()) != null)  builder.append(line);
  } catch (IOException e) {
      e.printStackTrace();
  } finally {
      if (reader != null) {
          try {
              reader.close();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  }

  return builder.toString();
}

  public void logout()
  { access_token = "";
    refresh_token = "";
    expiration = 0;
   }

  public void login(final String code) { 
    new MyThread() {
      public void run() { refreshAndExec(code,null,null); }
    }.start();
  }

  public void refresh() { 
    new MyThread() {
      public void run() { refreshAndExec(null,null,null); }
    }.start();
  }


  private void refreshAndExec(final String code,
                              final String url_str,
                              final String method)
  {
    // code == null: refresh token
    // code != null: retrieve token using code (login)

    writeLog("");
    writeLog("Strava: refreshAndExec");
    writeLog("code = " + code);
    writeLog("url = " + url_str);
    writeLog("method = " + method);

    long remaining_sec = expiration - System.currentTimeMillis()/1000;

    writeLog("remaining: " + remaining_sec + " sec");

    if (code == null && remaining_sec > 300)
    { // no refresh necessary
      writeLog("no refresh necessary");
      if (url_str != null) executeRequest(url_str,method);
      return;
     }

    writeLog("refreshing token");

    final boolean login = code != null;

    final HttpsClient https_client = new HttpsClient();
    https_client.setHost("www.strava.com");
    https_client.setPort("443");
    https_client.setSSL(true);
    https_client.setRequestType("POST");


    String url  = "api/v3/oauth/token";
    url += "?client_id=" + sTracksRoot.STRAVA_CLIENT_ID;
    url += "&client_secret=" + sTracksRoot.STRAVA_CLIENT_SECRET;

    if (login)
    { url += "&grant_type=authorization_code";
      url += "&code=" + code;
     }
    else
    { url += "&grant_type=refresh_token";
      url += "&refresh_token=" + refresh_token;
     }


    HttpsClient.HttpsResult https_result = https_client.loadString(url);

    if (https_result.hasError()) 
    { writeLog("Strava: " + https_result.getErrorText());
      //showToast("Strava: " + https_result.getErrorText());
      if (code != null) access_token = "";
      return;
     }


    String s = https_result.getString();

    int p;

    p = s.indexOf("access_token");
    if (p != -1)
    { p = s.indexOf(":",p);
      int q = s.indexOf(",",p);
      if (q == -1) q = s.indexOf("}",p);
      access_token = s.substring(p+2,q-1);
     }

    p = s.indexOf("refresh_token");
    if (p != -1)
    { p = s.indexOf(":",p);
      int q = s.indexOf(",",p);
      if (q == -1) q = s.indexOf("}",p);
      refresh_token = s.substring(p+2,q-1);
     }
     
    p = s.indexOf("expires_at\":");
    if (p != -1)
    { p = s.indexOf(":",p);
      int q = s.indexOf(",",p);
      if (q == -1) q = s.indexOf("}",p);
      expiration = Integer.parseInt(s.substring(p+1,q));
     }

    update(access_token, refresh_token, expiration, login);

    if (url_str != null) executeRequest(url_str,method);

  }

 
  void executeRequest(String url_str, String method)
  { 
    // method: POST, GET, DELETE

    result = null;

    //errorText = null;
    errorText = "";

    String authorizationHeader = "Bearer " + access_token;

    try {

      URL url = new URL(url_str);

      HttpURLConnection conn = (HttpURLConnection)url.openConnection();
 
      conn.setRequestMethod(method);

      conn.setUseCaches(false);
      conn.setDoInput(true);

      conn.setRequestProperty("Authorization",authorizationHeader);
      conn.setRequestProperty("Accept-Charset","UTF-8");

      if (method.equals("GET")) {
       }

      if (method.equals("POST"))
      { conn.setDoOutput(true);
        conn.setRequestProperty("Content-Type", 
                                       "application/x-www-form-urlencoded");
       }

      if (method.equals("DELETE")) {
          int status = conn.getResponseCode();
          if (status < 400) result = "Activity has been removed.";
          conn.disconnect();
          return;
       }


      //conn.connect();

      InputStream in = conn.getInputStream();

      byte[] buffer = new byte[1024];

      FileOutputStream out = null;
      StringBuilder builder = new StringBuilder();

      if (file != null) out = new FileOutputStream(file);
       
      int n = 0;
      long count = 0;
      while ((n = in.read(buffer)) != -1) 
      { if (out != null)
          out.write(buffer,0,n);
        else
          builder.append(new String(buffer,0,n));
        count += n;
       }
      in.close();

      if (out != null) 
         out.close();
      else
         { result = builder.toString();
           if (result != null) result = unescapeUnicode(result);
          }

      conn.disconnect();

    } catch(Exception e) { errorText = "Strava Request: " + e.toString(); 
                           //StringWriter sw = new StringWriter();
                           //e.printStackTrace(new PrintWriter(sw));
                           //errorText = sw.toString(); 
                          }

    if (errorText != null) {
      //ack("Error", errorText);
        writeLog("Strava Error: " + errorText);
    }

  }


  public void getCurrentAthlete(String data[]) 
  { 
    String url = api_url + "/athlete"; 
    refreshAndExec(null,url,"GET");

    if (result == null || data == null) return;

     data[0] = jsonValue("firstname",0);
     data[1] = jsonValue("lastname",0);
     data[2] = jsonValue("email",0);
  }


/*
  public void writeResult(File file) 
  { if (result == null) result = errorText;
    byte[] bytes = result.getBytes();
    try {
    FileOutputStream out = new FileOutputStream(file);
    out.write(bytes,0,bytes.length);
    out.close();
    } catch (IOException e) {}
  }
*/


  public void loadAthleteTotals() 
  { 
    refreshAndExec(null, api_url+"/athlete", "GET");
    if (result == null) return;

    String id = jsonValue("id",0);

    String url = api_url + "/athletes/" + id + "/stats"; 

    refreshAndExec(null,url,"GET");
    if (result == null) return;

    int pos = result.indexOf("\"all_ride_totals\"");

    if (pos == -1) return;

    try {
      total_tracks = Integer.parseInt(jsonValue("count",pos));
      total_dist = Integer.parseInt(jsonValue("distance",pos));
      total_ascent = Integer.parseInt(jsonValue("elevation_gain",pos));
    } catch(Exception ex) {}

  }



  public void uploadActivity(String name, File file, String format, 
                                                     String type,
                                                     String description) 
  { 
    String url = api_url + "/uploads"; 
    errorText = "";
    result = null;

    String authorizationHeader = "Bearer " + access_token;

//result = "file length: " + file.length();

    try {

       MultipartUtility mp = new MultipartUtility(url,authorizationHeader,
                                                                     "UTF-8");

       mp.addField("name",name);
       mp.addField("data_type",format);
       mp.addField("activity_type",type);
       mp.addField("description",description);
       mp.addFile("file",file);

       result = mp.connect();

     } catch (Exception e) { errorText = e.toString(); }

  }

  public void checkUploadStatus(String id)
  { String url = api_url + "/uploads/" + id; 
    refreshAndExec(null,url,"GET");
   }



  public void retrieveActivity(String id) 
  { String url = api_url + "/activities/" + id; 
    refreshAndExec(null,url,"GET");
    if (result == null) return;

    File f = file;
    file = null;

    int p = result.indexOf("\"polyline\":");
    if (p == -1) return;
    int q = result.indexOf("\"",p+12);

    String poly_string = result.substring(p+12,q);

    Polyline pl = new Polyline(poly_string.replace("\\\\","\\"));

    List<Polyline.Point> L = pl.decode();

    StringBuilder builder = new StringBuilder();

    builder.append(String.format("length = %d\n",L.size()));

    for(int i=0;i<L.size();i++) 
        builder.append(L.get(i).toString());

    String x = builder.toString();
    
    file = f;
    if (file != null) 
    { try {
        FileOutputStream out = new FileOutputStream(file);
        out.write(x.getBytes());
      } catch (IOException e) {}
     }
  }


  private int parseStream (String txt, String type, double data[])
  { 

    int p = 0;

    p = txt.indexOf("\"type\":\"" + type + "\"",p);
    if (p == -1) return 0;

    p = txt.indexOf("\"data\"",p);
    if (p == -1) return 0;

    p = txt.indexOf("[",p);
    if (p == -1) return 0;
    p++;

    int stop = txt.indexOf("]",p);
    if (stop == -1) return 0;

    int count = 0;

    while (p < stop)
    { while (Character.isWhitespace(txt.charAt(p))) p++;
      int q = p;
      while (txt.charAt(q) != ',' && txt.charAt(q) != ']') q++;
      if (data != null) {
         //data[count] = Float.parseFloat(txt.substring(p,q));
         try {
           data[count] = Float.parseFloat(txt.substring(p,q));
         } catch(Exception ex) { data[count] = 0; }
      }
      p = q+1;
      count++;
    }

    return count;
  }


  private int parseStreamLatLng(String txt, double lat[], double lon[])
  { 
    int p = 0;

/*
    p = txt.indexOf("\"type\"",p);
    if (p == -1) return 0;

    p = txt.indexOf(type,p);
    if (p == -1) return 0;
*/
    p = txt.indexOf("\"type\":\"latlng\"",p);
    if (p == -1) return 0;

    p = txt.indexOf("\"data\"",p);
    if (p == -1) return 0;


    p = txt.indexOf("[",p);
    if (p == -1) return 0;
    p++;

/*
    int stop = txt.indexOf("]",p);
    if (stop == -1) return 0;
*/
    int stop = txt.length()-1;

    int count = 0;

    while (p < stop)
    { while (txt.charAt(p) != '[') p++;
      p = p+1;
      int q = p;
      while (txt.charAt(q) != ',') q++;
      lat[count] = Float.parseFloat(txt.substring(p,q));
      p = q+1;
      while (txt.charAt(q) != ']') q++;
      lon[count] = Float.parseFloat(txt.substring(p,q));
      count++;
      p = q+1;
      if (txt.charAt(p) != ',') break;
    }

    return count;
  }




  public void downloadActivity(String id, File trk_file) 
  { 
    String info = getDescription(id);


    if (info == null)
    { write_tmp_file("strava_error.txt","ERROR = " + errorText);
      return;
     }

    //showToast(info);

    write_tmp_file("strava_info.txt",info);

    String descr = null;
    String labels = null;

    int p = info.indexOf("name=");
    if (p != -1)
    { int q = info.indexOf(";",p);
      descr = info.substring(p+5,q);
      if (descr.startsWith("ROUVY")) 
      { descr = descr.replace("ROUVY - ","");
        descr = descr.replaceAll("\\| ","");
        labels = "Rolle@Rouvy";
      }
    }

    showToast(descr);

    int start_t = 0;
  
    p = info.indexOf("start=");
    if (p != -1)
    { int q = info.indexOf(";",p);
      String s = info.substring(p+6,q);
      start_t = Integer.parseInt(s); // sec !
     }

    String url = api_url + "/activities/" + id + "/streams/";
    url += "time";
    url += ",latlng";
    url += ",distance";
    url += ",altitude";
    url += ",velocity_smooth";
    url += ",heartrate";
    url += ",cadence";
    url += ",watts";
    url += ",temp";
    url += "?resolution=high";

    refreshAndExec(null,url,"GET");

    String txt = result;

    if (txt == null)
    { showToast("downloadActivity: txt =  null");
      return;
    }

    int length = parseStream (txt,"time",null);

    write_tmp_file("strava_stream.txt",txt);


/*
    String s;
    s = jsonValue("original_size",0);
    int original_size = Integer.parseInt(s);
*/


    double lat[] = new double[length];
    double lon[] = new double[length];
    parseStreamLatLng(txt,lat,lon);

    double time[] = new double[length];
    parseStream(txt,"time",time);

    double alt[] = new double[length];
    parseStream(txt,"altitude",alt);

    double dst[] = new double[length];
    parseStream(txt,"distance",dst);

    double spd[] = new double[length];
    parseStream(txt,"velocity_smooth",spd);

    double hrt[] = new double[length];
    parseStream(txt,"heartrate",hrt);

    double pwr[] = new double[length];
    parseStream(txt,"watts",pwr);


    String track_name = trk_file.getName().replace(".trk","");
/*
    if (label1 != null) track_name += "\n@" + label1;
    if (label2 != null) track_name += "@" + label2;
    if (descr  != null) track_name += "\n!" + descr;
*/

    String device_name = "";

    info += "points=" + length + ";";

    TrkWriter trk_writer = new TrkWriter(trk_file);

    trk_writer.begin_track(track_name,device_name,labels,descr,info);

    for(int i=0; i<length; i++)
    { long t = start_t + (int)time[i];
      long msec = 1000*t; // trk_writer takes msec !
      double prec = 5.0;
      double asc = 0;
      int cad = 0;
      int temp = 0;
      trk_writer.add_point(0,msec,lon[i],lat[i],alt[i],prec,spd[i],dst[i],asc,
                             (int)hrt[i],(int)pwr[i],cad,temp);
    }

    //trk_writer.add_break(t);
    //trk_writer.add_lap();

    trk_writer.finish_track();

  }


  public void deleteActivity(String id) 
  { String url = api_url + "/activities/" + id; 
    refreshAndExec(null,url,"DELETE");
  }

  public void listActivityLaps(String id) 
  { String url = api_url + "/activities/" + id + "/laps"; 
    refreshAndExec(null,url,"GET");
  }

  public void listActivityZones(String id) 
  { String url = api_url + "/activities/" + id + "/zones"; 
    refreshAndExec(null,url,"GET");
  }



  public ArrayList<String> getTrackList(int page, int page_size) 
  { 
    String url = api_url + 
                 "/athlete/activities?page=" + page + "&per_page=" + page_size;
    refreshAndExec(null,url,"GET");


    ArrayList<String> L = new ArrayList<String>();

    if (result == null) return L;

/*
    int pos = 0;
    while (pos < result.length() && result.charAt(pos) != '{') pos++;
    // invariant: pos = position of beginning '{' for next activity 
*/

    int pos = result.indexOf("\"name\"");

    //while (pos < result.length())
    while (pos != -1)
    {
      String s;

      String id = null;
      s = jsonValue("id",pos);
      if (s != null) id = s;

      String manual = "false";
      s = jsonValue("manual",pos);
      if (s != null) manual = s;

      String name =  "yyyy-mm-dd hh:mm";
      s =  jsonValue("start_date_local",pos);
      if (s != null) name = s;

      float dst = 0.0f;
      s = jsonValue("distance",pos);
      if (s != null) dst = Float.parseFloat(s);

      float asc = 0.0f;
      s  = jsonValue("total_elevation_gain",pos);
      if (s != null) asc = Float.parseFloat(s);

      float spd = 0.0f;
      s  = jsonValue("average_speed",pos);
      if (s != null) spd = 3.6f*Float.parseFloat(s);

      int   sec = 0;
      s  = jsonValue("moving_time",pos);
      if (s != null) sec = Integer.parseInt(s);

      // yyyy-mm-dd hh:mm id km h m

      int h = sec/3600;
      int m = (sec-3600*h)/60;

      String trk = name.substring(0,10)  + " " + name.substring(11,16);
      
      trk += String.format(Locale.US,
              " %5.1f km %2d:%02d h  %4.0f m  %4.1f km/h",dst/1000,h,m,asc,spd);


      trk += " manual=" + manual;
      trk += " id=" + id;

      L.add(trk);

/*
      int q = result.indexOf("has_kudoed",pos); // last trk value ?
      if (q != -1) pos = q;

      int count = 1;
      while (count > 0 && ++pos < result.length())
      { char c = result.charAt(pos);
        if (c == '{') count++;
        if (c == '}') count--;
       }

      while (pos < result.length() && result.charAt(pos) != '{') pos++;
*/

      pos = result.indexOf("\"name\"",pos+6);

    }

    return L;
  }


  public String getDescription(String id) 
  { 
    //showToast("track id = " + id);

    String url = api_url + "/activities/" + id; 
    refreshAndExec(null,url,"GET");

    if (result == null) return "";

    String txt = "";
    String s;


    int moving_time = 0;
    int elapsed_time = 0;

    if ((s = jsonValue("name",0)) != null && !s.equals("null")) 
      txt += "name=" + s + ";";

    if ((s = jsonValue("description",0)) != null && !s.equals("null")) 
      txt += "description=" + s + ";";

    if ((s = jsonValue("moving_time",0)) != null) 
    { moving_time = Integer.parseInt(s);
      txt += "time=" + s + ";";
     }

    if ((s = jsonValue("elapsed_time",0)) != null) 
    { elapsed_time = Integer.parseInt(s);
      txt += "elapsed_time=" + s + ";";
      txt += "breaks=" + (elapsed_time - moving_time) + ";";
     }

    if ((s = jsonValue("start_date_local",0)) != null) 
    { SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
      Date d = f.parse(s,new ParsePosition(0));
      long t = d.getTime()/1000; // sec since epoche (1970)
      txt += "start=" + t + ";";
      txt += "end=" + (t + elapsed_time) + ";";
     }

    if ((s = jsonValue("distance",0)) != null) 
      txt += "dist=" + s + ";";

    if ((s = jsonValue("total_elevation_gain",0)) != null) 
      txt += "ascent=" + s + ";";


    if ((s = jsonValue("average_speed",0)) != null) 
      txt += "avgspeed=" + s + ";";

    if ((s = jsonValue("max_speed",0)) != null) 
      txt += "maxspeed=" + s + ";";

    if ((s = jsonValue("average_heartrate",0)) != null) 
      txt += "avghrate=" + s + ";";

    if ((s = jsonValue("max_heartrate",0)) != null) 
      txt += "maxhrate=" + s + ";";

    if ((s = jsonValue("average_watts",0)) != null) 
      txt += "avgpower=" + s + ";";

    if ((s = jsonValue("max_watts",0)) != null) 
      txt += "maxpower=" + s + ";";

    int laps = countLaps(id);
    txt += "laps=" + laps + ";";

    return txt;
  }


  int countLaps(String id) 
  { 
    String url = api_url + "/activities/" + id + "/laps"; 
    refreshAndExec(null,url,"GET");

    if (result == null) return 0;

    int count = 0;

    int p = result.indexOf("lap_index");
    while (p != -1)
    { count++;
      p = result.indexOf("lap_index",p+8);
     }

    return count;

  }


}
