package com.algobase.service;

import java.util.ArrayList;
import java.io.File;

import android.location.Location;

import com.algobase.stracks.sTracksRoot;
import com.algobase.gpx.*;
import com.algobase.ascent.*;

import com.algobase.share.geo.*;

class Course {

  public void writeLog(String s) { }

  class CoursePoint {
    Location loc;
    double   dist;
    double   ascent;

    CoursePoint() {}

    CoursePoint(Location loc, double dst, double asc) {
       this.loc = loc;
       this.dist = dst;
       this.ascent = asc;
    }
  }

  CoursePoint[] crs_points;
  int current_crs_p = -1;

  String name = null;

  public int size() { return crs_points.length; }

  public String   getName() { return name; }

  public Location getLocation(int i) { 
    if (i < 0 || i >= crs_points.length) return null;
    return crs_points[i].loc; 
  }

  public double getDistance(int i) { return crs_points[i].dist; }
  public double getAscent(int i) { return crs_points[i].ascent; }

  public double getTotalDistance() { 
       int i = crs_points.length-1;
       return crs_points[i].dist; 
  }

  public double getTotalAscent() { 
       int i = crs_points.length-1;
       return crs_points[i].ascent; 
  }

  public void clear() { crs_points = null; }



  void recompute_ascent()
  {
    float ascent_eps = 1.0f;
    float point_mindist = 5.0f;

    //double eps = TotalAscent.BARO_EPS*ascent_eps;
    double eps = TotalAscent.ASCENT_EPS[2]*ascent_eps;

    double total_ascent = 0;
    double total_descent = 0;

    CoursePoint last_p = crs_points[0];
    last_p.ascent = 0;

    for(int i=1; i<crs_points.length; i++)
    {
      CoursePoint p = crs_points[i];
     
      double diff_dist = p.dist - last_p.dist;
      double diff_alt  = p.loc.getAltitude() - last_p.loc.getAltitude();

      double dmin = p.loc.getAccuracy();
      if (dmin < point_mindist) dmin = point_mindist;

      if (diff_dist > dmin) 
      { double slope = diff_alt/diff_dist;
        if (Math.abs(diff_alt) >= eps)
        { if (Math.abs(slope) < 0.5)  
          { if (diff_alt > 0) total_ascent += diff_alt;
            if (diff_alt < 0) total_descent -= diff_alt;
           }
          last_p = p;
        }
      }

    //writeLog(String.format("%d: total_ascent = %.2f",i,total_ascent));

      p.ascent = total_ascent;
    }
  }


  public void reverse()
  { double total_dist = getTotalDistance();
    int n = crs_points.length;
    CoursePoint[] A = crs_points;
    crs_points =  new CoursePoint[n]; 
    for(int i=0; i<n; i++)
    { CoursePoint p = A[n-i-1];
      double d = total_dist - p.dist;
      crs_points[i] = new CoursePoint(p.loc,d,0);
    }
    recompute_ascent();
  }

  

  public void load(File file)
  {
    final ArrayList<CoursePoint> A = new ArrayList<CoursePoint>(); 

    TrkReader trk = new TrkReader(file) {

        @Override
        public boolean handle_track_begin(int i, String s) {
           name = s;
           return true;
        }


        @Override
        public void handle_break(int i, long t) { 
        }


        @Override
        public boolean handle_trackpoint(int i, Location loc, double dist,
                                                              double spd,
                                                              double asc,
                                                              int hrt,
                                                              int pwr,
                                                              int cad,
                                                              int tmp)
        { 
          if (A.size() == 0) {
            A.add(new CoursePoint(loc,dist,asc));
            return true;
          }

          CoursePoint last = A.get(A.size()-1);
          //int steps = (int)(1 + (dist-last.dist)/50);
          int steps = (int)(1 + (dist-last.dist)/30);
          double d_lon = (loc.getLongitude() - last.loc.getLongitude())/steps;
          double d_lat = (loc.getLatitude() - last.loc.getLatitude())/steps;
          double d_dist = (dist - last.dist)/steps;
          double d_asc = (asc - last.ascent)/steps;

          for(int j=1; j<steps; j++) 
          { Location lc = new Location("gps");
            lc.setLongitude(last.loc.getLongitude() + j*d_lon);
            lc.setLatitude(last.loc.getLatitude() + j*d_lat);
            lc.setAltitude(loc.getAltitude());
            A.add(new CoursePoint(lc,dist+j*d_dist,asc+j*d_asc));
          }

          A.add(new CoursePoint(loc,dist,asc));

          return true;
        }
    };

    trk.read();

    crs_points = new CoursePoint[A.size()];
    crs_points = A.toArray(crs_points); 

  }


  public int find_closest_point(Location loc)
  {
    int i_min = -1;
    double d_min = Double.MAX_VALUE;

    for(int i=0; i<crs_points.length; i++)
    { double d = crs_points[i].loc.distanceTo(loc);
      if (d < d_min) {
        d_min = d;
        i_min = i;
      }
    }

    return i_min;
  }


  public int find_next_point(Location loc, int pos, int steps, double max_dst)
  {
    if (pos < 0) pos = 0;

    int p_next = -1;

    // search for loc with dist < max_dst for some number of points forward

    for(int i=0; i<5; i++) {
      int p = pos + i;
      if (p < crs_points.length && loc.distanceTo(crs_points[p].loc) < max_dst)
      { p_next = p;
        break;
      }
    }

    if (p_next != -1) return p_next;

    // search backward

    for(int i=1; i<5; i++) {
      int p = pos - i;
      if (p >= 0 && loc.distanceTo(crs_points[p].loc) < max_dst)
      { p_next = p;
        break;
      }
    }

    if (p_next != -1) return p_next;



    // find closest course point in range [pos-steps ... pos+steps]
    // (search all points if steps < 0)

    double d_min = Double.MAX_VALUE;

    int start = 0;
    int end = crs_points.length;

    if (steps > 0) 
    { start = pos - steps;
      if (start < 0) start = 0;
      end = pos + steps;
      if (end > crs_points.length) end = crs_points.length;
     }

    for(int i=start; i<end; i++)
    { double d = crs_points[i].loc.distanceTo(loc);

      if (d < max_dst) {
        p_next = i;
        break;
      }

      if (d < d_min) {
        d_min = d;
        p_next = i;
      }
    }

    return p_next;
  }



  public int find_next_point_by_dist(double dist, int pos)
  { if (pos < 0) pos = 0;
    while (pos < crs_points.length && dist >= getDistance(pos)) pos++;
    return pos-1;
  }
     
     

  Location find_closest_loc(Location loc, int p)
  {
    Location loc_p = crs_points[p].loc;

    double d_min = loc.distanceTo(loc_p);

    Location loc_min = new Location("gps");
    loc_min.setLongitude(loc_p.getLongitude());
    loc_min.setLatitude(loc_p.getLatitude());

    // for i in [-1,+1]
    for(int i = -1; i <= +1; i+=2)
    {
      int q = p + i;
      if (q < 0 || q >= crs_points.length-1) continue;

      Location loc_q = getLocation(q);
      double dist = loc_p.distanceTo(loc_q);
      int steps = (int)(dist/5);
      double lon_d = (loc_q.getLongitude() - loc_p.getLongitude())/(steps+1);
      double lat_d = (loc_q.getLatitude() - loc_p.getLatitude())/(steps+1);
      double lon = loc_p.getLongitude() + lon_d;
      double lat = loc_p.getLatitude() + lat_d;

      while (--steps > 0)
      { lon += lon_d;
        lat += lat_d;
        double d = Geometry.polarDistance(loc.getLongitude(),
                                          loc.getLatitude(), 
                                          lon,lat);
        if (d < d_min) 
        { d_min = d;
          loc_min.setLongitude(lon);
          loc_min.setLatitude(lat);
         }
        else break;
      }
    }

    return loc_min;

  }


  Location closest_segment_position(Location loc, int p)
  { //int p = current_crs_p;

    int q = p+1;
    Location loc_p = crs_points[p].loc;

    if (q >= crs_points.length) return loc_p;

    Location loc_q = crs_points[q].loc;

    double dist = loc_p.distanceTo(loc_q);
    int steps = (int)(1+dist/4);
    double lon_d = (loc_q.getLongitude() - loc_p.getLongitude())/steps;
    double lat_d = (loc_q.getLatitude() - loc_p.getLatitude())/steps;

    double d_min = Double.MAX_VALUE;

    Location loc_min = new Location("gps");

    for(int i = 0; i<steps; i++)
    { double lon = loc_p.getLongitude() + i*lon_d;
      double lat = loc_p.getLatitude() + i*lat_d;
      double d = Geometry.polarDistance(loc.getLongitude(),
                                        loc.getLatitude(),lon,lat);
      if (d < d_min) 
      { d_min = d;
        loc_min.setLongitude(lon);
        loc_min.setLatitude(lat);
        if (i == steps-1) current_crs_p = q;
       }
    }

    return loc_min;
  }

}

