/*

    Support for Motorrad Routenplaner (Map&Guide) .bcr files.

    Copyright (C) 2005-2007 Olaf Klein, o.b.klein@gpsbabel.org

    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 USA
*/

/*
    2006/01/22: reader simplified with inifile library
    2007/01/30: new option prefer_shortnames
    		don't check global_opts.objective
    2007/04&14: new handling of DESCRIPTION lines
*/

#include "defs.h"
#include "csv_util.h"
#include "garmin_tables.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "cet_util.h"
#include "inifile.h"

#define MYNAME "bcr"

#undef BCR_DEBUG

#define R_EARTH		6371000		/* radius of our big blue ball */
#define BCR_DEF_ICON		"Standort"
#define BCR_DEF_MPS_ICON	"Waypoint"
#define BCR_UNKNOWN		(double) 999999999

/*
    6371014 would be a better value when converting to f.e. to mapsoure,
    but this seems to be used by Map&Guide when exporting to XML.
*/

static gbfile* fout;
static char* filename;
static int curr_rte_num, target_rte_num;
static double radius;
static inifile_t* ini;

/* placeholders for options */

static char* rtenum_opt;
static char* rtename_opt;
static char* radius_opt;
static char* prefer_shortnames_opt;

static
arglist_t bcr_args[] = {
  {
    "index", &rtenum_opt, "Index of route to write (if more than one in source)",
    NULL, ARGTYPE_INT, "1", NULL
  },
  {
    "name", &rtename_opt, "New name for the route",
    NULL, ARGTYPE_STRING, ARG_NOMINMAX
  },
  {
    "radius", &radius_opt, "Radius of our big earth (default 6371000 meters)", "6371000",
    ARGTYPE_FLOAT, ARG_NOMINMAX
  },
  {
    "prefer_shortnames", &prefer_shortnames_opt, "Use shortname instead of description",
    NULL, ARGTYPE_BOOL, ARG_NOMINMAX
  },
  ARG_TERMINATOR
};

typedef struct {
  const char* bcr_name;
  const char* mps_name;
  const char* symbol_DE;
  int  warned;
} bcr_icon_mapping_t;

static
bcr_icon_mapping_t bcr_icon_mapping[] = {
  { BCR_DEF_ICON,		BCR_DEF_MPS_ICON, 	BCR_DEF_ICON },
  { "",			BCR_DEF_MPS_ICON, 	"Eigene Adressen" },
  { "AdrMon alpen",	"Summit",		"Pass-Strassen" },
  { "AdrMon bauern",	NULL,			"Bauern- und Biohoefe" },
  { "AdrMon cmpngs",	"Campground",		"Campingplaetzte" },
  { "AdrMon p_aeu",	"Scenic Area",		"Sehenswertes" },
  { "AdrMon p_beu",	"Gas Station",		"Tanken" },
  { "AdrMon p_deu",	"Parking Area",		"Parken" },
  { "AdrMon p_feu",	"Restaurant",		"Gastro" },
  { "AdrMon p_geu",	"Museum",		"Freizeit" },
  { "AdrMon p_heu",	"Gas Station",		"Tankstellen" },
  { "AdrMon p_keu",	NULL,			"Faehrverbindungen" },
  { "AdrMon p_leu",	NULL,			"Grenzuebergaenge" },
  { "AdrMon p_teu",	NULL,			"Wein- und Sektgueter" },
  { "AdrMon RUINEN",	"Ghost Town",		"Burgen und Schloesser" },
  { "AdrMon NFHAUS",	"Residence",		"Naturfreundehaeuser" },
  { "AdrMon racing",	"Bike Trail",		"Rennstrecken" },
  { "AdrMon TNKRST",	"Bar",			"Tankraststaetten" },
  { "AdrMon tpclub",	"Contact, Biker",	"Motorrad-Clubs" },
  { "AdrMon tpequ",	NULL,			"Motorrad-Equipment" },
  { "AdrMon tphot",	"Hotel",		"Motorrad-Hotels" },
  { "AdrMon tpmh",	NULL,			"Motorradhaendler" },
  { "AdrMon tpss",	"Restricted Area",	"Sperrungen" },
  { "AdrMon tpsw",	"Scenic Area",		"Sehenswertes" },
  { "AdrMon tptref",	NULL,			"Treffpunkte" },
  { "AdrMon VORTE",	"Information",		"Ortsinformationen" },
  { "AdrMon WEBCAM",	NULL,			"WebCam-Standorte" },
  { "AdrMon youthh",	NULL,			"Jugendherbergen" },
  { "Town",		"City (Small)",		"Orte" },
  { NULL,			NULL,			NULL, 0 }
};

static void
bcr_handle_icon_str(const char* str, waypoint* wpt)
{
  bcr_icon_mapping_t* m;

  wpt->icon_descr = BCR_DEF_MPS_ICON;

  for (m = bcr_icon_mapping; (m->bcr_name); m++) {
    if (case_ignore_strcmp(str, m->bcr_name) == 0) {
      int nr;

      if (m->symbol_DE == NULL) {
        if (! m->warned) {
          m->warned = 1;
          warning(MYNAME ": Unknown icon \"%s\" found. Please report.\n", str);
        }
        return;
      }
      wpt->description = xstrdup(m->symbol_DE);
      if (m->mps_name != NULL) {
        nr = gt_find_icon_number_from_desc(m->mps_name, MAPSOURCE);
        wpt->icon_descr = gt_find_desc_from_icon_number(nr, MAPSOURCE, NULL);
      }
      return;
    }
  }
}

static const char*
get_bcr_icon_from_icon_descr(const char* icon_descr)
{
  const char* result = BCR_DEF_ICON;

  if (icon_descr) {
    bcr_icon_mapping_t* m;

    for (m = bcr_icon_mapping; (m->bcr_name); m++) {
      if (! m->mps_name) {
        continue;
      }
      if (case_ignore_strcmp(icon_descr, m->mps_name) == 0) {
        result = m->bcr_name;
        break;
      }
    }
  }
  return result;
}

static void
bcr_init_radius(void)
{
  if (radius_opt != NULL) {			/* preinitialize the earth radius */
    radius = atof(radius_opt);
    if (radius <= 0) {
      fatal(MYNAME ": Sorry, the radius should be greater than zero!\n");
    }
  } else {
    radius = (double)R_EARTH;
  }

  if (global_opts.verbose_status > 0) {
    printf(MYNAME ": We calculate with radius %f meters.\n", radius);
  }
}

static void
bcr_rd_init(const char* fname)
{
  filename = xstrdup(fname);
  ini = inifile_init(fname, MYNAME);
  if (ini->unicode) {
    cet_convert_init(CET_CHARSET_UTF8, 1);
  }
  bcr_init_radius();
}

static void
bcr_rd_deinit(void)
{
  inifile_done(ini);
  xfree(filename);
}

/* ------------------------------------------------------------*/

static void
bcr_create_waypts_from_route(route_head* route)
{
  waypoint* wpt;
  queue* elem, *tmp;

  QUEUE_FOR_EACH(&route->waypoint_list, elem, tmp) {
    wpt = waypt_dupe((waypoint*) elem);
    waypt_add(wpt);
  }
}

static void
bcr_wgs84_to_mercator(const double lat, const double lon, int* north, int* east)
{
  double N, E;

  N = log(tan(lat * M_PI / 360 + M_PI / 4)) * radius;
  E = lon * radius * M_PI / (double)180;

  if (lat > 0) {
    N += 0.500000000001;  /* we go from double to integer */
  } else {
    N -= 0.500000000001;  /* it's time to round a little bit */
  }
  if (lon > 0) {
    E += 0.500000000001;
  } else {
    E -= 0.500000000001;
  }

  *north = N;
  *east = E;
}

void
bcr_mercator_to_wgs84(const int north, const int east, double* lat, double* lon)
{
  *lat = 2 * (atan(exp(north / radius)) - M_PI / 4) / M_PI * (double)180;
  *lon = (double)east * (double)180 / (radius * M_PI);
}

/* ------------------------------------------------------------- */

static void
bcr_data_read(void)
{
  int index;
  char* str;
  route_head* route;

  route = route_head_alloc();

  if ((str = inifile_readstr(ini, "client", "routename"))) {
    route->rte_name = xstrdup(str);
  }

  route_add_head(route);

  for (index = 1; index > 0; index ++) {

    char station[32];
    char* str;
    int mlat, mlon;		/* mercator data */
    waypoint* wpt;

    snprintf(station, sizeof(station), "STATION%d", index);
    if (NULL == (str = inifile_readstr(ini, "coordinates", station))) {
      break;
    }

    if (2 != sscanf(str, "%d,%d", &mlon, &mlat)) {
      fatal(MYNAME ": structure error at %s (Coordinates)!\n", station);
    }

    wpt = waypt_new();

    wpt->shortname = xstrdup(station);
    bcr_mercator_to_wgs84(mlat, mlon, &wpt->latitude, &wpt->longitude);

    if (NULL != (str = inifile_readstr(ini, "client", station))) {
      char* cx;

      cx = strchr(str, ',');
      if (cx == NULL) {
        fatal(MYNAME ": structure error at %s (Client)!\n", station);
      }
      *cx++ = '\0';
      bcr_handle_icon_str(str, wpt);
    }

    if (NULL != (str = inifile_readstr(ini, "description", station))) {
      char* c;

      c = strchr(str, ',');
      if (c != NULL) {
        *c = '\0';
      }
      if (*str) {
        wpt->notes = xstrdup(str);
      }
      if ((str = c)) {
        str++;
        c = strchr(str, ',');
        if (c != NULL) {
          *c = '\0';
        }
        if (*str) {
          xfree(wpt->shortname);
          wpt->shortname = xstrdup(str);
        }
      }
    }

    route_add_wpt(route, wpt);
  }

  /* remove empty route */
  if (route->rte_waypt_ct == 0) {
    route_del_head(route);
  } else {
    bcr_create_waypts_from_route(route);
  }
}

/* %%% bcr write support %%% ----------------------------------- */

static void
bcr_wr_init(const char* fname)
{
  filename = xstrdup(fname);
  fout = gbfopen(fname, "wb", MYNAME);
  bcr_init_radius();
}

static void
bcr_wr_deinit(void)
{
  gbfclose(fout);
  xfree(filename);
}

static void
bcr_route_trailer(const route_head* rte)
{
}

static void
bcr_write_wpt(const waypoint* wpt)
{
}

void bcr_write_line(gbfile* fout, const char* key, int* index, const char* value)
{
  if (value == NULL) {			/* this is mostly used in the world of windows */
    /* so we respectfully add a CR/LF on each line */
    gbfprintf(fout, "%s\r\n", key);
  } else {
    char* tmp;

    tmp = (value != NULL) ? xstrdup(value) : xstrdup("");
    if (index != NULL) {
      gbfprintf(fout, "%s%d=%s\r\n", key, *index, tmp);
    } else {
      gbfprintf(fout, "%s=%s\r\n", key, tmp);
    }
    xfree(tmp);
  }
}

static void
bcr_route_header(const route_head* route)
{
  queue* elem, *tmp;
  waypoint* wpt;
  char* sout;
  int i, north, east, nmin, nmax, emin, emax;

  curr_rte_num++;
  if (curr_rte_num != target_rte_num) {
    return;
  }

  bcr_write_line(fout, "[CLIENT]", NULL, NULL);			/* client section */
  bcr_write_line(fout, "REQUEST", NULL, "TRUE");

  sout = route->rte_name;
  if (rtename_opt != 0) {
    sout = rtename_opt;
  }
  if (sout != NULL) {
    bcr_write_line(fout, "ROUTENAME", NULL, sout);
  } else {
    bcr_write_line(fout, "ROUTENAME", NULL, "Route");
  }

  bcr_write_line(fout, "DESCRIPTIONLINES", NULL, "0");

  i = 0;
  QUEUE_FOR_EACH(&route->waypoint_list, elem, tmp) {
    const char* icon;
    waypoint* wpt = (waypoint*) elem;

    i++;

    icon = get_bcr_icon_from_icon_descr(wpt->icon_descr);

    xasprintf(&sout, "%s,%.f", icon, BCR_UNKNOWN);
    bcr_write_line(fout, "STATION", &i, sout);
    xfree(sout);
  }

  bcr_write_line(fout, "[COORDINATES]", NULL, NULL);		/* coords section */

  nmin = emin = (1<<30);
  emax = nmax = -nmin;

  i = 0;
  QUEUE_FOR_EACH(&route->waypoint_list, elem, tmp) {
    i++;
    wpt = (waypoint*) elem;

    bcr_wgs84_to_mercator(wpt->latitude, wpt->longitude, &north, &east);

    if (north > nmax) {
      nmax = north;
    }
    if (east > emax) {
      emax = east;
    }
    if (north < nmin) {
      nmin = north;
    }
    if (east < emin) {
      emin = east;
    }

    xasprintf(&sout, "%d,%d", east, north);
    bcr_write_line(fout, "STATION", &i, sout);
    xfree(sout);
  }

  bcr_write_line(fout, "[DESCRIPTION]", NULL, NULL);		/* descr. section */

  i = 0;
  QUEUE_FOR_EACH(&route->waypoint_list, elem, tmp) {
    char* s1, *s2, *sout;

    i++;
    wpt = (waypoint*) elem;
    s1 = wpt->notes;
    if (s1 == NULL) {
      s1 = wpt->description;
    }

    if (prefer_shortnames_opt || (s1 == NULL) || (*s1 == '\0')) {
      s2 = s1;
      s1 = wpt->shortname;
    } else {
      s2 = wpt->shortname;
    }

    if (s1 == NULL) {
      s1 = xstrdup("");
    } else {
      s1 = csv_stringclean(s1, ",\t\r\n");
    }
    if (s2 == NULL) {
      s2 = xstrdup("");
    } else {
      s2 = csv_stringclean(s2, ",\t\r\n");
    }

    if (*s2) {
      xasprintf(&sout, "%s,%s,@,0", s1, s2);
    } else {
      xasprintf(&sout, "%s,%s,@,0", s1, s1);
    }
    bcr_write_line(fout, "STATION", &i, sout);

    xfree(s1);
    xfree(s2);
    xfree(sout);
  }

  bcr_write_line(fout, "[ROUTE]", NULL, NULL);			/* route section */

  xasprintf(&sout, "%d,%d,%d,%d", emin, nmax, emax, nmin);
  bcr_write_line(fout, "ROUTERECT", NULL, sout);
  xfree(sout);

}

static void
bcr_data_write(void)
{
  target_rte_num = 1;

  if (rtenum_opt != NULL) {
    target_rte_num = atoi(rtenum_opt);
    if (((unsigned)target_rte_num > route_count()) || (target_rte_num < 1))
      fatal(MYNAME ": invalid route number %d (1..%d))!\n",
            target_rte_num, route_count());
  }
  curr_rte_num = 0;
  route_disp_all(bcr_route_header, bcr_route_trailer, bcr_write_wpt);
}

ff_vecs_t bcr_vecs = {
  ff_type_file,
  { ff_cap_none, ff_cap_none, (ff_cap)(ff_cap_read | ff_cap_write)},
  bcr_rd_init,
  bcr_wr_init,
  bcr_rd_deinit,
  bcr_wr_deinit,
  bcr_data_read,
  bcr_data_write,
  NULL,
  bcr_args,
  CET_CHARSET_MS_ANSI, 0	/* CET-REVIEW */
};
