/*
 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
 *
 * Copyright (C) 2003-2008, Evan Battaglia <gtoevan@gmx.net>
 * Copyright (C) 2007, Quy Tonthat <qtonthat@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
#include <glib.h>
#include <glib/gi18n.h>

#include "dems.h"
#include "background.h"

typedef struct {
  VikDEM *dem;
  guint ref_count;
} LoadedDEM;

GHashTable *loaded_dems = NULL;
/* filename -> DEM */

static void loaded_dem_free ( LoadedDEM *ldem )
{
  vik_dem_free ( ldem->dem );
  g_free ( ldem );
}

void a_dems_uninit ()
{
  if ( loaded_dems )
    g_hash_table_destroy ( loaded_dems );
}

/* To load a dem. if it was already loaded, will simply
 * reference the one already loaded and return it.
 */
VikDEM *a_dems_load(const gchar *filename)
{
  LoadedDEM *ldem;

  /* dems init hash table */
  if ( ! loaded_dems )
    loaded_dems = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, (GDestroyNotify) loaded_dem_free );

  ldem = (LoadedDEM *) g_hash_table_lookup ( loaded_dems, filename );
  if ( ldem ) {
    ldem->ref_count++;
    return ldem->dem;
  } else {
    VikDEM *dem = vik_dem_new_from_file ( filename );
    if ( ! dem )
      return NULL;
    ldem = g_malloc ( sizeof(LoadedDEM) );
    ldem->ref_count = 1;
    ldem->dem = dem;
    g_hash_table_insert ( loaded_dems, g_strdup(filename), ldem );
    return dem;
  }
}

void a_dems_unref(const gchar *filename)
{
  LoadedDEM *ldem = (LoadedDEM *) g_hash_table_lookup ( loaded_dems, filename );
  if ( !ldem ) {
    /* This is fine - probably means the loaded list was aborted / not completed for some reason */
    return;
  }
  ldem->ref_count--;
  if ( ldem->ref_count == 0 )
    g_hash_table_remove ( loaded_dems, filename );
}

/* to get a DEM that was already loaded.
 * assumes that its in there already,
 * although it could not be if earlier load failed.
 */
VikDEM *a_dems_get(const gchar *filename)
{
  LoadedDEM *ldem = g_hash_table_lookup ( loaded_dems, filename );
  if ( ldem )
    return ldem->dem;
  return NULL;
}


/* Load a string list (GList of strings) of dems. You have to use get to at them later.
 * When updating a list as a parameter, this should be before freeing the list so
 * the same DEMs won't be loaded & unloaded.
 * Modifies the list to remove DEMs which did not load.
 */

/* TODO: don't delete them when they don't exist.
 * we need to warn the user, but we should keep them in the GList.
 * we need to know that they weren't referenced though when we
 * do the a_dems_list_free().
 */
int a_dems_load_list ( GList **dems, gpointer threaddata )
{
  GList *iter = *dems;
  guint dem_count = 0;
  const guint dem_total = g_list_length ( *dems );
  while ( iter ) {
    if ( ! a_dems_load((const gchar *) (iter->data)) ) {
      g_warning ( _("Could not load DEM from file: %s"), (gchar*)(iter->data) );
      GList *iter_temp = iter->next;
      g_free ( iter->data );
      (*dems) = g_list_remove_link ( (*dems), iter );
      iter = iter_temp;
    } else {
      iter = iter->next;
    }
    /* When running a thread - inform of progress */
    if ( threaddata ) {
      dem_count++;
      /* NB Progress also detects abort request via the returned value */
      int result = a_background_thread_progress ( threaddata, ((gdouble)dem_count) / dem_total );
      if ( result != 0 )
	return -1; /* Abort thread */
    }
  }
  return 0;
}

/* Takes a string list (GList of strings) of dems (filenames).
 * Unrefs all the dems (i.e. "unloads" them), then frees the
 * strings, the frees the list.
 */
void a_dems_list_free ( GList *dems )
{
  GList *iter = dems;
  while ( iter ) {
    a_dems_unref ((const gchar *)iter->data);
    g_free ( iter->data );
    iter = iter->next;
  }
  g_list_free ( dems );
}

GList *a_dems_list_copy ( GList *dems )
{
  GList *rv = g_list_copy ( dems );
  GList *iter = rv;
  while ( iter ) {
    if ( ! a_dems_load((const gchar *) (iter->data)) ) {
      GList *iter_temp = iter->next; /* delete link, don't bother strdup'ing and free'ing string */
      rv = g_list_remove_link ( rv, iter );
      iter = iter_temp;
    } else {
      iter->data = g_strdup((gchar *)iter->data); /* copy the string too. */
      iter = iter->next;
    }
  }
  return rv;
}

gint16 a_dems_list_get_elev_by_coord ( GList *dems, const VikCoord *coord )
{
  static struct UTM utm_tmp;
  static struct LatLon ll_tmp;
  GList *iter = dems;
  VikDEM *dem;
  gint elev;

  while ( iter ) {
    dem = a_dems_get ( (gchar *) iter->data );
    if ( dem ) {
      if ( dem->horiz_units == VIK_DEM_HORIZ_LL_ARCSECONDS ) {
        vik_coord_to_latlon ( coord, &ll_tmp );
        ll_tmp.lat *= 3600;
        ll_tmp.lon *= 3600;
        elev = vik_dem_get_east_north(dem, ll_tmp.lon, ll_tmp.lat);
        if ( elev != VIK_DEM_INVALID_ELEVATION )
          return elev;
      } else if ( dem->horiz_units == VIK_DEM_HORIZ_UTM_METERS ) {
        vik_coord_to_utm ( coord, &utm_tmp );
        if ( utm_tmp.zone == dem->utm_zone &&
             (elev = vik_dem_get_east_north(dem, utm_tmp.easting, utm_tmp.northing)) != VIK_DEM_INVALID_ELEVATION )
            return elev;
      }
    }
    iter = iter->next;
  }
  return VIK_DEM_INVALID_ELEVATION;
}

typedef struct {
  const VikCoord *coord;
  VikDemInterpol method;
  gint elev;
} CoordElev;

static gboolean get_elev_by_coord(gpointer key, LoadedDEM *ldem, CoordElev *ce)
{
  VikDEM *dem = ldem->dem;
  gdouble lat, lon;

  if ( dem->horiz_units == VIK_DEM_HORIZ_LL_ARCSECONDS ) {
    struct LatLon ll_tmp;
    vik_coord_to_latlon (ce->coord, &ll_tmp );
    lat = ll_tmp.lat * 3600;
    lon = ll_tmp.lon * 3600;
  } else if (dem->horiz_units == VIK_DEM_HORIZ_UTM_METERS) {
    static struct UTM utm_tmp;
    if (utm_tmp.zone != dem->utm_zone)
      return FALSE;
    vik_coord_to_utm (ce->coord, &utm_tmp);
    lat = utm_tmp.northing;
    lon = utm_tmp.easting;
  } else
    return FALSE;

  switch (ce->method) {
    case VIK_DEM_INTERPOL_NONE:
      ce->elev = vik_dem_get_east_north(dem, lon, lat);
      break;
    case VIK_DEM_INTERPOL_SIMPLE:
      ce->elev = vik_dem_get_simple_interpol(dem, lon, lat);
      break;
    case VIK_DEM_INTERPOL_BEST:
      ce->elev = vik_dem_get_shepard_interpol(dem, lon, lat);
      break;
    default: break;
  }
  return (ce->elev != VIK_DEM_INVALID_ELEVATION);
}

/* TODO: keep a (sorted) linked list of DEMs and select the best resolution one */
gint16 a_dems_get_elev_by_coord ( const VikCoord *coord, VikDemInterpol method )
{
  CoordElev ce;

  if (!loaded_dems)
    return VIK_DEM_INVALID_ELEVATION;

  ce.coord = coord;
  ce.method = method;
  ce.elev = VIK_DEM_INVALID_ELEVATION;

  if(!g_hash_table_find(loaded_dems, (GHRFunc)get_elev_by_coord, &ce))
    return VIK_DEM_INVALID_ELEVATION;
  return ce.elev;
}

/**
 * a_dems_overlaps_bbox
 *
 * Potentially could return first DEM that overlaps the bbox
 *  but as yet that doesn't seem too useful
 */
gboolean a_dems_overlaps_bbox ( LatLonBBox bbox )
{
  if (!loaded_dems)
    return FALSE;

  gboolean ans = FALSE;
  LatLonBBox dem_bbox;

  gpointer key, value;
  GHashTableIter ght_iter;
  g_hash_table_iter_init ( &ght_iter, loaded_dems );
  while ( g_hash_table_iter_next (&ght_iter, &key, &value) ) {
    dem_bbox = vik_dem_get_bbox ( ((LoadedDEM*)value)->dem );
    if ( BBOX_INTERSECT(dem_bbox, bbox) ) {
      ans = TRUE;
      break;
    }
  }
  return ans;
}
