/*****************************************************************************
 *
 * This MobilityDB code is provided under The PostgreSQL License.
 * Copyright (c) 2016-2025, Université libre de Bruxelles and MobilityDB
 * contributors
 *
 * MobilityDB includes portions of PostGIS version 3 source code released
 * under the GNU General Public License (GPLv2 or later).
 * Copyright (c) 2001-2025, PostGIS contributors
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without a written
 * agreement is hereby granted, provided that the above copyright notice and
 * this paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL UNIVERSITE LIBRE DE BRUXELLES BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
 * EVEN IF UNIVERSITE LIBRE DE BRUXELLES HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * UNIVERSITE LIBRE DE BRUXELLES SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON
 * AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 *****************************************************************************/

/**
 * @file
 * @brief A simple program that reads from a CSV file synthetic trip data in
 * Brussels generated by the MobilityDB-BerlinMOD generator
 * https://github.com/MobilityDB/MobilityDB-BerlinMOD
 * and generate statics about the Brussels communes (or municipalities)
 * traversed by the trips.
 *
 * The input files are
 * - `communes.csv`: data from the 19 communes composing Brussels obtained from
 *   OSM and publicly available statistical data
 * - `brussels_region.csv`: geometry of the Brussels region obtained from OSM.
 *   It is the spatial union of the 19 communes
 * - `trips.csv`: 154 trips from 5 cars during 11 days obtained from the
 *   generator at scale factor 0.1. The input file has been generated with
 *   the following SQL command on the database containing the generated data
 * @code
 * COPY (SELECT tripid, vehid, day, seqno, asHexEWKB(trip) AS trip FROM trips WHERE vehid < 6 ORDER BY tripid) TO '/home/user/src/berlinmod_trips.csv' CSV HEADER;
 * @endcode
 * In the above files, the coordinates are given in the 3857 coordinate system,
 * https://epsg.io/3857
 * and the timestamps are given in the Europe/Brussels time zone.
 * This simple program does not cope with erroneous inputs, such as missing
 * fields or invalid values.
 *
 * The program can be build as follows
 * @code
 * gcc -Wall -g -I/usr/local/include -o 06_berlinmod_clip 06_berlinmod_clip.c -L/usr/local/lib -lmeos
 * @endcode
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <meos.h>
#include <meos_geo.h>

/**
 * Maximum length in characters of a trip in the input data. 
 * This value is set according to the following query executed in the database
 * created by the MobilityDB-BerlinMOD generator.
 * @code
 * SELECT MAX(length(asHexEWKB(trip))) FROM trips;
 * -- 328178
 * @endcode
 */
#define MAX_LEN_TRIP 400001
/* Maximum length in characters of the output matrix */
#define MAX_LEN_MATRIX 65536
/* Maximum length in characters of a geometry in the input data */
#define MAX_LEN_GEOM 100001
/* Maximum length in characters of a header record in the input CSV file */
#define MAX_LEN_HEADER 1024
/* Maximum length in characters of a name in the input data */
#define MAX_LEN_NAME 101
/* Maximum length in characters of a date in the input data */
#define MAX_LEN_DATE 12
/* Number of vehicles */
#define NO_VEHICLES 5
/* Number of communes */
#define NO_COMMUNES 19

typedef struct
{
  int id;
  char name[MAX_LEN_NAME];
  int population;
  GSERIALIZED *geom;
} commune_record;

typedef struct
{
  char name[MAX_LEN_NAME];
  GSERIALIZED *geom;
} region_record;

typedef struct
{
  int tripid;
  int vehid;
  int seq;
  Temporal *trip;
} trip_record;

/* Arrays to compute the results */
commune_record communes[NO_COMMUNES];
double distance[NO_VEHICLES + 1][NO_COMMUNES + 3] = {0};

char trip_buffer[MAX_LEN_TRIP];
char geo_buffer[MAX_LEN_GEOM];
char header_buffer[MAX_LEN_HEADER];
char date_buffer[MAX_LEN_DATE];

region_record brussels_region;

/* Read communes from file */
int read_communes(void)
{
  /* You may substitute the full file path in the first argument of fopen */
  FILE *file = fopen("data/brussels_communes.csv", "r");
  if (! file)
  {
    printf("Error opening input file 'brussels_communes.csv'\n");
    meos_finalize();
    return EXIT_FAILURE;
  }

  int no_records = 0;

  /* Read the first line of the file with the headers */
  fscanf(file, "%1023s\n", header_buffer);

  /* Continue reading the file */
  do
  {
    int read = fscanf(file, "%d,%100[^,],%d,%100000[^\n]\n",
      &communes[no_records].id, communes[no_records].name,
      &communes[no_records].population, geo_buffer);
    if (ferror(file))
    {
      printf("Error reading input file 'brussels_communes.csv'\n");
      meos_finalize();
      return EXIT_FAILURE;
    }
    if (read != 4)
    {
      printf("Commune record with missing values\n");
      meos_finalize();
      return EXIT_FAILURE;
    }

    /* Transform the string representing the geometry into a geometry value */
    communes[no_records++].geom = geom_in(geo_buffer, -1);
  } while (!feof(file));

  /* Close the file */
  fclose(file);

  printf("%d commune records read\n", no_records);
  return EXIT_SUCCESS;
}

/* Read Brussels region from file */
int read_brussels_region(void)
{
  /* You may substitute the full file path in the first argument of fopen */
  FILE *file = fopen("data/brussels_region.csv", "r");
  if (! file)
  {
    printf("Error opening input file 'brussels_region.csv'\n");
    meos_finalize();
    return EXIT_FAILURE;
  }

  /* Read the first line of the file with the headers */
  fscanf(file, "%1023s\n", header_buffer);

  /* Continue reading the file */
  int read = fscanf(file, "%100[^,],%100000[^\n]\n", brussels_region.name,
    geo_buffer);
  if (ferror(file) || read != 2)
  {
    if (ferror(file))
      printf("Error reading file\n");
    else
      printf("Region record with missing values\n");
    fclose(file);
    meos_finalize();
    return EXIT_FAILURE;
  }

  /* Transform the string representing the geometry into a geometry value */
  brussels_region.geom = geom_in(geo_buffer, -1);

  /* Close the file */
  fclose(file);

  printf("Brussels region record read\n");
  return EXIT_SUCCESS;
}

/**
 * Print a distance matrix in tabular form
 */
void
matrix_print(double distance[NO_VEHICLES + 1][NO_COMMUNES + 3],
  bool all_communes)
{
  size_t len = 0;
  char buf[MAX_LEN_MATRIX];
  int i, j;

  /* Print table header */
  len += snprintf(buf, MAX_LEN_MATRIX - 1, "\n                --");
  for (j = 1; j < NO_COMMUNES + 2; j++)
  {
    if (all_communes || distance[NO_VEHICLES][j] != 0)
      len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, "---------");
  }
  len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, 
    "\n                | Commmunes");
  len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, 
    "\n    --------------");
  for (j = 1; j < NO_COMMUNES + 2; j++)
  {
    if (all_communes || distance[NO_VEHICLES][j] != 0)
      len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, "---------");
  }
  len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, 
    "\nVeh | Distance | ");
  for (j = 1; j < NO_COMMUNES + 1; j++)
  {
    if (all_communes || distance[NO_VEHICLES][j] != 0)
      len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, "   %2d   ", j);
  }
  len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, 
    "|  Inside | Outside\n");
  for (j = 0; j < NO_COMMUNES + 3; j++)
  {
    if (all_communes || distance[NO_VEHICLES][j] != 0)
      len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, "---------");
  }
  len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, "\n");

  /* Print for each vehicle */
  for (i = 0; i < NO_VEHICLES; i++)
  {
    /* Print the vehicle number and the total distance for the vehicle */
    len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, 
      " %2d | %8.3f |", i + 1, distance[i][0]);
    /* Print the total distance per commune for the vehicle */
    for (j = 1; j <= NO_COMMUNES; j++)
    {
      if (all_communes || distance[NO_VEHICLES][j] != 0)
      {
        len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, 
          " %7.3f", distance[i][j]);
      }
    }
    /* Print the total distance outside and inside Brussels for the vehicle */
    for (j = NO_COMMUNES + 1; j < NO_COMMUNES + 3; j++)
      len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1,
        " | %7.3f", distance[i][j]);
    len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, "\n");
  }

  /* Print the total row */
  for (j = 0; j < NO_COMMUNES + 3; j++)
  {
    if (all_communes || distance[NO_VEHICLES][j] != 0)
      len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, "---------");
  }
  len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, 
    "\n    | %8.3f |", distance[NO_VEHICLES][0]);
  /* Print the total distance per commune */
  for (j = 1; j <= NO_COMMUNES; j++)
  {
    if (all_communes || distance[NO_VEHICLES][j] != 0)
      len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1,
        " %7.3f", distance[NO_VEHICLES][j]);
  }
  /* Print the total distance outside and inside Brussels */
  for (j = NO_COMMUNES + 1; j < NO_COMMUNES + 3; j++)
  {
    if (all_communes || distance[NO_VEHICLES][j] != 0)
      len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, 
        " | %7.3f", distance[NO_VEHICLES][j]);
  }
  len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, "\n");
  for (j = 0; j < NO_COMMUNES + 3; j++)
  {
    if (all_communes || distance[NO_VEHICLES][j] != 0)
      len += snprintf(buf + len, MAX_LEN_MATRIX - len - 1, "---------");
  }
  snprintf(buf + len, MAX_LEN_MATRIX - len - 1, "\n\n");
  printf("%s", buf);

  return;
}

/* Main program */
int main(void)
{
  /* Get start time */
  clock_t t;
  t = clock();
  
  /* Initialize MEOS */
  meos_initialize();

  /* Read communes file */
  read_communes();

  /* Read communes file */
  read_brussels_region();

  /* You may substitute the full file path in the first argument of fopen */
  FILE *file = fopen("data/berlinmod_trips.csv", "r");
  if (! file)
  {
    printf("Error opening input file 'berlinmod_trips.csv'\n");
    meos_finalize();
    return EXIT_FAILURE;
  }

  trip_record trip_rec;
  int no_records = 0, i;

  /* Read the first line of the file with the headers */
  fscanf(file, "%1023s\n", header_buffer);
  printf("Processing trip records (one '*' marker per trip)\n");

  /* Continue reading the file */
  do
  {
    int read = fscanf(file, "%d,%d,%10[^,],%d,%400000[^\n]\n",
      &trip_rec.tripid, &trip_rec.vehid, date_buffer, &trip_rec.seq,
      trip_buffer);
    if (ferror(file) || read != 5)
    {
      if (ferror(file))
        printf("Error reading line from input file 'berlinmod_trips.csv'\n");
      else
        printf("Trip record with missing values\n");
      fclose(file);
      meos_finalize();
      return EXIT_FAILURE;
    }

    no_records++;
    printf("*");
    fflush(stdout);

    /* Transform the string representing the trip into a temporal value */
    trip_rec.trip = temporal_from_hexwkb(trip_buffer);

    /* Compute the total distance */
    double d = tpoint_length(trip_rec.trip) / 1000;
    /* Add to the vehicle total and the column total */
    distance[trip_rec.vehid - 1][0] += d;
    distance[NO_VEHICLES][0] += d;
    /* Loop for each commune */
    for (i = 0; i < NO_COMMUNES; i ++)
    {
      Temporal *atgeom = tgeo_at_geom(trip_rec.trip, communes[i].geom);
      if (atgeom)
      {
        /* Compute the length of the trip projected to the commune */
        d = tpoint_length(atgeom) / 1000;
        /* Add to the cell */
        distance[trip_rec.vehid - 1][i + 1] += d;
        /* Add to the row total, the commune total, and inside total */
        distance[trip_rec.vehid - 1][NO_COMMUNES + 1] += d;
        distance[NO_VEHICLES][i + 1] += d;
        distance[NO_VEHICLES][NO_COMMUNES + 1] += d;
        free(atgeom);
      }
    }
    /* Compute the distance outside Brussels Region */
    Temporal *minusgeom = tpoint_minus_geom(trip_rec.trip,
      brussels_region.geom, NULL);
    if (minusgeom)
    {
      d = tpoint_length(minusgeom) / 1000;
      /* Add to the row */
      distance[trip_rec.vehid - 1][NO_COMMUNES + 2] += d;
      /* Add to the column total */
      distance[NO_VEHICLES][NO_COMMUNES + 2] += d;
      free(minusgeom);
    }

    /* Free memory */
    free(trip_rec.trip);

  } while (!feof(file));

  printf("\n%d trip records read.\n\n", no_records);
  /* The last argument states whether all communes, including those that have
     a zero value, are printed */
  matrix_print(distance, false);

  /* Free memory */
  for (i = 0; i < NO_COMMUNES; i++)
    free(communes[i].geom);
  free(brussels_region.geom);

  /* Close the file */
  fclose(file);

  /* Finalize MEOS */
  meos_finalize();

  /* Calculate the elapsed time */
  t = clock() - t;
  double time_taken = ((double) t) / CLOCKS_PER_SEC;
  printf("The program took %f seconds to execute\n", time_taken);

  return EXIT_SUCCESS;
}
