/**
 * \file io_cubep3m_header.c
 *
 * Provides functions for reading and writing the header of CUBEP3M
 * files.
 */


/**********************************************************************
 *    Includes                                                        *
 **********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>
#ifdef HAVE_STDINT_H
#  include <stdint.h>
#else
#  include <replace_stdint.h>
#endif
#include <string.h>
#include <ctype.h>

#include "io_cubep3m_header.h"
#include "io_util.h"


/**********************************************************************
 *    Local defines, structure definitions and typedefs               *
 **********************************************************************/
#define RHO_CRIT_0 2.7755397e11   // [h^2*Msun]/[Mpc^3]
#define H0         100.           // [h*km]/[sec*Mpc]


/**********************************************************************
 *    Prototypes of local functions                                   *
 **********************************************************************/

/**
 * \brief  Tries to get the file number from the file name.
 *
 * This will identify the last number in the file-name and interpret
 * this as the file number, i.e for 8.397xv24.dat it would return 24.
 *
 * \param  *fname  The filename.
 *
 * \return  Returns hopefully the file number...
 */
static int
local_extract_file_number_from_name(const char *fname);


/**********************************************************************
 *    Implementation of global functions                              *
 **********************************************************************/
extern io_cubep3m_header_t
io_cubep3m_header_get(io_logging_t log, io_cubep3m_t f)
{
	io_cubep3m_header_t dummy;
	long                skipsize;
	int                 iloop;
	FILE                *fcubep3m;
	char                cubep3mline[2048];
	int                 pos[3];
	int                 tmp;
	double              delta;

	/* Some sanity checks */
	if ((f == NULL) || (f->file == NULL))
		return NULL;

	/* Check if there already is a header, do nothing then */
	if (f->header != NULL)
		return f->header;

	/* Create the header structure array */
	dummy = (io_cubep3m_header_t)malloc(sizeof(io_cubep3m_header_struct_t));
	if (dummy == NULL) {
		io_logging_memfatal(log, "CUBEP3M header structure");
		return NULL;
	}

	/* just to be on the safe side: rewind the file to the start... */
	rewind(f->file);

	/* Now start reading the header */
	io_util_readint32(f->file, &(dummy->np_local), f->swapped);
	io_util_readfloat(f->file, &(dummy->a), f->swapped);
	io_util_readfloat(f->file, &(dummy->t), f->swapped);
	io_util_readfloat(f->file, &(dummy->tau), f->swapped);
	io_util_readint32(f->file, &(dummy->nts), f->swapped);
	io_util_readfloat(f->file, &(dummy->dt_f_acc), f->swapped);
	io_util_readfloat(f->file, &(dummy->dt_pp_acc), f->swapped);
	io_util_readfloat(f->file, &(dummy->dt_c_acc), f->swapped);
	io_util_readint32(f->file, &(dummy->cur_checkpoint), f->swapped);
	io_util_readint32(f->file, &(dummy->cur_projection), f->swapped);
	io_util_readint32(f->file, &(dummy->cur_halofind), f->swapped);
	io_util_readfloat(f->file, &(dummy->mass_p), f->swapped);


	/* Get important unit things not in the file header from the user */
	if ((fcubep3m = fopen("cubep3m.info", "r")) == NULL) {
		io_logging_fatal(log,
		                 "Could not open cubep3m.info containing the "
		                 "following information:");
		io_logging_fatal(log,
		                 "0.3               CUBEP3M_OMEGA0");
		io_logging_fatal(log,
		                 "0.7               CUBEP3M_LAMBDA0");
		io_logging_fatal(log,
		                 "20.0              CUBEP3M_BOXSIZE [Mpc/h]");
		io_logging_fatal(log,
		                 "512               CUBEP3M_NGRID");
		io_logging_fatal(log,
		                 "10                CUBEP3M_NODES_DIM");
		io_logging_fatal(log, "Please generate this file. Aborting now!");
		exit(EXIT_FAILURE);
	}
	fgets(cubep3mline, 2048, fcubep3m);
	sscanf(cubep3mline, "%lf", &(dummy->omega0));
	fgets(cubep3mline, 2048, fcubep3m);
	sscanf(cubep3mline, "%lf", &(dummy->lambda0));
	fgets(cubep3mline, 2048, fcubep3m);
	sscanf(cubep3mline, "%lf", &(dummy->boxsize));
	fgets(cubep3mline, 2048, fcubep3m);
	sscanf(cubep3mline, "%" SCNu64 "", &(dummy->ngrid));
	dummy->nptotal = (dummy->ngrid / 2) * (dummy->ngrid / 2) 
	                 * (dummy->ngrid / 2);
	fgets(cubep3mline, 2048, fcubep3m);
	sscanf(cubep3mline, "%i", &(dummy->nodes_dim));
	dummy->lunit = dummy->boxsize / ((double)(dummy->ngrid));
	fclose(fcubep3m);

	/* Calc conversion factors for mass and velocities */
	dummy->munit = dummy->boxsize * dummy->boxsize * dummy->boxsize
	               / ((double)(dummy->nptotal)*8.) * dummy->omega0
	               * RHO_CRIT_0; // mass per cell, one particle has 8 cells
	dummy->vunit = dummy->boxsize * 1.5 * sqrt(dummy->omega0) * H0
	               / (dummy->ngrid * dummy->a);

	/* Calc offset of file according to its position in file grid */
	dummy->file_number = local_extract_file_number_from_name(f->fname);
	tmp                = dummy->file_number;
	pos[2]             = tmp / (dummy->nodes_dim * dummy->nodes_dim);
	tmp               -= pos[2] * (dummy->nodes_dim * dummy->nodes_dim);
	pos[1]             = tmp / dummy->nodes_dim;
	tmp               -= pos[1] * dummy->nodes_dim;
	pos[0]             = tmp;
	delta              = dummy->ngrid / ((double)(dummy->nodes_dim));
	dummy->offset[0]   = pos[0] * delta;
	dummy->offset[1]   = pos[1] * delta;
	dummy->offset[2]   = pos[2] * delta;

	/* Done */
	f->header = dummy;
	return dummy;
} /* io_cubep3m_header_get */

extern void
io_cubep3m_header_del(io_logging_t log, io_cubep3m_header_t *header)
{
	if ((header == NULL) || (*header == NULL))
		return;

	free(*header);
	*header = NULL;

	return;
}

extern void
io_cubep3m_header_write(io_logging_t        log,
                        io_cubep3m_header_t header,
                        io_cubep3m_t        f)
{
	if ((header == NULL) || (f == NULL))
		return;

	if (f->mode != IO_FILE_WRITE)
		return;

	if (f->file == NULL)
		return;

	if (header != f->header) {
		io_logging_msg(log, INT32_C(1),
		               "Writing a different header than stored in "
		               "the file object to the file.");
	}

	/* TODO: Write the header */
	return;
}

extern void
io_cubep3m_header_log(io_logging_t log, io_cubep3m_header_t header)
{
	io_logging_msg(log, INT32_C(5),
	               "  np_local:                      %" PRIi32,
	               header->np_local);
	io_logging_msg(log, INT32_C(5),
	               "   a:                            %e",
	               header->a);
	io_logging_msg(log, INT32_C(5),
	               "   t:                            %e",
	               header->t);
	io_logging_msg(log, INT32_C(5),
	               "   tau:                          %e",
	               header->tau);
	io_logging_msg(log, INT32_C(5),
	               "  nts:                           %" PRIi32,
	               header->nts);
	io_logging_msg(log, INT32_C(5),
	               "   dt_f_acc:                     %e",
	               header->dt_f_acc);
	io_logging_msg(log, INT32_C(5),
	               "   dt_pp_acc:                    %e",
	               header->dt_pp_acc);
	io_logging_msg(log, INT32_C(5),
	               "   dt_c_acc:                     %e",
	               header->dt_c_acc);
	io_logging_msg(log, INT32_C(5),
	               "   cur_checkpoint:               %" PRIi32,
	               header->cur_checkpoint);
	io_logging_msg(log, INT32_C(5),
	               "   cur_projection:               %" PRIi32,
	               header->cur_projection);
	io_logging_msg(log, INT32_C(5),
	               "   cur_halofind:                 %" PRIi32,
	               header->cur_halofind);
	io_logging_msg(log, INT32_C(5),
	               "   mass_p:                       %e",
	               header->mass_p);
	io_logging_msg(log, INT32_C(5),
	               "  Information from `cubep3m.info'");
	io_logging_msg(log, INT32_C(5),
	               "   omega0:                       %e",
	               header->omega0);
	io_logging_msg(log, INT32_C(5),
	               "   lambda0:                      %e",
	               header->lambda0);
	io_logging_msg(log, INT32_C(5),
	               "   boxsize:                      %e",
	               header->boxsize);
	io_logging_msg(log, INT32_C(5),
	               "   lunit:                        %e",
	               header->lunit);
	io_logging_msg(log, INT32_C(5),
	               "   vunit:                        %e",
	               header->vunit);
	io_logging_msg(log, INT32_C(5),
	               "   munit:                        %e",
	               header->munit);
	io_logging_msg(log, INT32_C(5),
	               "   nptotal:                      %" PRIu64,
	               header->nptotal);
	io_logging_msg(log, INT32_C(5),
	               "   ngrid:                        %" PRIu64,
	               header->ngrid);
	io_logging_msg(log, INT32_C(5),
	               "   nodes_dim:                    %i",
	               header->mass_p);
	io_logging_msg(log, INT32_C(5),
	               "  Derived information");
	io_logging_msg(log, INT32_C(5),
	               "   file_number:                  %i",
	               header->file_number);
	io_logging_msg(log, INT32_C(5),
	               "   offset:                       %e  %e  %e",
	               header->offset[0], header->offset[1],
	               header->offset[2]);

	return;
} /* io_cubep3m_header_log */

/**********************************************************************
 *    Implementation of local functions                               *
 **********************************************************************/
static int
local_extract_file_number_from_name(const char *fname)
{
	int file_number = 0;
	int pos         = strlen(fname) - 1;

	/* Skip the ending of the file */
	while (pos > 0 && !(isdigit(fname[pos])))
		pos--;
	/* Now find the beginning of the *last* number in the filename */
	while (pos > 0 && isdigit(fname[pos - 1]))
		pos--;

	/* Parse that number as the file number */
	sscanf(fname + pos, "%i", &file_number);

	return file_number;
}
