/*
 *  COLA -- a satellite close-approach finder
 *  Copyright (C) 1996 Matthew Francey
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Send bugs reports, comments, critique, etc, to mdf@angoss.com.
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#include <string.h>

#include "sgp.h"

/*
 * an atof() for fixed-fields
 */
static double
sgp_getf(char *s, int b, int e)
{
	double	x;
	char	c;

	c = s[e];
	s[e] = '\0';
	x = atof(s + b - 1);
	s[e] = c;
	return x;
}

/*
 * an atol() for fixed-fields
 */
static long
sgp_geti(char *s, int b, int e)
{
	long	x;
	char	c;

	c = s[e];
	s[e] = '\0';
	x = atol(s + b - 1);
	s[e] = c;
	return x;
}

/*
 * decode NORAD's strange "scientific" format
 */
static double
sgp_gete(char *s, int b)
{
	char	d[11];

	s    += b - 1;
	d[0]  = s[0];
	d[1]  = '.';
	d[2]  = s[1];
	d[3]  = s[2];
	d[4]  = s[3];
	d[5]  = s[4];
	d[6]  = s[5];
	d[7]  = 'e';
	d[8]  = s[6];
	d[9]  = s[7];
	d[10] = '\0';
	return atof(d);
}

/*
 * pattern matcher for 2-line element sets
 */
static char *tlepat[3] = {
	"AAAAAAAAAAAAAAANNN.NNNN.NNNN.NNNN.N",
	"1 NNNNNA NNNNNAAA NNNNN.NNNNNNNN +.NNNNNNNN +NNNNN-N +NNNNN-N N NNNNN",
	"2 NNNNN NNN.NNNN NNN.NNNN NNNNNNN NNN.NNNN NNN.NNNN NN.NNNNNNNNNNNNNN"
};

static int
sgp_match(char *pat, char *s, int n)
{
	char	c1, c2;

	while((c1 = *pat++) != '\0' && --n >= 0) {
		if((c2 = *s++) == '\0')
			return 0;
		switch(c1) {
		case	'N':
			if(!isdigit(c2) && !isspace(c2))
				return 0;
			break;

		case	'A':
			break;

		case	'+':
		case	'-':
			if(c2 != '+' && c2 != '-' && !isspace(c2))
				return 0;
			break;

		default:
			if(c1 != c2)
				return 0;
			break;
		}
	}
	return 1;
}

/*
 * compute the NORAD checksum character
 */
static char
sgp_check(char *s, int n)
{
	int	v = 0;
	char	c;

	while((c = *s++) != '\0' && n-- != 0) {
		if(isdigit(c))
			v += (int) (c - '0');
		else if(c == '-')
			++v;
	}
	return '0' + (char) (v % 10);
}

/*
 * load a file of element sets
 */
SAT *
sgp_sat_read(FILE *fp)
{
	char	line0[80], line1[80], line2[80];
	SAT	*s, *head, *tail;
	int	expect, n;

	line0[0] = '\0';
	head = tail = NULL;
	expect = 1;
	while(fgets(line2, sizeof(line2), fp) != NULL) {
		/*
		 * auto-detect NORAD 2-line element sets.
		 */
		if(expect == 1) {
			if(sgp_match(tlepat[1], line2, 68)) {
				strncpy(line1, line2, 70);
				expect = 2;
			} else
				strcpy(line0, line2);
			continue;
		}
		if(!sgp_match(tlepat[2], line2, 68)) {
			expect = 1;
			strcpy(line0, line2);
			continue;
		}

		/*
		 * ok, we have line0 (name), line1 and line2
		 *
		 * build up a SAT structure
		 */
		n = strlen(line0);
		while(n > 0 && isspace(line0[n-1]))
			--n;
		line0[n] = '\0';
		if(n == 0)
			sprintf(line0, "#%ld", sgp_geti(line1, 3, 7));
		s = (SAT *)calloc(1, sizeof(SAT) + strlen(line0) + 1);
		if(s == NULL) {
			fprintf(stderr, "no core\n");
			exit(1);
		}

		/*
		 * check line0 -- if size and brightness info is
		 * present, decode it.
		 */
		s->name = (char *)&s[1];
		s->mode = (n != 0) ? SGP_SAT_HASNAME : 0L;
		if(sgp_match(tlepat[0], line0, 35)) {
			s->len   = sgp_getf(line0, 16, 20);
			s->wid   = sgp_getf(line0, 21, 25);
			s->hgt   = sgp_getf(line0, 26, 30);
			s->mag0  = sgp_getf(line0, 31, 35);
			s->mode |= SGP_SAT_SIZEINFO;
			n = 16;
			while(n > 0 && isspace(line0[n-1]))
				--n;
			line0[n] = '\0';
			if(n == 0)
				sprintf(line0, "#%ld", sgp_geti(line1, 3, 7));
		}
		strcpy(s->name, line0);

		/*
		 * checksum
		 */
		if(sgp_check(line1, 68) != line1[68])
			s->mode |= SGP_SAT_CHECKSUM1;
		if(sgp_check(line2, 68) != line2[68])
			s->mode |= SGP_SAT_CHECKSUM2;

		/*
		 * line 1 of a NORAD element set
 		 */
		s->norad  = sgp_geti(line1, 3, 7);
		s->t0     = sgp_date(sgp_getf(line1, 19, 20)*1000.0
				   + sgp_getf(line1, 21, 32));
		s->dn0    = sgp_getf(line1, 34, 43)*(2.0*M_PI);
		s->ddn0   = sgp_gete(line1, 45)*(2.0*M_PI);
		s->bstar  = sgp_gete(line1, 54);
		s->bulnum = (int) sgp_geti(line1, 65, 68);
		s->ephtyp = line1[63 - 1];
		s->eclass = line1[ 8 - 1];
		strncpy(s->desig, line1 + 9, 8);
		if(s->desig[0] == ' ')
			s->desig[0] = '\0';

		/*
	 	 * line 2 of a NORAD element set
	 	 */
		s->incl0 = sgp_getf(line2,  9, 16)*(M_PI/180.0);
		s->node0 = sgp_getf(line2, 18, 25)*(M_PI/180.0);
		s->ecc0  = sgp_getf(line2, 27, 33)*(1.0/10000000.0);
		s->peri0 = sgp_getf(line2, 35, 42)*(M_PI/180.0);
		s->M0    = sgp_getf(line2, 44, 51)*(M_PI/180.0);
		s->n0    = sgp_getf(line2, 53, 63)*(2.0*M_PI);
		s->rev0  = sgp_geti(line2, 64, 68);

		/*
		 * and connect to the list
		 */
		if(head == NULL)
			head = s;
		if(tail != NULL)
			tail->next = s;
		s->prev = tail;
		tail    = s;

		/*
		 * and reset
		 */
		expect   = 1;
		line0[0] = '\0';
	}
	return head;
}

/*
 * encode a value in NORAD's strange "scientific" format
 */
static void
sgp_pute(char *s, double v)
{
	char	b[13];

	sprintf(b, "%12.4e", v*10.0);
	s[0] = b[2];
	s[1] = b[4];
	s[2] = b[5];
	s[3] = b[6];
	s[4] = b[7];
	s[5] = b[9];
	s[6] = b[11];
}

/*
 * save a set of elements to a file
 */
int
sgp_sat_write(FILE *fp, SAT *s)
{
	char	line[80];

	for(; s != NULL; s = s->next) {
		/*
		 * line 0 (if any)
		 */
		if(s->mode & SGP_SAT_SIZEINFO) {
			if(fprintf(fp, "%-15s%5.1f%5.1f%5.1f%5.1f\n",
						s->name, s->len, s->wid,
						s->hgt, s->mag0) <= 0)
				return -1;
		} else if(s->mode & SGP_SAT_HASNAME) {
			if(fprintf(fp, "%s\n", s->name) <= 0)
				return -1;
		}

		/*
		 * line 1
		 */
		sprintf(line,
			"1 %05lu%c %8.8s %14.8f%11.8f                   %c %4d",
			s->norad, s->eclass, s->desig,
			sgp_ydd(s->t0), (0.5/M_PI)*s->dn0,
				s->ephtyp, s->bulnum);
	
		/*
		 * ugly
		 */
		line[33] = line[32];
		line[32] = ' ';
		sgp_pute(&line[45], (0.5/M_PI)*s->ddn0);
		sgp_pute(&line[54], s->bstar);
		line[68] = sgp_check(line, 68);
		line[69] = '\n';
		line[70] = '\0';
		if(fputs(line, fp) < 0)
			return -1;

		/*
		 * line 2
		 */
		sprintf(line,
			"2 %05lu %8.4f %8.4f %07ld %8.4f %8.4f %11.8f%5ld",
			s->norad, (180.0/M_PI)*s->incl0, (180.0/M_PI)*s->node0,
			(long) (1.0e7*s->ecc0), (180.0/M_PI)*s->peri0,
			(180.0/M_PI)*s->M0, (0.5/M_PI)*s->n0, s->rev0);
		line[68] = sgp_check(line, 68);
		line[69] = '\n';
		line[70] = '\0';
		if(fputs(line, fp) < 0)
			return -1;
	}
	return 0;
}

SAT *
sgp_sat_free(SAT *s)
{
	SAT	*n = s->next;

	if(s->mdata != NULL)
		free(s->mdata);
	if(s->idata != NULL)
		free(s->idata);
	free((void *)s);
	return n;
}

void
sgp_sat_list_free(SAT *s)
{
	while(s != NULL)
		s = sgp_sat_free(s);
}
