/* main.c: main program */

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

#include "config.h"

#include "ui.h"
#include "defaults.h"
#include "options.h"
#include "statistics.h"
#include "camera.h"
#include "render.h"
#include "error.h"
#include "scene.h"
#include "geom.h"
#include "patch_type.h"
#include "patch_flags.h"
#include "material.h"
#include "edf.h"
#include "surface.h"
#include "readmgf.h"
#ifndef NO_VRML
#include "readvrml.H"
#endif
#include "pools.h"
#include "patch.h"
#include "cubature.h"
#include "cluster.h"
#include "radiance.h"
#include "raytracing.h"
#include "monitor.h"
#include "vertex.h"
#include "brep.h"
#include "compound.h"
#include "grid.h"
#include "ipc.h"
#include "renderhook.h"
#include "tonemapping.h"
#include "fileopts.h"
#include "ui_pathdebug.h"
#include "background_edf.h"


/* statistics stuff */
int    nrpatches, nrlightsources;
double total_area;
double average_direct_potential, total_direct_potential;
double max_direct_potential, max_direct_importance; 
COLOR  total_emitted_power, estimated_average_radiance;
COLOR  average_reflectivity, max_selfemitted_radiance, max_selfemitted_power;
double reference_luminance;

/* program name, as determines from zero-th command line argument */
char *progname;

/* default alarm signal handler: if there is not one set explicitely in the
 * program, the program would be killed if an ALARM signal is raised. Alarm
 * signals are used for waking up now and then during the computations. */
static void default_alarm_handler(int sig)
{
  /* does nothing, except catching the ALARM signal. If we wouldn't catch it,
   * the program would be terminated instantly. */
  time_t t = time(NULL);
fprintf(stderr, "%s%s line %d: You're lucky that *I* catched that alarm signal ...\n",
	asctime(localtime(&t)),
	__FILE__, __LINE__);
}

static void PatchAccumulateStats(PATCH *patch)
{
  COLOR 
    E = PatchAverageEmittance(patch, ALL_COMPONENTS),
    R = PatchAverageNormalAlbedo(patch, BSDF_ALL_COMPONENTS),
    power;

  total_area += patch->area;
  COLORSCALE(patch->area, E, power);
  COLORADD(total_emitted_power, power, total_emitted_power);
  COLORADDSCALED(average_reflectivity, patch->area, R, average_reflectivity);
  /* convert radiant exitance to exitant radiance */
  COLORSCALE((1./M_PI), E, E);
  COLORMAX(E, max_selfemitted_radiance, max_selfemitted_radiance);
  COLORMAX(power, max_selfemitted_power, max_selfemitted_power);
}

static void ComputeSomeSceneStats(void)
{
  VECTOR zero;
  COLOR one, average_absorption, BP;

  COLORSETMONOCHROME(one, 1.);
  VECTORSET(zero, 0, 0, 0);

  /* initialize */
  COLORCLEAR(total_emitted_power);
  COLORCLEAR(average_reflectivity);
  COLORCLEAR(max_selfemitted_radiance);
  COLORCLEAR(max_selfemitted_power);
  total_area = 0.;

  /* accumulate */
  PatchListIterate(Patches, PatchAccumulateStats);

  /* averages ... */
  COLORSCALEINVERSE(total_area, average_reflectivity, average_reflectivity);
  COLORSUBTRACT(one, average_reflectivity, average_absorption);
  COLORSCALEINVERSE(M_PI * total_area, total_emitted_power, estimated_average_radiance);

  /* include background radiation */
  BP = BackgroundPower(Background, &zero);
  COLORSCALE(1. / (4 * M_PI), BP, BP);
  COLORADD(total_emitted_power, BP, total_emitted_power);
  COLORADD(estimated_average_radiance, BP, estimated_average_radiance);

  COLORDIV(estimated_average_radiance, average_absorption, estimated_average_radiance);

  total_direct_potential = max_direct_potential = average_direct_potential = max_direct_importance = 0.;
}

/* adds the background to the global light source patch list */
void AddBackgroundToLightSourceList(void)
{
  if (Background)
    {
      // add to list
      LightSourcePatches = PatchListAdd(LightSourcePatches, Background->bkgPatch);
    }
}


/* adds the patch to the global light source patch list if the patch is on 
 * a light source (i.e. when the surfaces material has a non-null edf). */
static void AddPatchToLightSourceListIfLightSource(PATCH *patch)
{
  if (patch->surface->material->edf) {
    LightSourcePatches = PatchListAdd(LightSourcePatches, patch);
    nrlightsources++;
  }
}

/* Build the global light source patch list. */
static void BuildLightSourcePatchList(void)
{
  LightSourcePatches = PatchListCreate();
  nrlightsources = 0;
  PatchListIterate(Patches, AddPatchToLightSourceListIfLightSource);

  // add background if present
  AddBackgroundToLightSourceList();
  nrlightsources++;
}

/* Tries to read the scene in the given file. Returns False if not succesful.
 * Returns True if succesful. There's nothing GUI specific in this function. 
 * When a file cannot be read, the current scene is restored. */
Boolean ReadFile(char *filename)
{
  char *dot, *slash, *extension;
  FILE *input;
  GEOMLIST *oWorld, *oClusteredWorld;
  GEOM *oClusteredWorldGeom;
  MATERIALLIST *oMaterialLib;
  PATCHLIST *oPatches, *oLightSourcePatches;
  GRID *oWorldGrid;
  RADIANCEMETHOD *oRadiance;
  RAYTRACINGMETHOD *oRayTracing;
  BACKGROUND *oBackground;
  int opatchid, onrpatches;
  clock_t t, last;

  /* check whether the file can be opened if not reading from stdin */
  if (filename[0] != '#') {
    if ((input = fopen(filename, "r")) == (FILE *)NULL ||
	fgetc(input) == EOF) {
      if (input) fclose(input);
      Error(NULL, "Can't open file '%s' for reading", filename);
      return False;
    }
    fclose(input);
  }

  /* get current directory from the filename */
  current_directory = (char *)Alloc(strlen(filename)+1);
  sprintf(current_directory, "%s", filename);
  if ((slash = strrchr(current_directory, '/')) != NULL)
    *slash = '\0';
  else
    *current_directory = '\0';

  ErrorReset();

  /* terminate any active radiance or raytracing methods */
  fprintf(stderr, "Terminating current radiance/raytracing method ... \n");
  oRadiance = Radiance;
  SetRadianceMethod((RADIANCEMETHOD *)NULL);
  oRayTracing = RayTracing;
  SetRayTracing((RAYTRACINGMETHOD *)NULL);

  /* save the current scene so it can be restored if errors occur when 
   * reading the new scene */
  fprintf(stderr, "Saving current scene ... \n");
  oWorld = World; World = GeomListCreate();
  oMaterialLib = MaterialLib; MaterialLib = MaterialListCreate();
  oPatches = Patches; Patches = PatchListCreate();
  opatchid = PatchGetNextID(); PatchSetNextID(1);
  onrpatches = nrpatches;
  oClusteredWorld = ClusteredWorld; ClusteredWorld = GeomListCreate();
  oClusteredWorldGeom = ClusteredWorldGeom;
  oLightSourcePatches = LightSourcePatches;
  oWorldGrid = WorldGrid;
  oBackground = Background; Background = (BACKGROUND *)NULL;

  /* read the MGF file. The result is a new World and MaterialLib if 
   * everything goes well. */
  fprintf(stderr, "Reading the scene from file '%s' ... \n", filename);
  last = clock();

  if ((dot = strrchr(filename, '.')) != NULL)
    extension = dot+1;
  else
    extension = "mgf";

  if (strcmp(extension, "gz")  == 0 ||
      strcmp(extension, "Z")   == 0 ||
      strcmp(extension, "bz")  == 0 ||
      strcmp(extension, "bz2") == 0) 
  {
    /* compressed file, find real extension */
    do {
      dot--;
    } while (dot>filename && *dot!='.');
    if (*dot == '.')
      extension = dot+1;
  }

  if (strncmp(extension, "mgf", 3) == 0)	ReadMgf(filename);
#ifndef NO_VRML
  else if (strncmp(extension, "wrl", 3) == 0 ||
	   strncmp(extension, "vrml", 4) == 0)	ReadVrml(filename);
#endif
  else {
#ifndef NO_VRML
    Error(NULL, "Unsupported scene format (no MGF or VRML)");
#else
    Error(NULL, "Unsupported scene format (no MGF)");
#endif
  }

  t = clock();
  fprintf(stderr, "Reading took %g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
  last = t;

  Free(current_directory, strlen(filename)+1);
  current_directory = NULL;

  /* check for errors */
  if (!World) {
    /* restore the old scene */
    fprintf(stderr, "Restoring old scene ... ");
    fflush(stderr);
    GeomListIterate(World, GeomDestroy);
    GeomListDestroy(World);
    World = oWorld;

    MaterialListIterate(MaterialLib, MaterialDestroy);
    MaterialListDestroy(MaterialLib);
    MaterialLib = oMaterialLib;

    Patches = oPatches;
    PatchSetNextID(opatchid);
    nrpatches = onrpatches;

    ClusteredWorld = oClusteredWorld;
    ClusteredWorldGeom = oClusteredWorldGeom;
    LightSourcePatches = oLightSourcePatches;

    WorldGrid = oWorldGrid;
    Background = oBackground;

    SetRadianceMethod(oRadiance);
    SetRayTracing(oRayTracing);

    t = clock();
    fprintf(stderr, "%g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
    last = t;
    fprintf(stderr, "Done.\n");

    if (!ErrorOccurred()) 
      Error(NULL, "Empty world");

    return False;	/* not succesful */
  }

  /* initialize "monitor" */
  MonitorInit();

  /* dispose of the old scene */
  fprintf(stderr, "Disposing of the old scene ... "); fflush(stderr);
  if (Radiance) Radiance->Terminate();
  if (RayTracing) RayTracing->Terminate();

  PatchListDestroy(oPatches);
  PatchListDestroy(oLightSourcePatches);

  GeomListIterate(oWorld, GeomDestroy);
  GeomListDestroy(oWorld);

  if (oClusteredWorldGeom) GeomDestroy(oClusteredWorldGeom);
  if (oBackground) oBackground->methods->Destroy(oBackground->data);

  DestroyGrid(oWorldGrid);

  MaterialListIterate(oMaterialLib, MaterialDestroy);
  MaterialListDestroy(oMaterialLib);

  t = clock();
  fprintf(stderr, "%g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
  last = t;

  /* build the new patch list, this is duplicating already available
   * information and as such potentially dangerous, but we need it
   * so many times, so ... */
  fprintf(stderr, "Building patch list ... "); fflush(stderr);

  Patches = BuildPatchList(World, PatchListCreate());

  t = clock();
  fprintf(stderr, "%g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
  last = t;  

  /* destilate the list of patches on light sources from the above patch list. */
  fprintf(stderr, "Building light source patch list ... "); fflush(stderr);

  BuildLightSourcePatchList();

  t = clock();
  fprintf(stderr, "%g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
  last = t;

  /* build a cluster hierarchy for the new scene. */
  fprintf(stderr, "Building cluster hierarchy ... "); fflush(stderr);

  ClusteredWorldGeom = CreateClusterHierarchy(Patches);
  if (GeomIsCompound(ClusteredWorldGeom))
    ClusteredWorld = (GEOMLIST *)(ClusteredWorldGeom->obj);
  else { /* small memory leak here ... but exceptional situation! */ 
    ClusteredWorld = GeomListAdd(GeomListCreate(), ClusteredWorldGeom);
    Warning(NULL, "Strange clusters for this world ...");
  }

  t = clock();
  fprintf(stderr, "%g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
  last = t;

  /* engridding the thing */
  WorldGrid = CreateGrid(ClusteredWorldGeom);

  t = clock();
  fprintf(stderr, "Engridding took %g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
  last = t;

  /* estimate average radiance, for radiance to display RGB conversion */
  fprintf(stderr, "Computing some scene statistics ... "); fflush(stderr);

  nrpatches = nrelements;
  ComputeSomeSceneStats();
  reference_luminance = 5.42 * ((1.-ColorGray(average_reflectivity)) * 
				ColorLuminance(estimated_average_radiance));

  t = clock();
  fprintf(stderr, "%g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
  last = t;

  /* Initialize tone mapping. */
  fprintf(stderr, "Initializing tone mapping ... "); fflush(stderr);

  InitToneMapping();

  t = clock();
  fprintf(stderr, "%g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
  last = t;

  printf("Stats: total_emitted_power .............: %f W\n"
	 "       estimated_average_illuminance ...: %f W/sr\n"
	 "       average_reflectivity ............: %f\n"
	 "       max_selfemitted_radiance ........: %f W/sr\n"
	 "       max_selfemitted_power ...........: %f W\n"
	 "       adaptation_luminance ............: %f cd/m2\n"
	 "       total_area ......................: %f m2\n",
	 ColorGray(total_emitted_power),
	 ColorGray(estimated_average_radiance),
	 ColorGray(average_reflectivity),
	 ColorGray(max_selfemitted_radiance),
	 ColorGray(max_selfemitted_power),
	 tmopts.lwa,
	 total_area);

  /* initialize radiance for the freshly loaded scene */
  if (oRadiance) {
    fprintf(stderr, "Initializing radiance computations ... "); fflush(stderr);

    SetRadianceMethod(oRadiance);

    t = clock();
    fprintf(stderr, "%g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
    last = t;  
  }

  if (oRayTracing)
  {
    fprintf(stderr, "Initializing raytracing computations ... \n");

    SetRayTracing(oRayTracing);

    t = clock();
    fprintf(stderr, "%g secs.\n", (float)(t-last)/(float)CLOCKS_PER_SEC);
    last = t;  
  }

  /* Remove possible renderhooks */
  RemoveAllRenderHooks();

  /* Re-initialize path debug */
  InitPathDebug();

  fprintf(stderr, "Initialisations done.\n");
  UpdateFileStats();

  /* set main window title to filename */
#ifndef LONG_FNTITLE
  {
    char *slash = strrchr(filename, '/');
    SetWindowTitle(slash ? slash+1 /*skip '/'*/ : filename);
  }
#else
  SetWindowTitle(filename);
#endif

  return True;	/* succes */
}

/* global initializations */
void Init(void)
{
  signal(SIGALRM, default_alarm_handler);

  /* Transforms the cubature rules for quadrilaterals to be over the domain [0,1]^2
   * instead of [-1,1]^2. See cubature.[ch] */
  FixCubatureRules();

  monochrome = DEFAULT_MONOCHROME;
  force_onesided_surfaces = DEFAULT_FORCE_ONESIDEDNESS;
  nqcdivs = DEFAULT_NQCDIVS;

  MgfDefaults();
#ifndef NO_VRML
  VrmlDefaults();
#endif
  RenderingDefaults();
  ToneMapDefaults();
  CameraDefaults();
  RadianceDefaults();
  RayTracingDefaults();
  InterfaceDefaults();
  IpcDefaults();

  /* Default vertex compare flags: both location and normal is relevant. Two
   * vertices without normal, but at the same location, are to be considered
   * different. */
  VertexSetCompareFlags(VCMP_LOCATION | VCMP_NORMAL);

  /* Specify what routines to be used to compare vertices when using 
   * BREP_VERTEX_OCTREEs */
  BrepSetVertexCompareRoutine((BREP_COMPARE_FUNC)VertexCompare);
  BrepSetVertexCompareLocationRoutine((BREP_COMPARE_FUNC)VertexCompareLocation);
}

static void SetSeed(void *pseed)
{
  int seedval = *(int *)pseed;
  srand48(seedval);
}

static void ReadMGFFromStdin(void *bla)
{
  ReadFile("#.mgf");
}

#ifndef NO_VRML
static void ReadVRMLFromStdin(void *bla)
{
  ReadFile("#.wrl");
}
#endif

static void ShowUsage(void *bla);

static int yes=1, no=0;

static void ForceOnesidedOption(void *value)
{
  force_onesided_surfaces = *(int *)value;
}

static void MonochromeOption(void *value)
{
  monochrome = *(int *)value;
}

static CMDLINEOPTDESC globalOptions[] = {
  {"-mgf", 	3,	TYPELESS, 	NULL, 	ReadMGFFromStdin,
   "-mgf           \t\t: read MGF file from standard input"},
#ifndef NO_VRML
  {"-vrml", 	3,	TYPELESS, 	NULL, 	ReadVRMLFromStdin,
   "-vrml          \t\t: read VRML'97 file from standard input"},
#endif
  {"-nqcdivs",  3, 	Tint,		&nqcdivs, DEFAULT_ACTION,
   "-nqcdivs <integer>\t: number of quarter circle divisions"},
  {"-force-onesided", 10, TYPELESS,	&yes,   ForceOnesidedOption,
   "-force-onesided\t\t: force one-sided surfaces"},
  {"-dont-force-onesided", 14, TYPELESS,  &no,   ForceOnesidedOption,
   "-dont-force-onesided\t: allow two-sided surfaces"},
  {"-monochromatic", 5,	TYPELESS,	&yes,   MonochromeOption,
   "-monochromatic \t\t: convert colors to shades of grey"},
  {"-seed", 	2,	Tint, 		NULL, 	SetSeed, 
   "-seed <integer>\t\t: set seed for random number generator"},
  {"-help", 	2,	TYPELESS,	NULL,	ShowUsage,
   "-help          \t\t: show program usage and command line options"},
  {NULL	, 	0,	TYPELESS, 	NULL, 	DEFAULT_ACTION,
   NULL }
};

/* show program usage */
static void ShowUsage(void *bla)
{
  printf("Usage: %s [options] [filename]\n", progname);

  printf("\nGeneral options:\n");
  PrintOptions(stdout, globalOptions);
  PrintMgfOptions(stdout);
#ifndef NO_VRML
  PrintVrmlOptions(stdout);
#endif
  PrintCameraOptions(stdout);
  PrintRenderingOptions(stdout);
  PrintToneMapOptions(stdout);
  PrintRadianceOptions(stdout);
  PrintRayTracingOptions(stdout);
  PrintInterfaceOptions(stdout);
  PrintIpcOptions(stdout);

  exit(0);
}

/* processes command line arguments not recognized by the Xt GUI toolkit */
static void ParseGlobalOptions(int *argc, char **argv)
{
  /* copy program name from zeroeth argument */
  progname = strdup(argv[0]);

  ParseMgfOptions(argc, argv);
#ifndef NO_VRML
  ParseVrmlOptions(argc, argv);
#endif
  ParseRenderingOptions(argc, argv);
  ParseToneMapOptions(argc, argv);
  ParseCameraOptions(argc, argv);
  ParseRadianceOptions(argc, argv);
  ParseRayTracingOptions(argc, argv);
  ParseInterfaceOptions(argc, argv);
  ParseIpcOptions(argc, argv);

  ParseOptions(globalOptions, argc, argv); 	/* this one comes last in order to
						 * have all other options parsed before
						 * reading input from stdin. */
}

int main(int argc, char **argv)
{
  /* various global initialisations: sets defaults for all possible things. */
  Init();

  /* process command line arguments */
  ParseGlobalOptions(&argc, argv);

  StartUserInterface(&argc, argv);	/* never returns. */
  return 0;
}

