/* $Id: swc.c,v 1.9 2006/11/16 17:06:16 knolli Exp $ */

/**
 * \file swc.c
 *
 * Converts (theoretically) anything to anything..
 */


/**********************************************************************\
 *    Includes                                                        *
\**********************************************************************/
#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <io.h>
#include "part.h"


/**********************************************************************\
 *  Defines                                                           *
\**********************************************************************/
#define PACKAGE_OWNER "Steffen R. Knollmann"
#define OUTPUT_PRE "swc_output"
#define LOGFILE_NAME_PRE "swc"


/**********************************************************************\
 *  Global variables                                                  *
\**********************************************************************/
/**
 * Stores the prefix for the outputfiles
 */
static gchar *global_output_pre = OUTPUT_PRE;
/**
 * Stores the type of the input files
 */
static int32_t global_intype = INT32_C(-1);
/**
 * Stores the type of the output files
 */
static int32_t global_outtype = INT32_C(-1);
/**
 * Toggle for usage of particle weight or not.
 */
static gboolean global_weight = FALSE;
/**
 * If set, print only the version info and then exit
 */
static gboolean global_version = FALSE;
/**
 * This will get the remaining argument of the commandline
 */
static gchar **global_cmdline_rest = NULL;
/**
 * This will hold the filename of the logfile.
 */
static gchar *global_logfname = LOGFILE_NAME_PRE;
/**
 * This will hold the log verbosity.
 */
static int32_t global_logverbose = INT32_C(99);
/**
 * This will hold the reduction factor.
 */
static int32_t global_reduce = INT32_C(1);
/**
 * This will hold the reduction factor.
 */
static unsigned int global_seed = 23152;

static double global_posscale = 1.0;
static double global_velscale = 1.0;
static double global_weightscale = 1.0;

/** The options layout describing the commandline options */
static GOptionEntry entries[] = {
	{ "output", 'o', 0, G_OPTION_ARG_FILENAME,
	  &global_output_pre,
	  "The prefix for outputfiles, defaults to " OUTPUT_PRE,
	  NULL },
	{ "intype", 0, 0, G_OPTION_ARG_INT,
	  &global_intype,
	  "Type of the input: 0 Amiga, 60 Gadget, 61 MGadget",
	  NULL},
	{ "outtype", 0, 0, G_OPTION_ARG_INT,
	  &global_outtype,
	  "Type of output: 0 Amiga, 60 Gadget, 61 MGadget",
	  NULL},
	{ "weight", 'w', 0, G_OPTION_ARG_NONE,
	  &global_weight,
	  "Use particle weight. (Default: No)",
	  NULL },
	{ "log", 'l', 0, G_OPTION_ARG_FILENAME,
	  &global_logfname,
	  "The logfile name (.log gets appended), defaults to "
	  LOGFILE_NAME_PRE,
	  NULL },
	{ "verbosity", 0, 0, G_OPTION_ARG_INT,
	  &global_logverbose,
	  "Verbosity level, defaults to 99",
	  NULL},
	{ "reduce", 'r', 0, G_OPTION_ARG_INT,
	  &global_reduce,
	  "Reduce particle number by given factor.",
	  NULL},
	{ "seed", 0, 0, G_OPTION_ARG_INT,
	  &global_seed,
	  "The seed value for the RNG.",
	   NULL},
	{ "posscale", 0, 0, G_OPTION_ARG_DOUBLE,
	  &global_posscale,
	  "Scale factor from file units to Mpc.",
	   NULL},
	{ "velscale", 0, 0, G_OPTION_ARG_DOUBLE,
	  &global_velscale,
	  "Scale velocities by this factor.",
	   NULL},
	{ "weightscale", 0, 0, G_OPTION_ARG_DOUBLE,
	  &global_weightscale,
	  "Scale factor from file units to Msun.",
	   NULL},
	{ "version", 'v', 0, G_OPTION_ARG_NONE,
	  &global_version,
	  "Print a version information and exit.",
	  NULL },
	{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY,
	  &global_cmdline_rest,
	  "Huibuh",
	  NULL },
	{ NULL }
};

/**********************************************************************\
 *    Prototypes of local functions                                   *
\**********************************************************************/
static void
local_parse_commandline(int argc, char **argv);


/**********************************************************************\
 *    Implementation of global functions                              *
\**********************************************************************/
int
main(int argc, char **argv)
{
	uint64_t numpart, tmp;
	io_file_t in, out;
	io_logging_t log;
	spart_t parts;
	double pmass, lambda0, omega0, boxsize, redshift, expansion;

	/* Setup the parameters */
	local_parse_commandline(argc, argv);

	/* Setup logging stuff */
	log = io_logging_start(global_logfname, global_logverbose, 0);
	io_logging_section(log, "Prolog");
	io_logging_msg(log, INT32_C(0), "Using %s...",
	               global_cmdline_rest[0]);

	/* Get file for reading particles */
	io_logging_section(log, "Setting up files for reading");
	in = io_file_open(log,
	                  global_cmdline_rest[0],
	                  global_intype,
	                  IO_FILE_UNKOWN_SWAPPING,
	                  IO_FILE_READ);
	if (in == NULL)
		exit(EXIT_FAILURE);
	io_logging_msg(log, INT32_C(0),
	               "File opened.");
	io_file_init(log, in);
	io_logging_msg(log, INT32_C(0),
	               "File initialized.");

	/* Say a bit about the file */
	io_logging_section(log, "Input file details");
	io_file_log(log, in);
	io_file_get(log, in, IO_FILE_GET_BOXSIZE, &boxsize);
	io_file_get(log, in, IO_FILE_GET_PMASS, &pmass);
	io_file_get(log, in, IO_FILE_GET_Z, &redshift);
	io_file_get(log, in, IO_FILE_GET_A, &expansion);
	io_file_get(log, in, IO_FILE_GET_OMEGA0, &omega0);
	io_file_get(log, in, IO_FILE_GET_OMEGAL, &lambda0);

	/* Figure out how many particles are to be worked on */
	io_logging_section(log, "Preparing reading");
	numpart = io_file_get_numpart(log, in);

	/* Get storage for particles */
	io_logging_msg(log, INT32_C(0),
	               "Number of particles in memory: %" PRIu64 " (%f MB)",
	               numpart,
	               (float)(numpart*sizeof(spart_struct_t)/1024./1024.));
	parts = spart_create(log, numpart);
	if (parts == NULL) {
		io_logging_memfatal(log, "particle storage");
		exit(EXIT_FAILURE);
	}

	/* Now read the particles */
	io_logging_section(log, "Reading particles");
	if (global_weight)
		tmp = io_file_readpart(log, in, UINT64_C(0), numpart,
		                       (void *)&(parts->pos[0]),
		                       (void *)&(parts->pos[1]),
		                       (void *)&(parts->pos[2]),
		                       (void *)&(parts->mom[0]),
		                       (void *)&(parts->mom[1]),
		                       (void *)&(parts->mom[2]),
		                       (void *)&(parts->weight),
		                       (void *)&(parts->id),
		                       (int32_t)sizeof(spart_struct_t),
		                       (int32_t)sizeof(float),
		                       (int32_t)sizeof(part_id_t));
	else
		tmp = io_file_readpart(log, in, UINT64_C(0), numpart,
		                       (void *)&(parts->pos[0]),
		                       (void *)&(parts->pos[1]),
		                       (void *)&(parts->pos[2]),
		                       (void *)&(parts->mom[0]),
		                       (void *)&(parts->mom[1]),
		                       (void *)&(parts->mom[2]),
		                       NULL,
		                       (void *)&(parts->id),
		                       (int32_t)sizeof(spart_struct_t),
		                       (int32_t)sizeof(float),
		                       (int32_t)sizeof(part_id_t));
	if (tmp != numpart) {
		io_logging_warn(log, INT32_C(0),
		                "Read only %" PRIu64 " particles instead of "
		                "%" PRIu64 ". Will continue with %" PRIu64
		                " particles.",
		               tmp, numpart, tmp);
		numpart = tmp;
	}

	/* We can close the input file*/
	io_file_close(log, &in);

	/* Now open the target */
	io_logging_section(log, "Setting up files for writing");
	out = io_file_open(log,
	                   global_output_pre,
	                   global_outtype,
	                   IO_FILE_UNKOWN_SWAPPING,
	                   IO_FILE_WRITE);
	if (out == NULL)
		exit(EXIT_FAILURE);

	/* Reduce the particles? */
	if (global_reduce > 1) {
		spart_t newparts;
		uint64_t newnumpart;

		io_logging_section(log, "Reducing particle amount");
		newnumpart = numpart / global_reduce;
		newparts = spart_reduce(log, parts,
		                        numpart, newnumpart,
		                        global_seed);
		if (newparts == NULL) {
			io_logging_fatal(log, INT32_C(0),
			                 "Could not reduce particles!");
			exit(EXIT_FAILURE);
		}
		spart_del(&parts);
		parts = newparts;
		numpart = newnumpart;

		io_logging_section(log,
		                   "Adapting particle values after reduction");
		
		io_logging_msg(log, INT32_C(2), "Old pmass: %g  ", pmass);
		pmass *= global_reduce;
		io_logging_msg(log, INT32_C(2), "New pmass: %g  ", pmass);

	}

	/* Scaling particles and converting units */
	io_logging_section(log,
	                   "Scaling values from file units to AMIGA units");
	switch (global_intype) {
		case IO_FILE_AMIGA:
			io_logging_msg(log, INT32_C(0),
			               "No conversion needed, already reading "
			               "AMIGA units");
			break;
		case IO_FILE_GADGET:
		case IO_FILE_MGADGET:
			/* Scale boxsize */
			io_logging_msg(log, INT32_C(0),
			               "Scaling boxsize by (file unit -> Mpc)  : %g",
			               global_posscale);
			io_logging_msg(log, INT32_C(0), "Old boxsize: %g", boxsize);
			boxsize *=  global_posscale;
			io_logging_msg(log, INT32_C(0), "New boxsize: %g", boxsize);

			/* Scale pmass */
			io_logging_msg(log, INT32_C(0),
			               "Scaling pmass by (file unit -> Msun)   : %g",
			               global_weightscale);
			io_logging_msg(log, INT32_C(0), "Old pmass: %g  ", pmass);
			pmass *= global_weightscale;
			io_logging_msg(log, INT32_C(0), "New pmass: %g  ", pmass);

			/* Scale particle */
			io_logging_msg(log, INT32_C(0),
			               "Scaling positions by posscale/boxsize  : "
			               "%g/%g", global_posscale, boxsize);
			io_logging_msg(log, INT32_C(0),
			               "Scaling velocities by velscale         : %g",
			               global_velscale);
			io_logging_msg(log, INT32_C(0),
			               "Scaling weight by weightscale          : %g",
			               global_weightscale);
			io_logging_msg(log, INT32_C(0),
			               "Start scaling %" PRIu64 " particles.",
			               numpart);
			spart_scale(log, parts, numpart,
			            global_posscale/boxsize,
			             global_velscale * sqrt(expansion) * expansion
			            /(boxsize * 100.),
			            global_weightscale);
			io_logging_msg(log, INT32_C(0), "Done with scaling.");
			break;
		default:
			io_logging_warn(log, INT32_C(0),
			                "Don't know how to convert to %s units. "
			                "Doing nothing.",
			                io_file_typestr(global_intype));
	}

	/* Write important header variables */
	io_logging_section(log, "Writing header variables");
	io_logging_msg(log, INT32_C(0),
	               "Boxsize [Mpc/h] : %g", boxsize);
	io_logging_msg(log, INT32_C(0),
	               "Pmass [Msun]    : %g", pmass);
	io_logging_msg(log, INT32_C(0),
	               "Redshift : %g", redshift);
	io_logging_msg(log, INT32_C(0),
	               "Expansion: %g", expansion);
	io_logging_msg(log, INT32_C(0),
	               "Omega0   : %g", omega0);
	io_logging_msg(log, INT32_C(0),
	               "OmegaL   : %g", lambda0);
	io_file_set(log, out, IO_FILE_GET_BOXSIZE, &boxsize);
	io_file_set(log, out, IO_FILE_GET_PMASS, &pmass);
	io_file_set(log, out, IO_FILE_GET_Z, &redshift);
	io_file_set(log, out, IO_FILE_GET_A, &expansion);
	io_file_set(log, out, IO_FILE_GET_OMEGA0, &omega0);
	io_file_set(log, out, IO_FILE_GET_OMEGAL, &lambda0);

	/* Write particles */
	io_logging_section(log, "Writing particles");
		
	if (global_weight)
		io_file_writepart(log, out, UINT64_C(0), numpart,
		                  (void *)&(parts->pos[0]),
		                  (void *)&(parts->pos[1]),
		                  (void *)&(parts->pos[2]),
		                  (void *)&(parts->mom[0]),
		                  (void *)&(parts->mom[1]),
		                  (void *)&(parts->mom[2]),
		                  (void *)&(parts->weight),
		                  (void *)&(parts->id),
		                  (int32_t)sizeof(spart_struct_t),
		                  (int32_t)sizeof(float),
		                  (int32_t)sizeof(part_id_t));
	else
		io_file_writepart(log, out, UINT64_C(0), numpart,
		                  (void *)&(parts->pos[0]),
		                  (void *)&(parts->pos[1]),
		                  (void *)&(parts->pos[2]),
		                  (void *)&(parts->mom[0]),
		                  (void *)&(parts->mom[1]),
		                  (void *)&(parts->mom[2]),
		                  NULL,
		                  (void *)&(parts->id),
		                  (int32_t)sizeof(spart_struct_t),
		                  (int32_t)sizeof(float),
		                  (int32_t)sizeof(part_id_t));

	io_file_log(log, out);

	io_file_close(log, &out);

	/* Clean up */
	io_logging_section(log, "Epilog");
	spart_del(&parts);
	io_logging_stop(&log);

	return EXIT_SUCCESS;
}


/**********************************************************************\
 *    Implementation of local functions                               *
\**********************************************************************/
static void
local_parse_commandline(int argc, char **argv)
{
	GOptionContext *context;
	GError *error;

	/* Setup the commandline parsing */
	context = g_option_context_new("OUTFILE - swc main programm");
	g_option_context_set_ignore_unknown_options(context,
	                                            TRUE);
	g_option_context_add_main_entries(context, entries, NULL);

	/* Parse */
	error = NULL;
	g_option_context_parse(context,
	                       (gint *)&argc,
	                       (gchar ***)&argv, &error);
	if (error != NULL) {
		printf("%s\n", error->message);
		g_error_free(error);
	}

	/* Clean */
	g_option_context_free(context);

	/* Say Hi! */
	printf("Hello. This is %s - %s (%s).\n"
	       "(c) 2006 %s\n\n"
	       "This software comes without warranty. "
	       "Use it at your own risk.\n"
	       "Please send bugreports to %s.\n\n",
	       PACKAGE, PACKAGE_NAME, PACKAGE_VERSION,
	       PACKAGE_OWNER, PACKAGE_BUGREPORT);

	/* Check if we can exit */
	if (global_version)
		exit(EXIT_SUCCESS);

	/* The remaining argument should be the filename, if not, warning! */
	if (global_cmdline_rest == NULL) {
		printf("Missing a input filename.\n\n");
		exit(EXIT_FAILURE);
	}

	/* See if the input filetypt was specified, if not, warn */
	if (global_intype == -1) {
		printf("Input filetype not specified, use --intype!\n");
		exit(EXIT_FAILURE);
	}

	/* See if the output filetypt was specified, if not, warn */
	if (global_outtype == -1) {
		printf("Output filetype not specified, use --outtype!\n");
		exit(EXIT_FAILURE);
	}

	printf("\n");

	return;
}
