/*
===============================================================================

  FILE:  lasindex.cpp
  
  CONTENTS:
  
    This tool creates a *.lax file for a given *.las or *.laz file that contains
    a spatial indexing. When this LAX file is present it will be used to speed up
    access to the relevant areas of the LAS/LAZ file whenever a spatial queries
    of the type
    
      -inside_tile ll_x ll_y size
      -inside_circle center_x center_y radius
      -inside_rectangle min_x min_y max_x max_y  (or simply -inside)

     appears in the command line of any LAStools invocation. This acceleration is
     also available to users of the LASlib API. The LASreader class has three new
     functions called

     BOOL inside_tile(const F32 ll_x, const F32 ll_y, const F32 size);
     BOOL inside_circle(const F64 center_x, const F64 center_y, const F64 radius);
     BOOL inside_rectangle(const F64 min_x, const F64 min_y, const F64 max_x, const F64 max_y);

     if any of these functions is called the LASreader will only return the points
     that fall inside the specified region and use - when available - the spatial
     indexing information in the LAX file created by lasindex.

  PROGRAMMERS:
  
    martin.isenburg@rapidlasso.com  -  http://rapidlasso.com
  
  COPYRIGHT:
  
    (c) 2011-14, martin isenburg, rapidlasso - fast tools to catch reality

    This is free software; you can redistribute and/or modify it under the
    terms of the GNU Lesser General Licence as published by the Free Software
    Foundation. See the LICENSE.txt file for more information.

    This software is distributed WITHOUT ANY WARRANTY and without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  
  CHANGE HISTORY:
  
    17 May 2011 -- enabling batch processing with wildcards or multiple file names
    29 April 2011 -- created after cable outage during the royal wedding (-:
  
===============================================================================
*/

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

#include "lasreader.hpp"
#include "lasindex.hpp"
#include "lasquadtree.hpp"

void usage(bool error=false, bool wait=false)
{
  fprintf(stderr,"usage:\n");
  fprintf(stderr,"lasindex lidar.las\n");
  fprintf(stderr,"lasindex *.las\n");
  fprintf(stderr,"lasindex flight1*.las flight2*.las -verbose\n");
  fprintf(stderr,"lasindex lidar.las -tile_size 2 -maximum -50\n");
  fprintf(stderr,"lasindex -h\n");
  if (wait)
  {
    fprintf(stderr,"<press ENTER>\n");
    getc(stdin);
  }
  exit(error);
}

static void byebye(bool error=false, bool wait=false)
{
  if (wait)
  {
    fprintf(stderr,"<press ENTER>\n");
    getc(stdin);
  }
  exit(error);
}

static F64 taketime()
{
  return (F64)(clock())/CLOCKS_PER_SEC;
}

#ifdef COMPILE_WITH_GUI
extern int lasindex_gui(int argc, char *argv[], LASreadOpener* lasreadopener);
#endif

#ifdef COMPILE_WITH_MULTI_CORE
extern int lasindex_multi_core(int argc, char *argv[], LASreadOpener* lasreadopener, int cores);
#endif

int main(int argc, char *argv[])
{
  int i;
#ifdef COMPILE_WITH_GUI
  bool gui = false;
#endif
#ifdef COMPILE_WITH_MULTI_CORE
  I32 cores = 1;
#endif
  bool verbose = false;
  F32 tile_size = 0.0f;
  U32 threshold = 1000;
  U32 minimum_points = 100000;
  I32 maximum_intervals = -20;
  BOOL append = FALSE;
  F64 start_time = 0.0;
  F64 total_start_time = 0.0;

  LASreadOpener lasreadopener;

  if (argc == 1)
  {
#ifdef COMPILE_WITH_GUI
    return lasindex_gui(argc, argv, 0);
#else
    fprintf(stderr,"%s is better run in the command line\n", argv[0]);
    char file_name[256];
    fprintf(stderr,"enter input file: "); fgets(file_name, 256, stdin);
    file_name[strlen(file_name)-1] = '\0';
    lasreadopener.set_file_name(file_name);
#endif
  }
  else
  {
    for (i = 1; i < argc; i++)
    {
      if (argv[i][0] == '–') argv[i][0] = '-';
    }
    if (!lasreadopener.parse(argc, argv)) byebye(true);
  }

  for (i = 1; i < argc; i++)
  {
    if (argv[i][0] == '\0')
    {
      continue;
    }
    else if (strcmp(argv[i],"-h") == 0 || strcmp(argv[i],"-help") == 0)
    {
      fprintf(stderr, "LAStools (by martin@rapidlasso.com) version %d\n", LAS_TOOLS_VERSION);
      usage();
    }
    else if (strcmp(argv[i],"-v") == 0 || strcmp(argv[i],"-verbose") == 0)
    {
      verbose = true;
    }
    else if (strcmp(argv[i],"-version") == 0)
    {
      fprintf(stderr, "LAStools (by martin@rapidlasso.com) version %d\n", LAS_TOOLS_VERSION);
      byebye();
    }
    else if (strcmp(argv[i],"-gui") == 0)
    {
#ifdef COMPILE_WITH_GUI
      gui = true;
#else
      fprintf(stderr, "WARNING: not compiled with GUI support. ignoring '-gui' ...\n");
#endif
    }
    else if (strcmp(argv[i],"-cores") == 0)
    {
#ifdef COMPILE_WITH_MULTI_CORE
      if ((i+1) >= argc)
      {
        fprintf(stderr,"ERROR: '%s' needs 1 argument: number\n", argv[i]);
        usage(true);
      }
      argv[i][0] = '\0';
      i++;
      cores = atoi(argv[i]);
      argv[i][0] = '\0';
#else
      fprintf(stderr, "WARNING: not compiled with multi-core batching. ignoring '-cores' ...\n");
      i++;
#endif
    }
    else if (strcmp(argv[i],"-tile_size") == 0)
    {
      if ((i+1) >= argc)
      {
        fprintf(stderr,"ERROR: '%s' needs 1 argument: size\n", argv[i]);
        byebye(true);
      }
      i++;
      tile_size = (F32)atof(argv[i]);
    }
    else if (strcmp(argv[i],"-maximum") == 0)
    {
      if ((i+1) >= argc)
      {
        fprintf(stderr,"ERROR: '%s' needs 1 argument: number\n", argv[i]);
        byebye(true);
      }
      i++;
      maximum_intervals = atoi(argv[i]);
    }
    else if (strcmp(argv[i],"-minimum") == 0)
    {
      if ((i+1) >= argc)
      {
        fprintf(stderr,"ERROR: '%s' needs 1 argument: number\n", argv[i]);
        byebye(true);
      }
      i++;
      minimum_points = atoi(argv[i]);
    }
    else if (strcmp(argv[i],"-threshold") == 0)
    {
      if ((i+1) >= argc)
      {
        fprintf(stderr,"ERROR: '%s' needs 1 argument: value\n", argv[i]);
        byebye(true);
      }
      i++;
      threshold = atoi(argv[i]);
    }
    else if (strcmp(argv[i],"-append") == 0)
    {
      append = TRUE;
    }
    else
    {
      fprintf(stderr, "ERROR: cannot understand argument '%s'\n", argv[i]);
      byebye(true);
    }
  }

#ifdef COMPILE_WITH_GUI
  if (gui)
  {
    return lasindex_gui(argc, argv, &lasreadopener);
  }
#endif

#ifdef COMPILE_WITH_MULTI_CORE
  if (cores > 1)
  {
    if (lasreadopener.get_file_name_number() < 2)
    {
      fprintf(stderr,"WARNING: only %u input files. ignoring '-cores %d' ...\n", lasreadopener.get_file_name_number(), cores);
    }
    else if (lasreadopener.is_merged())
    {
      fprintf(stderr,"WARNING: input files merged on-the-fly. ignoring '-cores %d' ...\n", cores);
    }
    else
    {
      return lasindex_multi_core(argc, argv, &lasreadopener, cores);
    }
  }
#endif

  // check input

  if (!lasreadopener.active())
  {
    fprintf(stderr,"ERROR: no input specified\n");
    byebye(true, argc==1);
  }

/*
  // lasquadtree test

  LASquadtree quadtree;
  quadtree.setup(0, 99, 0, 99, 10);
  quadtree.intersect_rectangle(10, 10, 20, 20);
  quadtree.get_intersected_cells();
  while (quadtree.has_intersected_cells())
  {
    F32 min[2],max[2];
    quadtree.get_cell_bounding_box(quadtree.intersected_cell, min, max);
    fprintf(stderr," checking tile %d with %g/%g %g/%g\n", quadtree.intersected_cell, min[0], min[1], max[0], max[1]);
  }
  quadtree.intersect_tile(10, 10, 10);
  quadtree.get_intersected_cells();
  while (quadtree.has_intersected_cells())
  {
    F32 min[2],max[2];
    quadtree.get_cell_bounding_box(quadtree.intersected_cell, min, max);
    fprintf(stderr," checking tile %d with %g/%g %g/%g\n", quadtree.intersected_cell, min[0], min[1], max[0], max[1]);
  }
  fprintf(stderr,"intersect circle\n");
  quadtree.intersect_circle(10, 10, 10);
  quadtree.get_intersected_cells();
  while (quadtree.has_intersected_cells())
  {
    F32 min[2],max[2];
    quadtree.get_cell_bounding_box(quadtree.intersected_cell, min, max);
    fprintf(stderr," checking tile %d with %g/%g %g/%g\n", quadtree.intersected_cell, min[0], min[1], max[0], max[1]);
  }
*/

  // possibly loop over multiple input files

  if (verbose && lasreadopener.get_file_name_number() > 1)
  {
    total_start_time = taketime();
  }

  while (lasreadopener.active())
  {
    if (verbose) start_time = taketime();

    // open lasreader

    LASreader* lasreader = lasreadopener.open();
    if (lasreader == 0)
    {
      fprintf(stderr, "ERROR: could not open lasreader\n");
      byebye(true, argc==1);
    }

    // setup the quadtree

    LASquadtree* lasquadtree = new LASquadtree;
    if (tile_size == 0.0f)
    {
      F32 t;
      if (((lasreader->header.max_x - lasreader->header.min_x) < 1000) && ((lasreader->header.max_y - lasreader->header.min_y) < 1000))
      {
        t = 10.0f;
      }
      else if (((lasreader->header.max_x - lasreader->header.min_x) < 10000) && ((lasreader->header.max_y - lasreader->header.min_y) < 10000))
      {
        t = 100.0f;
      }
      else if (((lasreader->header.max_x - lasreader->header.min_x) < 100000) && ((lasreader->header.max_y - lasreader->header.min_y) < 100000))
      {
        t = 1000.0f;
      }
      else if (((lasreader->header.max_x - lasreader->header.min_x) < 1000000) && ((lasreader->header.max_y - lasreader->header.min_y) < 1000000))
      {
        t = 10000.0f;
      }
      else
      {
        t = 100000.0f;
      }
      if (verbose) fprintf(stderr,"no tile size specified. setting it to %g ...\n", t);
      lasquadtree->setup(lasreader->header.min_x, lasreader->header.max_x, lasreader->header.min_y, lasreader->header.max_y, t);
    }
    else
    {
      lasquadtree->setup(lasreader->header.min_x, lasreader->header.max_x, lasreader->header.min_y, lasreader->header.max_y, tile_size);
    }

    // create index and add points

    LASindex lasindex;
    lasindex.prepare(lasquadtree, threshold);
    while (lasreader->read_point()) lasindex.add(lasreader->point.get_x(), lasreader->point.get_y(), (U32)(lasreader->p_count-1));
  
    // delete the reader
    
    lasreader->close();
    delete lasreader;

    // adaptive coarsening

    lasindex.complete(minimum_points, maximum_intervals);

    // write to file

    if (append)
    {
      lasindex.append(lasreadopener.get_file_name());
    }
    else
    {
      lasindex.write(lasreadopener.get_file_name());
    }

    if (verbose) fprintf(stderr,"done with '%s'. took %g sec.\n", lasreadopener.get_file_name(), taketime()-start_time);
  }
  
  if (verbose && lasreadopener.get_file_name_number() > 1)
  {
    fprintf(stderr,"done with %u files. total time %g sec.\n", lasreadopener.get_file_name_number(), taketime()-total_start_time);
  }

  byebye(false, argc==1);

  return 0;
}
