package com.algobase.share.geo;

import java.lang.OutOfMemoryError;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.BufferedWriter;

import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.Locale;

import java.text.SimpleDateFormat;

import android.location.Location;
import android.os.Handler;
import android.os.Environment;
import android.util.Log;

import com.algobase.share.network.*;
import com.algobase.share.dialog.*;
import com.algobase.share.compat.*;
import com.algobase.share.system.*;


public class srtm3Matrix 
{
  //String srtm3_url = "http://chomsky.uni-trier.de/biking/srtm3.cgi?";
  String srtm3_url = "https://dds.cr.usgs.gov/srtm/version2_1/SRTM3/";

  int xserver_port = 9668;

  final String[] srtm3_regions = { "Eurasia",
                                   "North_America", 
                                   "South_America",
                                   "Africa", 
                                   "Australia", 
                                   "Islands" };

  
  public static final double DISTANCE_UNDEFINED = -9999; 
  // sTracksRoot.DISTANCE_UNDEFINED;


  final int width  = 1201;
  final int height = 1201;
//final int bytes  = 2 * width * height;

  final int MAX_DOWNLOADS_PER_DAY = 64;

  File  srtm3_dir;

  //Handler handler = new Handler();

  boolean auto_download = true;

  short[]  table = null;

  double[] alt_values = new double[8];

  double alt_sum = 0;
  double alt_avg = DISTANCE_UNDEFINED;

  int alt_count = 0;

  boolean initializing = false;
  boolean data_available = false;

  double lon_min;
  double lon_max;
  double lat_min;
  double lat_max;

  protected void write_log(String msg) { Log.v("SRTM3",msg); }
  protected void show_error(String msg) {}
  protected void onTileDownload(File file) {}


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


  private double orientation(double ax, double ay, double bx, double by,
                                                   double cx, double cy)
  {
    return Math.signum((ax - bx) * (ay - cy) - (ay - by) * (ax - cx));
   }


  private double interpolate_plane(double Ax, double Ay, double Az,
                                   double Bx, double By, double Bz,
                                   double Cx, double Cy, double Cz,
                                   double xx, double yy)
  {
    // find equation for plane through points A,B,C  
    // a*x + b*y + c*z + d = 0

    // then z = -(a*xx + b*yy + d)/c;

     double x1 = Bx - Ax;
     double y1 = By - Ay;
     double z1 = Bz - Az;
     double x2 = Cx - Ax;
     double y2 = Cy - Ay;
     double z2 = Cz - Az;

     double a = z1*y2 - y1*z2;
     double b = x1*z2 - z1*x2;
     double c = y1*x2 - x1*y2;

     double d = -(a*Ax + b*Ay + c*Az);

     return -(a*xx + b*yy + d)/c;
 }
 

  private String hgt_name(double lat, double lon)
  { 
    // lat and lon are integer !

    int lat_tile = (int)lat;
    int lon_tile = (int)lon;

    if (lat_tile >= 0)
    { if (lon_tile >= 0)
        return format("N%02dE%03d",lat_tile,lon_tile);
      else
        return format("N%02dW%03d",lat_tile,-lon_tile);
     }
    else
    {  if (lon_tile >= 0)
        return format("S%02dE%03d",-lat_tile,lon_tile);
      else
        return format("S%02dW%03d",-lat_tile,-lon_tile);
     }
  }


public void set_url(String url) { 
   srtm3_url = url; 
   write_log("set url = " + url);
}

public void set_auto_download(boolean b) { auto_download = b; }

public double lat_min() { return lat_min; }
public double lat_max() { return lat_max; }
public double lon_min() { return lon_min; }
public double lon_max() { return lon_max; }


    private boolean get_http(String url_str, File output_file, 
                                             final MyProgressDialog progress)
    { write_log("get_http: " + url_str);

      HttpClient http_client = new HttpClient(url_str) {
        @Override
        public void showProgress(final int kb) {
              if (progress != null) progress.setProgress(kb); 
        }

     };

     http_client.setReadTimeout(300000); // 5 min

     if (http_client.getFile(output_file)) return true;

     String err = http_client.getError();
     write_log("HTTP Client Error");
     write_log(err);

     String redirect_url = http_client.getRedirectUrl();

     if (redirect_url != null && !redirect_url.equals(url_str))
     { write_log("HTTP Redirect: " + redirect_url);
       return get_http(redirect_url,output_file,progress);
      }

     return false;
   }

  boolean receive_file(String host, File file, String name)
  {
    write_log("srtm3: receive " + name);

    File tmp_file = new File(file.getPath() + ".tmp");
    if (tmp_file.exists()) {
      write_log("srtm3: in progress " + file.getName());
      return true;
     }
    try { tmp_file.createNewFile(); } catch(Exception ex) {}

    String tile_name = file.getName().replace(".hgt.zip","");

    LedaSocket sock = new LedaSocket();
    if (!sock.connect(host,9667))
    { String err = String.format("%s: %s ",host,sock.getError());
      write_log(err);
      show_error(err);
      return false;
    }

    boolean success = false;

    int r = sock.receiveInt();
    sock.sendInt(0);
    sock.wait("ok");

    sock.sendString("login:A0.00:anonymous:no_check");
    sock.receiveString();

    sock.sendString("send-file");
    sock.wait("ok");
    sock.sendString(name);
    String msg = sock.receiveString();
    write_log("msg = " + msg);

    if (msg.startsWith("error:"))
    { // file not existing on server: create empty file
      write_log("create empty srtm3 file");
      try { file.createNewFile(); } catch (Exception ex) {}
      success = true;
     }
    else
    if (msg.equals("ok"))
    { sock.sendString("ok");
      sock.receiveFile(tmp_file);
      success = true;
     }

    sock.sendString("exit");
    write_log("disconnect");
    sock.disconnect();
    sock = null;

    if (tmp_file.exists()) {
      tmp_file.renameTo(file);
      onTileDownload(file);
    }

    return success;
  }


public boolean download_tile(File file, final MyProgressDialog progress)
{
  GregorianCalendar cal = new GregorianCalendar();
  Date dt = cal.getTime();
  String current_date = new SimpleDateFormat("yyyy-MM-dd").format(dt);

  File downloads_file = new File(srtm3_dir,"downloads.txt");
  String last_date = null;
  String count_str = null;

  int count = 0;

  if (downloads_file.exists()) {
     try { 
       FileReader file_reader = new FileReader(downloads_file);
       BufferedReader reader = new BufferedReader(file_reader);
       last_date = reader.readLine();
       count_str = reader.readLine();
       reader.close();
     } catch(Exception e) { write_log("srtm3: " + e.toString()); }
  }

  if (last_date != null && count_str != null) {   
   if (last_date.equals(current_date)) count = Integer.parseInt(count_str);
  }

  write_log("srtm3: downloads today = " + count);

  if (count >= MAX_DOWNLOADS_PER_DAY) {
    write_log("srtm3: MAX_DOWNLOADS_PER_DAY = " + MAX_DOWNLOADS_PER_DAY);
    return false;
  }
      
     
  String name = file.getName();


  write_log("srtm3: download " + name + "  count = " + count);

  if (progress != null)
  { progress.setMax(1024);
    progress.setProgress(0);
   }


/*
  for(int i=0; i<srtm3_regions.length; i++)
  { String url = srtm3_url + "/" + srtm3_regions[i] + "/" + name;
    write_log("download http: " + url);
    if (get_http(url,file,progress)) {
      // move region to front
      if (i > 0)
      { String s = srtm3_regions[0];
        srtm3_regions[0] = srtm3_regions[i];
        srtm3_regions[i] = s;
       }
      return true;
    }
  }
*/


  int p = srtm3_url.indexOf("/");
  String xhost = srtm3_url.substring(0,p);
  String xpath = srtm3_url.substring(p) + "/" + name;
  write_log("srtm3: xhost = " + xhost);
  write_log("srtm3: xpath = " + xpath);

  XClient xc = new XClient();
  xc.setHost(xhost);
  xc.setPort(xserver_port);
  boolean  download_ok = xc.getFile(xpath,file);
  String download_err = xc.getError();
  xc.disconnect();


  if (download_ok)
  { count++;
    try {
      FileWriter file_writer = new FileWriter(downloads_file);
      BufferedWriter writer = new BufferedWriter(file_writer);
      writer.write(current_date);
      writer.newLine();
      writer.write(format("%d",count));
      writer.newLine();
      writer.close();
    } catch(Exception e) { write_log("srtm3: " + e.toString()); }

    return true;
  }

  show_error("srtm3: download failed.");
 
  write_log("srtm3: download failed.");
  if (download_err != null) write_log("srtm3: " + download_err);
  write_log("srtm3: delete " + file.getName());
  write_log("");

  if (file.exists()) file.delete();

  return false;
}



public srtm3Matrix(File dir, Location loc, String url)
{
  srtm3_dir = dir;

  if (url != null) srtm3_url = url;

  lon_min = 0;
  lon_max = 0;
  lat_min = 0;
  lat_max = 0;
  table = null;

  if (loc != null) init(loc.getLatitude(),loc.getLongitude());
 }


public void deleteFiles()
{ File[] files = srtm3_dir.listFiles();
  for(int i=0; i<files.length; i++) files[i].delete();
}


void init(double lat, double lon) 
{ 
  write_log(format("srtm3: init %.6f  %.6f",lat,lon)); 

  if (table == null)
  { write_log("srtm3: allocate table");

    try {
      table = new short[width*height]; 
    } catch(OutOfMemoryError e) {}

    if (table == null)
    { write_log("srtm3: out of memory"); 
      return;
     }
   }

  if (initializing) 
  { write_log("srtm3: init still in progress"); 
    return;
   }

  initializing = true;
  data_available = false;

  // round to 0.5 - grid

  double lon_rounded = 0.5*Math.round(2*lon);
  double lat_rounded = 0.5*Math.round(2*lat);

  lon_min = lon_rounded - 0.5;
  lon_max = lon_rounded + 0.5;

  lat_min = lat_rounded - 0.5;
  lat_max = lat_rounded + 0.5;

  for(int i=0; i<table.length; i++) table[i] = -32768;

  read_data();
}


void read_data()
{ 
  write_log("srtm3: read_data download = " + auto_download);
  write_log(format("Bounds: %.2f %.2f %.2f %.2f",
                          lat_min,lat_max,lon_min,lon_max));

  double lat_frac = Math.abs(lat_min) % 1;
  double lon_frac = Math.abs(lon_min) % 1;

  data_available = true;

  if (lat_frac == 0 && lon_frac == 0)
  { String hgt = hgt_name(lat_min,lon_min);

    write_log("");
    write_log(format("srtm3: %s(%d,%d,%d,%d)", hgt,0,0,1200,1200));
    write_log("");

    if (!read_tile(hgt,0,0,1200,1200,0,0)) data_available = false;
  }


  if (lat_frac == 0.5 && lon_frac == 0)
  { 
    double d = (lat_max > 0) ? -0.5 : +0.5;
    String hgt1 = hgt_name(lat_max+d,lon_min);
    String hgt2 = hgt_name(lat_min+d,lon_min);

    write_log("");
    write_log(format("srtm3: %s(%d,%d,%d,%d)", hgt1,0,600,1200,600));
    write_log(format("srtm3: %s(%d,%d,%d,%d)", hgt2,0,0,1200,600));
    write_log("");

    if (!read_tile(hgt1,0,600,1200,600, 0,  0)) data_available = false;
    if (!read_tile(hgt2,0,  0,1200,600, 0,600)) data_available = false;
   }


  if (lat_frac == 0 && lon_frac == 0.5)
  { 
    double d = (lon_max > 0) ? -0.5 : +0.5;
    String hgt1 = hgt_name(lat_min,lon_min+d);
    String hgt2 = hgt_name(lat_min,lon_max+d);

    write_log("");
    write_log(format("srtm3: %s(%d,%d,%d,%d)", hgt1,600,0,600,1200));
    write_log(format("srtm3: %s(%d,%d,%d,%d)", hgt2,0,0,600,1200));
    write_log("");

    if (!read_tile(hgt1,600,0,600,1200, 0,  0)) data_available = false;
    if (!read_tile(hgt2,0,  0,600,1200, 600,0)) data_available = false;
   }


  if (lat_frac == 0.5 && lon_frac == 0.5)
  { 
    double dx = (lon_max > 0) ? -0.5 : +0.5;
    double dy = (lat_max > 0) ? -0.5 : +0.5;
    String hgt1 = hgt_name(lat_max+dy,lon_min+dx);
    String hgt2 = hgt_name(lat_max+dy,lon_max+dx);
    String hgt3 = hgt_name(lat_min+dy,lon_min+dx);
    String hgt4 = hgt_name(lat_min+dy,lon_max+dx);

    write_log("");
    write_log(format("srtm3: %s(%3d,%3d)", hgt1,600,600,600,600));
    write_log(format("srtm3: %s(%3d,%3d)", hgt2,  0,600,600,600));
    write_log(format("srtm3: %s(%3d,%3d)", hgt3,600,  0,600,600));
    write_log(format("srtm3: %s(%3d,%3d)", hgt4,  0,  0,600,600));
    write_log("");

    if (!read_tile(hgt1,600,600,600,600,   0,   0)) data_available = false;
    if (!read_tile(hgt2,0,  600,600,600, 600,   0)) data_available = false;
    if (!read_tile(hgt3,600,  0,600,600,   0, 600)) data_available = false;
    if (!read_tile(hgt4,0,    0,600,600, 600, 600)) data_available = false;
  }

  if (!data_available) {
    write_log("srtm3: data not available (read errors)");
    table = null;
  }

  initializing = false;
}


boolean read_tile(String hgt, int tile_x, int tile_y, int tile_w, int tile_h,
                                                      int xpos,   int ypos)
{ 
  int tile_x1 = tile_x + tile_w;
  int tile_y1 = tile_y + tile_h; 

/*
  write_log("");
  write_log(format("%s(%3d,%3d,%3d,%3d) --> %3d %3d", 
                                hgt,tile_x,tile_y,tile_x1,tile_y1,xpos,ypos));
*/

  final File file = new File(srtm3_dir, hgt + ".hgt.zip");

  if (!file.exists()) 
  { if (auto_download)
    {  new MyThread() {
          public void run() { download_tile(file,null); }
       }.start();
    }
    //write_log("file not existing");
    return false;
   }

  File tmp_file = new File(file.getPath() + ".tmp");
  if (tmp_file.exists())
  { // download in progress
    //show_error("tmp file");
    write_log("tmp_file existing");
    return false;
   }


  ZipFile zipFile = null;
  InputStream  istr = null;

  if (file.length() == 0)
  { write_log("srtm3: " + file.getName() + " is empty.");
    //show_error("empty file");
   }
  else
  { write_log("srtm3: open: " + file.getName());
    write_log("srtm3: size: " + file.length());
    try {
      zipFile = new ZipFile(file.getPath());
      Enumeration e = zipFile.entries();
      ZipEntry zipEntry = (ZipEntry)e.nextElement();
    //write_log("read_tile: entry = " + zipEntry.getName());
      istr = zipFile.getInputStream(zipEntry);
    } catch (IOException ex) { write_log(ex.toString()); }

    if (istr == null)
    { write_log("srtm3: delete corrupted zip file.");
      file.delete();
      return false;
     }
  }

  int bad_count = 0;

  for(int i=0; i<tile_y1; i++) 
  { 
    int count = 0;
    byte buf[] = new byte[2*width];

    if (istr == null) // empty zip file : set all entries to zero
    { count = buf.length;
      for(int k=0; k<count; k++) buf[k] = 0;
     }
    else
      try {
       while (count < buf.length)
       { int n = istr.read(buf,count,buf.length-count);
         if (n < 0) break; 
         count += n;
        }
      } catch (IOException ex) { write_log(ex.toString()); }


    if (count != buf.length)
    { write_log("srtm3: read_tile: count = " + count);
      return false;
     }

    if (i < tile_y) continue;

    short last_good = 0;

    int yy = (ypos + i-tile_y)*width + xpos;

    for(int j=0; j<tile_w; j++)
    { int b1 = (int)buf[2*(tile_x+j)] & 0xff;
      int b2 = (int)buf[2*(tile_x+j)+1] & 0xff;
      short z = (short)((b1 << 8) + b2);
      //if (z == (short)0xffff) 
      if (z == -32768) 
       { z = last_good;
         bad_count++;
        }
      else
        last_good = z;

      table[yy+j] = z;
     }

   }

  write_log("srtm3: bad points = " + bad_count);

  try {
    if (istr != null) istr.close();
    if (zipFile != null) zipFile.close();
  } catch (IOException ex) { write_log(ex.toString()); }

  return true;
}




private double polar_dist(double lon1, double lat1, double lon2, double lat2)
{ final double earth_radius = 6371000;
  final double f = Math.PI/180;
  lon1 *= f;
  lat1 *= f;
  lon2 *= f;
  lat2 *= f;
  double d = Math.sin(lat1) * Math.sin(lat2) + 
             Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon1-lon2);
  if (d > +1) d = +1;
  if (d < -1) d = -1;
  return Math.acos(d) * earth_radius;
}



public double getAltitude(Location loc)
{ return getAltitude(loc.getLatitude(),loc.getLongitude()); }


public double getAltitude(double lat, double lon)
{ 
  if (table == null ||
      lon < lon_min || lon > lon_max || lat < lat_min || lat > lat_max)
  { write_log("srtm3: getAltitude --> init");
    init(lat,lon);
    return DISTANCE_UNDEFINED;
   }

  if (!data_available) 
  { write_log("srtm3: data not available");
    return DISTANCE_UNDEFINED;
   }


  double x = 1200*(lon-lon_min);
  double y = 1200*(lat_max-lat);

  int x0 = (int)x;
  int y0 = (int)y;
  int x1 = (int)x+1;
  int y1 = (int)y+1;

/*
  write_log(format("x = %.2f [%d  %d]  y = %.2f [%d  %d]",x,x0,x1,y,y0,y1));
*/

  // (x,y) lies inside rectangle (x0,y0,x1,y1)

  double Ax = x0;
  double Ay = y0;
  double Az = table[width*y0 + x0];

  double Bx = x1;
  double By = y0;
  double Bz = table[width*y0 + x1];

  double Cx = x1;
  double Cy = y1;
  double Cz = table[width*y1 + x1];

  double Dx = x0;
  double Dy = y1;
  double Dz = table[width*y1 + x0];

/*
   D -----+--C
   |      |  |
   +------x--+-y
   |      |  |
   A -----+--B
          x
*/

  if (Az == -32768 || Bz == -32768 || Cz == -32768  || Dz == -32768)
  { write_log("srtm3: result = undefined");
    return DISTANCE_UNDEFINED; 
   }

/*
  // interpolate in two triangles

  double z1 = 0;
  double z2 = 0;


  if (orientation(Ax,Ay,Cx,Cy,x,y) < 0)
    z1 = interpolate_plane(Ax,Ay,Az,Bx,By,Bz,Cx,Cy,Cz,x,y);
  else
    z1 = interpolate_plane(Ax,Ay,Az,Cx,Cy,Cz,Dx,Dy,Dz,x,y);

  if (orientation(Bx,By,Dx,Dy,x,y) < 0)
    z2 = interpolate_plane(Bx,By,Bz,Cx,Cy,Cz,Dx,Dy,Dz,x,y);
  else
    z2 = interpolate_plane(Bx,By,Bz,Dx,Dy,Dz,Ax,Ay,Az,x,y);

  double z_plane = (z1+z2)/2;
*/


// double interpolation

  double z1 = Az + (Bz-Az)*(x-x0)/(x1-x0); // interpolate between A and B
  double z2 = Dz + (Cz-Dz)*(x-x0)/(x1-x0); // interpolate between D and C
  double z  = z1 + (z2-z1)*(y-y0)/(y1-y0); // interpolate vertically

  z = 0.01 * Math.floor(100*z + 0.5); // round to cm

  return z;
}



public void clearBuffer()
{ alt_count = 0;
  alt_sum = 0;
  alt_avg = DISTANCE_UNDEFINED;
  for(int i=0; i<alt_values.length; i++) alt_values[i] = 0;
}

public double updateAltitude(Location loc) { 
  return updateAltitude(loc.getLatitude(),loc.getLongitude()); 
}

public double updateAltitude(double lat, double lon)
{ double z = getAltitude(lat,lon);

//write_log(String.format("srtm3 update: z = %.2f  count = %d",z,alt_count));

  if (z != DISTANCE_UNDEFINED) 
  { int i = alt_count % alt_values.length;
    alt_sum = alt_sum - alt_values[i] + z;
    alt_values[i] = z;
    alt_count++;
  
   if (alt_count < alt_values.length)
     alt_avg = alt_sum / alt_count;
   else
     alt_avg = alt_sum / alt_values.length;
  }

  if (alt_avg < -1000)
    write_log(String.format("SRTM3 UPDATE: alt_avg = %.2f",alt_avg));

  return alt_avg; 
}


public static double getDistance(Location loc)
{ return loc.distanceTo(closestPoint(loc)); }

public static Location closestPoint(Location loc)
{ double lon = loc.getLongitude();
  double lat = loc.getLatitude();

  Location srtm3_loc = new Location("gps");

  srtm3_loc.setLatitude(Math.floor(1200*lat + 0.5)/1200.0);
  srtm3_loc.setLongitude(Math.floor(1200*lon + 0.5)/1200.0);
  return srtm3_loc;
}



}


