/*
# This file is part of the Astrometry.net suite.
# Licensed under a 3-clause BSD style license - see LICENSE
*/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <assert.h>

#include "nomad.h"
#include "nomad-fits.h"
#include "qfits_header.h"
#include "healpix.h"
#include "starutil.h"
#include "fitsioutils.h"
#include "boilerplate.h"

#define OPTIONS "ho:N:"

void print_help(char* progname) {
	BOILERPLATE_HELP_HEADER(stdout);
	printf("\nUsage:\n"
		   "  %s -o <output-filename-template>     [eg, nomad_%%03i.fits]\n"
		   "  [-N <healpix-nside>]  (default = 9)\n"
		   "  <input-file> [<input-file> ...]\n"
		   "\n"
		   "The output-filename-template should contain a \"printf\" sequence like \"%%03i\";\n"
		   "we use \"sprintf(filename, output-filename-template, healpix)\" to determine the filename\n"
		   "to be used for each healpix.\n\n"
		   "\nNOTE: WE ASSUME THE NOMAD FILES ARE GIVEN ON THE COMMAND LINE IN ORDER: 000/b0000.cat, 000/b0001.cat, etc.\n\n\n",
		   progname);
}


int main(int argc, char** args) {
	char* outfn = NULL;
    int c;
	int startoptind;
	int nrecords, nfiles;
	int Nside = 9;

	nomad_fits** nomads;

	int i, HP;
	int slicecounts[1800];

    while ((c = getopt(argc, args, OPTIONS)) != -1) {
        switch (c) {
		case '?':
        case 'h':
			print_help(args[0]);
			exit(0);
		case 'N':
			Nside = atoi(optarg);
			break;
		case 'o':
			outfn = optarg;
			break;
		}
    }

	if (!outfn || (optind == argc)) {
		print_help(args[0]);
		exit(-1);
	}

	if (Nside < 1) {
		fprintf(stderr, "Nside must be >= 1.\n");
		print_help(args[0]);
		exit(-1);
	}

	HP = 12 * Nside * Nside;

	printf("Nside = %i, using %i healpixes.\n", Nside, HP);

	nomads = calloc(HP, sizeof(nomad_fits*));

	memset(slicecounts, 0, 1800 * sizeof(uint));

	nrecords = 0;
	nfiles = 0;

	printf("Reading NOMAD files... ");
	fflush(stdout);

	startoptind = optind;
	for (; optind<argc; optind++) {
		char* infn;
		FILE* fid;
		unsigned char* map;
		size_t map_size;
		int i;
		int lastgrass;

		infn = args[optind];
		fid = fopen(infn, "rb");
		if (!fid) {
			fprintf(stderr, "Couldn't open input file %s: %s\n", infn, strerror(errno));
			exit(-1);
		}

		if ((optind > startoptind) && ((optind - startoptind) % 100 == 0)) {
			printf("\nReading file %i of %i: %s\n", optind - startoptind,
				   argc - startoptind, infn);
		}

		if (fseeko(fid, 0, SEEK_END)) {
			fprintf(stderr, "Couldn't seek to end of input file %s: %s\n", infn, strerror(errno));
			exit(-1);
		}

		map_size = ftello(fid);

		fseeko(fid, 0, SEEK_SET);

		map = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fileno(fid), 0);
		if (map == MAP_FAILED) {
			fprintf(stderr, "Couldn't mmap input file %s: %s\n", infn, strerror(errno));
			exit(-1);
		}
		fclose(fid);

		if (map_size % NOMAD_RECORD_SIZE) {
			fprintf(stderr, "Warning, input file %s has size %u which is not divisible into %i-byte records.\n",
					infn, (unsigned int)map_size, NOMAD_RECORD_SIZE);
		}

		printf("File %i of %i: %s: %i records.\n", optind - startoptind, argc - startoptind, infn, (int)(map_size / NOMAD_RECORD_SIZE));

		lastgrass = 0;
		for (i=0; i<map_size; i+=NOMAD_RECORD_SIZE) {
			nomad_entry entry;
			int hp;
			int slice;

			if ((i * 80 / map_size) != lastgrass) {
				printf(".");
				fflush(stdout);
				lastgrass = i * 80 / map_size;
			}
			
			if (nomad_parse_entry(&entry, map + i)) {
				fprintf(stderr, "Failed to parse NOMAD entry: offset %i in file %s.\n",
						i, infn);
				exit(-1);
			}

			// compute the nomad_id based on its DEC slice and index.
			slice = (int)(10.0 * (entry.dec + 90.0));
			assert(slice < 1800);
			assert((slicecounts[slice] & 0xffe00000) == 0);
			entry.nomad_id = (slice << 21) | (slicecounts[slice]);
			slicecounts[slice]++;

			hp = radectohealpix(deg2rad(entry.ra), deg2rad(entry.dec), Nside);

			if (!nomads[hp]) {
				char fn[256];
				sprintf(fn, outfn, hp);
				nomads[hp] = nomad_fits_open_for_writing(fn);
				if (!nomads[hp]) {
					fprintf(stderr, "Failed to initialized FITS file %i (filename %s).\n", hp, fn);
					exit(-1);
				}

				// header remarks...
				fits_header_add_int(nomads[hp]->header, "HEALPIX", hp, "The healpix number of this catalog.");
				fits_header_add_int(nomads[hp]->header, "NSIDE", Nside, "The healpix resolution.");
				BOILERPLATE_ADD_FITS_HEADERS(nomads[hp]->header);
				qfits_header_add(nomads[hp]->header, "HISTORY", "Created by the program \"nomadtofits\"", NULL, NULL);
				qfits_header_add(nomads[hp]->header, "HISTORY", "nomadtofits command line:", NULL, NULL);
				fits_add_args(nomads[hp]->header, args, argc);
				qfits_header_add(nomads[hp]->header, "HISTORY", "(end of command line)", NULL, NULL);

				if (nomad_fits_write_headers(nomads[hp])) {
					fprintf(stderr, "Failed to write header for FITS file %s.\n", fn);
					exit(-1);
				}
			}

			if (nomad_fits_write_entry(nomads[hp], &entry)) {
				fprintf(stderr, "Failed to write FITS entry.\n");
				exit(-1);
			}

			nrecords++;
		}

		munmap(map, map_size);

		nfiles++;
		printf("\n");
	}
	printf("\n");

	// close all the files...
	for (i=0; i<HP; i++) {
		if (!nomads[i])
			continue;
		if (nomad_fits_fix_headers(nomads[i]) ||
			nomad_fits_close(nomads[i])) {
			fprintf(stderr, "Failed to close file %i: %s\n", i, strerror(errno));
		}
	}

	printf("Read %u files, %u records.\n", nfiles, nrecords);

	free(nomads);

	return 0;
}

