#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "Aurinkokunta.h"
#include "Cities.h"
#include "GetCity.h"
#include "Solomite.h"
#include "SoloCType.h"

/*

	This module is ported from an old Commodore 64 source file from
	1984. I have not attempted to make the original code more
	beautiful. But, it works!

*/

enum PROPERTIES {
    P_RECT,
    P_DECL,
    P_ELON,
    P_DIST,
    P_DIAM,
    P_MAXH,
    P_DE18,
    P_DE6,
    P_RISE,
    P_SET,
    P_DS6,
    P_DS18
};

static struct BODY *allBodys[BODYS] = {NULL, NULL, NULL, NULL, NULL, NULL};

static long Offset(enum PROPERTIES p) {
    switch(p) {
    case P_RECT:
	return (char *)(&allBodys[0]->rectascension) - (char *)allBodys[0];
	break;
    case P_DECL:
	return (char *)(&allBodys[0]->declination) - (char *)allBodys[0];
	break;
    case P_ELON:
	return (char *)(&allBodys[0]->elongation) - (char *)allBodys[0];
	break;
    case P_DIST:
	return (char *)(&allBodys[0]->distance) - (char *)allBodys[0];
	break;
    case P_DIAM:
	return (char *)(&allBodys[0]->diameter) - (char *)allBodys[0];
	break;
    case P_MAXH:
	return (char *)(&allBodys[0]->maxHeight) - (char *)allBodys[0];
	break;
    case P_DE18:
	return (char *)(&allBodys[0]->timeOf18DegRise) - (char *)allBodys[0];
	break;
    case P_DE6:
	return (char *)(&allBodys[0]->timeOf6DegRise) - (char *)allBodys[0];
	break;
    case P_RISE:
	return (char *)(&allBodys[0]->timeOfRise) - (char *)allBodys[0];
	break;
    case P_SET:
	return (char *)(&allBodys[0]->timeOfSet) - (char *)allBodys[0];
	break;
    case P_DS6:
	return (char *)(&allBodys[0]->timeOf6DegSet) - (char *)allBodys[0];
	break;
    case P_DS18:
	return (char *)(&allBodys[0]->timeOf18DegSet) - (char *)allBodys[0];
	break;
    }
    return -1;
}

static void printBodyElement(enum BODYNAME name, const char *title, enum PROPERTIES p) {
    int start = (name == BN_ALL) ? 0:name;
    int end =   (name == BN_ALL) ? BODYS-1:name;
    int i;
    for (i=start; i<=end; i++) {
	double *d = (double *)(((char *)allBodys[i]) + Offset(p));
	if (*d != VAL_NA || start != end) {
	    if (title && i == start)
		printf("%s", title);
			
	    if (*d == VAL_LINE)
		printf(title ? "  ----" : "----");
	    else if (*d != VAL_NA)
		if (p == P_ELON)
		    printf((title ? "%3d" : "%d"), (int)*d);
		else if (p == P_DIST || p == P_DIAM || p == P_MAXH)
		    printf((title ? "%5.1f" : "%1.1f"), *d);
		else
		    printf((title ? "%6.2f" : "%2.2f"), *d);

	    if (title)
		putchar((i==end) ? '\n' : '\t');
	} else if (*d == VAL_NA && !title) {
	    printf("N/A");
	}
    }
}


static void printBody(enum BODYNAME name) {
    int start = (name == BN_ALL) ? 0:name;
    int end =   (name == BN_ALL) ? BODYS-1:name;
    int i, tz;
    BOOL neg = (allBodys[start]->city.timeZone < 0) ? TRUE : FALSE;
    tz = neg ? -allBodys[start]->city.timeZone : allBodys[start]->city.timeZone;
    printf("\nCity: %s (%3.2f°N %3.2f°E), Date: %4d-%02d-%02d, "
	   "TZ: %c%d.%02d\n\t\t\t",
	   allBodys[start]->city.name,
	   allBodys[start]->city.latitude, allBodys[start]->city.longitude,
	   allBodys[start]->day.tm_year+1900, allBodys[start]->day.tm_mon+1,
	   allBodys[start]->day.tm_mday,
	   (neg ? '-' : '+'), tz / 3600, (tz / 60) % 60);
    for (i=start; i<=end; i++) {
	if (strlen(allBodys[i]->name) < 6)
	    putchar(' ');
	if (strlen(allBodys[i]->name) < 4)
	    putchar(' ');
	printf("%s%c", allBodys[i]->name, ((i==end) ? '\n' : '\t'));
    }
    printBodyElement(name, "Rectascension (h.mm)\t", P_RECT);
    printBodyElement(name, "Declination (° ')\t", P_DECL);
    printBodyElement(name, "Elongation (°)\t\t", P_ELON);
    printBodyElement(name, "Distance (au)\t\t", P_DIST);
    printBodyElement(name, "Diameter ('')\t\t", P_DIAM);
    printBodyElement(name, "Max height (°)\t\t", P_MAXH);
    printBodyElement(name, "Stellar dark 18° (h.mm)\t", P_DE18);
    printBodyElement(name, "End of dark 6° (h.mm)\t", P_DE6);
    printBodyElement(name, "Rising time (h.mm)\t", P_RISE);
    printBodyElement(name, "Setting time (h.mm)\t", P_SET);
    printBodyElement(name, "Beg. of dark 6° (h.mm)\t", P_DS6);
    printBodyElement(name, "Stellar dark 18° (h.mm)\t", P_DS18);
}

void Update(enum BODYNAME name, const struct tm *day, const struct CITY *city) {
    int start = (name == BN_ALL) ? 0:name;
    int end =   (name == BN_ALL) ? BODYS-1:name;
    int i;
    for (i=start; i<=end; i++) {
	if (!allBodys[i] ||
	    memcmp(&allBodys[i]->day, day, sizeof(struct tm)) ||
	    memcmp(&allBodys[i]->city, city, sizeof(struct CITY))) {
	    if (allBodys[i])
		free(allBodys[i]);
	    if (!(allBodys[i] = Aurinkokunta(i, day, city))) {
		printf("Solomite failed.\n");
		exit(EXIT_FAILURE);
	    }
	}
    }
}


int CharToBody(char c, BOOL quiet) {
    switch(c) {
    case 'S':
	return BN_SUN;
    case 'M':
	return BN_MERCURY;
    case 'V':
	return BN_VENUS;
    case 'm':
	return BN_MARS;
    case 'j':
	return BN_JUPITER;
    case 's':
	return BN_SATURN;
    case 'a':
	return BN_ALL;
    default:
	if (!quiet)
	    printf("*Bad body '%c'*", c);
	return -1;
    }
}

static char months[2][12] = {
    {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

int main(int argc, char **argv) {
    time_t timeOfDay = time(NULL);
    struct tm day = *localtime(&timeOfDay);
    int i;
    BOOL anyPrintOpts = FALSE;
    const struct CITY *city = GetDefaultCity();
    struct CITY customCity = {0.0, 0.0, 0, "Unknown"};

    for (i=1; i<argc; i++) {
	if (!strcmp(argv[i], "-h")) {
	    printf("\nSolomite v" VERSION " " COPYRIGHT " leopold@cs.tut.fi\n"
		   "\nUsage:\n%s [anything]\n\nanything may be:\n"
		   "-bd\tb is the body (S=Sun M=Mercury V=Venus m=Mars "
		   "j=Jupiter s=Saturn).\n"
		   "\td is a descriptor (c=reCtascension, l=decLination, e="
		   "Elongation,\n\ti=dIstance, m=diaMeter, h=max "
		   "Height,\n\tZ=18° before rise, D=6° before rise, "
		   "R=rise,\n\ts=set, d=6° after set, z=18° after set, "
		   "n=name.\n"
		   "-ab\tDisplay all information about body b (a=all "
		   "bodies).\n"
		   "-d yy-mm-dd\tSet current date.\n"
		   "-d nn\tChange current date by offset nn.\n"
		   "-dt\tSet current date to today.\n"
		   "-D\tShow current date.\n"
		   "-z h.mm\tSet current time zone to h.mm.\n"
		   "-Z\tShow current time zone.\n"
		   "-c nn\tSet current city to nn. The search is case and "
		   "space independent,\n\ti.e. the keyword \"hotsiminh\" "
		   "will match \"Ho Tsi Minh\".\n"
		   "-n nn\tSet current city to nn, but don´t change time zone\n"
		   "-C\tShow current city.\n"
		   "-l n e\tSet current location to n° northern latitude and "
		   "e° eastern longitude.\n"
		   "-L nn\tShow current location.\n"
		   "-P\tShow all cities in the database.\n"
		   "[else]\tAll other strings are printed as is with the "
		   "exception of \\n, which\n\twill create a new line "
		   "and \\t, which will create a tab.\n"
		   "\tAny special character (like '-') may be escaped "
		   "with a '\\'.\n\n"
		   "Examples:\n"
		   "%s -aa\n%s -c rauma \"At \" -C \", the sun rises today "
		   "@\" -SR \" and sets @\" -Ss \".\"\n"
		   "\n", argv[0], argv[0], argv[0]);
	    anyPrintOpts = TRUE;
	} else if (argv[i][0] == '-' && strlen(argv[i]) == 3) {
	    int bn = CharToBody(argv[i][1], TRUE);
	    if (bn >= 0 && bn != BN_ALL) {
		int p = -2;
		switch(argv[i][2]) {
		case 'c':
		    p = P_RECT;
		    break;
		case 'l':
		    p = P_DECL;
		    break;
		case 'e':
		    p = P_ELON;
		    break;
		case 'i':
		    p = P_DIST;
		    break;
		case 'm':
		    p = P_DIAM;
		    break;
		case 'h':
		    p = P_MAXH;
		    break;
		case 'Z':
		    p = P_DE18;
		    break;
		case 'D':
		    p = P_DE6;
		    break;
		case 'R':
		    p = P_RISE;
		    break;
		case 's':
		    p = P_SET;
		    break;
		case 'd':
		    p = P_DS6;
		    break;
		case 'z':
		    p = P_DS18;
		    break;
		case 'n':
		    p = -1;
		    break;
		}
		if (p < -1) {
		    printf("*Illegal operation '%c'*", argv[i][2]);
		} else if (p < 0) {
		    int start = (bn == BN_ALL) ? 0:bn;
		    int end =   (bn == BN_ALL) ? BODYS-1:bn;
		    int j;
		    Update(bn, &day, city);
		    for (j=start; j<=end; j++) {
			printf("%s", allBodys[j]->name);
		    }
		    anyPrintOpts = TRUE;
		} else {
		    Update(bn, &day, city);
		    printBodyElement(bn, NULL, p);
		    anyPrintOpts = TRUE;
		}
	    } else {
		int bn;
		switch(argv[i][1]) {
		case 'a':
		    if ((bn = CharToBody(argv[i][2], FALSE)) >= 0) {
			Update(bn, &day, city);
			printBody(bn);
			anyPrintOpts = TRUE;
		    }
		    break;
		default:
		    if (!strcmp(argv[i], "-dt")) {
			day = *localtime(&timeOfDay);
		    } else {
			printf("*Illegal option '-%c'*", argv[i][1]);
		    }
		    break;
		}
	    }
	} else if (!strcmp(argv[i], "-d")) {
	    if (++i >= argc) {
		printf("*No date for option '-d'*");
	    } else {
		char *s = argv[i];
		if ((*s == '-' || *s == '+' || IsDigit(*s)) &&
		    !(*(s+1+strspn(s+1, "1234567890")))) {
		    int offset = atoi(s);
		    int y=day.tm_year, m=day.tm_mon, d=day.tm_mday;
		    if (offset >= 0) {
			while (offset--) {
			    if (++d > months[y&3 ? 0:1][m]) {
				if (++m >= 12) {
				    y++;
				    m = 0;
				}
				d = 1;
			    }
			}
		    } else {
			while(offset++) {
			    if (--d < 1) {
				if (--m < 0) {
				    y--;
				    m = 11;
				}
				d = months[y&3 ? 0:1][m];
			    }
			}
		    }
		    if (y > 149 || y < 50) {
			printf("*Illegal year %d (range 1950..2049)*",
			       y+1900);
		    } else {
			day.tm_year = y;
			day.tm_mon = m;
			day.tm_mday = d;
		    }
		} else {
		    int y=0, m=0, d=0;
		    BOOL goodDate = FALSE;
		    if (IsDigit(*s)) {
			if (IsDigit(*(s+1))) {
			    y = (*s-'0')*10 + (*(s+1)-'0');
			    s += 2;
			} else {
			    y = *s++ - '0';
			}
			if (*s == '-') {
			    s++;
			    if (IsDigit(*s)) {
				if (IsDigit(*(s+1))) {
				    m = (*s-'0')*10 + (*(s+1)-'0');
				    s += 2;
				} else {
				    m = *s++ - '0';
				}
				if (*s == '-') {
				    s++;
				    if (IsDigit(*s)) {
					if (IsDigit(*(s+1))) {
					    d = (*s-'0')*10 + (*(s+1)-'0');
					    s += 2;
					} else {
					    d = *s++ - '0';
					}
					if (*s == '\0')
					    goodDate = TRUE;
				    }
				}
			    }
			}
		    }
		    if (goodDate) {
			if (m < 1 || m > 12) {
			    printf("*Illegal month %d (range 1..12)*", m);
			} else if (d < 1 || d > months[y&3 ? 0:1][m-1]) {
			    printf("*Illegal day %d (range 1..%d)*", d,
				   months[y&3 ? 0:1][m-1]);
			} else {
			    day.tm_year = (y >= 50) ? y : y+100;
			    day.tm_mon = m-1;
			    day.tm_mday = d;
			}
		    } else {
			printf("*Malformed date*");
		    }
		}
	    }
	} else if (!strcmp(argv[i], "-D")) {
	    printf("%4d-%02d-%02d", day.tm_year+1900,
		   day.tm_mon+1, day.tm_mday);
	    anyPrintOpts = TRUE;
	} else if (!strcmp(argv[i], "-z")) {
	    if (++i >= argc) {
		printf("*No time zone for option '-z'*");
	    } else {
		double tz = atof(argv[i]);
		if (fabs(tz) >= 24.0) {
		    printf("*Illegal time zone %3.2f (range -24.0..24.0)*",
			   tz);
		} else {
		    BOOL neg = (tz < 0.0) ? TRUE : FALSE;
		    tz = neg ? -tz : tz;
		    if (city != &customCity)
			customCity = *city;
		    customCity.timeZone = (neg ? -1 : 1) * (((int)tz*3600) +
							    (int)((tz-(int)tz)*6000));
		    city = &customCity;
		}
	    }
	} else if (!strcmp(argv[i], "-Z")) {
	    int neg, tz;
	    neg = (city->timeZone < 0) ? TRUE : FALSE;
	    tz = neg ? -city->timeZone : city->timeZone;
	    printf("%c%d.%02d", (neg ? '-' : '+'), tz / 3600, (tz / 60) % 60);
	    anyPrintOpts = TRUE;
	} else if (!strcmp(argv[i], "-C")) {
	    printf("%s", city->name);
	    anyPrintOpts = TRUE;
	} else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "-n")) {
	    if (++i >= argc) {
		printf("*No city for option '-c'*");
	    } else {
		const struct CITY *c = GetCity(argv[i]);
		if (c) {
		    if (!strcmp(argv[i-1], "-n")) {
			int tz = city->timeZone;
			customCity = *c;
			customCity.timeZone = tz;
			city = &customCity;
		    } else {
			city = c;
		    }
		} else {
		    printf("*Unknown city %s*", argv[i]);
		}
	    }
	} else if (!strcmp(argv[i], "-L")) {
	    printf("%3.2f°N %3.2f°E", city->latitude, city->longitude);
	    anyPrintOpts = TRUE;
	} else if (!strcmp(argv[i], "-l")) {
	    double latitude, longitude;
	    i += 2;
	    if (i >= argc) {
		printf("*No latitude and/or longitude for option '-l'*");
	    } else if (fabs((latitude = atof(argv[i-1]))) > 90.0) {
		printf("*Illegal latitude %s (range: -90..90)*", argv[i-1]);
	    } else if (fabs((longitude = atof(argv[i]))) > 180.0) {
		printf("*Illegal longitude %s (range: -180..180)*", argv[i]);
	    } else {
		if (city != &customCity)
		    customCity = *city;
		customCity.latitude = latitude;
		customCity.longitude = longitude;
		strcpy(customCity.name, "<unknown>");
		city = &customCity;
	    }
	} else if (!strcmp(argv[i], "-P")) {
	    PrintCities();
	    anyPrintOpts = TRUE;
	} else {
	    char *s = argv[i];
	    BOOL esc = FALSE;
	    while (*s) {
		if (esc) {
		    if (*s == 'n')
			putchar('\n');
		    else if (*s == 't')
			putchar('\t');
		    else 
			putchar(*s);
		    esc = FALSE;
		} else if (*s == '\\') {
		    esc = TRUE;
		} else {
		    putchar(*s);
		}
		s++;
	    }
	    anyPrintOpts = TRUE;
	}
    }

    if (!anyPrintOpts) {
	Update(BN_SUN, &day, city);
	printf("%02d-%02d-%02d: Twilight: ",
	       day.tm_year%100, day.tm_mon+1, day.tm_mday); 
	printBodyElement(BN_SUN, NULL, P_DE6);
	printf(", Sunrise: "); 
	printBodyElement(BN_SUN, NULL, P_RISE);
	printf(", Sunset: "); 
	printBodyElement(BN_SUN, NULL, P_SET);
	printf(", Dark: "); 
	printBodyElement(BN_SUN, NULL, P_DS6);
    }

    printf("\n");

    return EXIT_SUCCESS;
}
