/**************************************************************

string.c (C-Munipack project)
String conversion functions
Copyright (C) 2003 David Motl, dmotl@volny.cz

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

**************************************************************/

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

#include "cmpack_common.h"
#include "strutils.h"
#include "comfun.h"

#define ALLOC_BY 64

/***********************      Datatypes      ******************/

struct _CmpackString
{
	int count, capacity;
	char *buf;
};

/*********************   String conversion functions   **************************/
        
/* Parses string in various formats to R.A./DEC./LON./LAT. */
static int rd_parse(const char *buf, double *val)
{
	int i, res, sign, space;
	const char *sptr;
	char *aux, *dptr;
	double d[3];
	struct lconv * lc;

	if (val) 
		*val = 0.0;
	if (!buf) 
		return CMPACK_ERR_INVALID_PAR;

	lc = localeconv();

	/* Preprocessing of the string.
	   - Determine the sign and remove it from the string
	   - Replace period and comma by correct decimal separator
	   - Replace non-numeric characters by spaces
	   - Replace series of whitespace characters by a single space
	   - Removes leading and trailing whitespace characters
	*/
	space = sign = 1;
	aux = (char *)cmpack_malloc(strlen(buf)+1);
	for (sptr=buf, dptr=aux; *sptr!='\0'; sptr++) {
		char ch = *sptr;
		if (ch=='+' || ch=='N' || ch=='E' || ch=='n' || ch=='e') {
			sign = 1;
		} else
		if (ch=='-' || ch=='W' || ch=='S' || ch=='w' || ch=='s') {
			sign = -1;
		} else
		if (ch=='.' || ch==',') {
			*dptr++ = *lc->decimal_point;
			space = 0;
		} else
		if (ch>='0' && ch<='9') {
			*dptr++ = ch;
			space = 0;
		} else 
		if (!space) {
			*dptr++ = ' ';
			space = 1;
		}
	}
	*dptr = '\0';
	rtrim(aux);

	/* The string consists of up to three integer or real numbers 
	   separated by a single space. We parse them using sscanf
	   function into vector d */
	d[0] = d[1] = d[2] = 0.0;
	i = sscanf(aux, "%80lf %80lf %80lf", d, d+1, d+2);
	if (i>0) {
		/* At least one component was found */
		if (val)
			*val = sign*(d[0] + d[1]/60.0 + d[2]/3600.0);
		res = CMPACK_ERR_OK;
	} else {
		/* There are no numbers in the given string */
		if (val)
			*val = 0;
		res = CMPACK_ERR_INVALID_PAR;
	}
	cmpack_free(aux);
	
	return res;
}
       
/******************** Conversion of coordinates **********************/

/* Converts the string to R.A. */
int cmpack_strtora(const char *buf, double *ra)
{
	double x;

	if (rd_parse(buf, &x)!=0 || x<0.0 || x>=24.0) {
		if (ra)
			*ra = 0.0;
		return CMPACK_ERR_INVALID_PAR;
	} else {
		if (ra)
			*ra = x;
		return CMPACK_ERR_OK;
	}
}

/* Converts the R.A. into the string */
int cmpack_ratostr(double ra, char *buf, int buflen)
{
	return cmpack_ratostr2(ra, buf, buflen, 0);
}

/* Converts the R.A. into the string */
int cmpack_ratostr2(double ra, char *buf, int buflen, int places)
{
	if (ra >= 0.0 && ra<24.0) {
		if (places == 0) {
			int x = (int)(ra * 3600.0 + 0.5);
			sprintf(buf, "%d %02d %02d", (x / 3600), ((x / 60) % 60), (x % 60));
			return CMPACK_ERR_OK;
		}
		else if (places == 1) {
			int x = (int)(ra * 36000.0 + 0.5);
			sprintf(buf, "%d %02d %02d.%01d", (x / 36000), ((x / 600) % 60), ((x / 10) % 60), (x % 10));
			return CMPACK_ERR_OK;
		}
		else if (places == 2) {
			int x = (int)(ra * 360000.0 + 0.5);
			sprintf(buf, "%d %02d %02d.%02d", (x / 360000), ((x / 6000) % 60), ((x / 100) % 60), (x % 100));
			return CMPACK_ERR_OK;
		}
		else if (places == 3) {
			int x = (int)(ra * 3600000.0 + 0.5);
			sprintf(buf, "%d %02d %02d.%03d", (x / 3600000), ((x / 60000) % 60), ((x / 1000) % 60), (x % 1000));
			return CMPACK_ERR_OK;
		}
	}
	return CMPACK_ERR_INVALID_PAR;
}

int cmpack_strtodec(const char *buf, double *dec)
/* Converts the string to DEC. in degrees */
{
	double x;

	if (rd_parse(buf, &x)!=0 || x<-90.0 || x>90.0) {
		if (dec)
			*dec = 0.0;
		return CMPACK_ERR_INVALID_PAR;
	} else {
		if (dec)
			*dec = x;
		return CMPACK_ERR_OK;
	}
}

int cmpack_dectostr(double dec, char *buf, int buflen)
/* Converts the DEC. into the string */
{
	return cmpack_dectostr2(dec, buf, buflen, 0);
}

int cmpack_dectostr2(double dec, char *buf, int buflen, int places)
/* Converts the DEC. into the string */
{
	if (dec >= -90.0 && dec <= 90.0) {
		if (dec >= 0) {
			if (places == 0) {
				int x = (int)(dec*3600.0 + 0.5);
				if (x > 0)
					sprintf(buf, "+%d %02d %02d", (x / 3600), ((x / 60) % 60), (x % 60));
				else
					strcpy(buf, "0 00 00");
				return CMPACK_ERR_OK;
			}
			else if (places == 1) {
				int x = (int)(dec*36000.0 + 0.5);
				if (x > 0)
					sprintf(buf, "+%d %02d %02d.%01d", (x / 36000), ((x / 600) % 60), ((x / 10) % 60), (x % 10));
				else
					strcpy(buf, "0 00 00.0");
				return CMPACK_ERR_OK;
			}
			else if (places == 2) {
				int x = (int)(dec*360000.0 + 0.5);
				if (x > 0)
					sprintf(buf, "+%d %02d %02d.%02d", (x / 360000), ((x / 6000) % 60), ((x / 100) % 60), (x % 100));
				else
					strcpy(buf, "0 00 00.00");
				return CMPACK_ERR_OK;
			}
			else if (places == 3) {
				int x = (int)(dec*3600000.0 + 0.5);
				if (x > 0)
					sprintf(buf, "+%d %02d %02d.%03d", (x / 3600000), ((x / 60000) % 60), ((x / 1000) % 60), (x % 1000));
				else
					strcpy(buf, "0 00 00.000");
				return CMPACK_ERR_OK;
			}
		}
		else {
			if (places == 0) {
				int x = (int)(-dec*3600.0 + 0.5);
				if (x > 0)
					sprintf(buf, "-%d %02d %02d", (x / 3600), ((x / 60) % 60), (x % 60));
				else
					strcpy(buf, "0 00 00");
				return CMPACK_ERR_OK;
			}
			else if (places == 1) {
				int x = (int)(-dec*36000.0 + 0.5);
				if (x > 0)
					sprintf(buf, "-%d %02d %02d.%01d", (x / 36000), ((x / 600) % 60), ((x / 10) % 60), (x % 10));
				else
					strcpy(buf, "0 00 00.0");
				return CMPACK_ERR_OK;
			}
			else if (places == 2) {
				int x = (int)(-dec*360000.0 + 0.5);
				if (x > 0)
					sprintf(buf, "-%d %02d %02d.%02d", (x / 360000), ((x / 6000) % 60), ((x / 100) % 60), (x % 100));
				else
					strcpy(buf, "0 00 00.00");
				return CMPACK_ERR_OK;
			}
			else if (places == 3) {
				int x = (int)(-dec*3600000.0 + 0.5);
				if (x > 0)
					sprintf(buf, "-%d %02d %02d.%03d", (x / 3600000), ((x / 60000) % 60), ((x / 1000) % 60), (x % 1000));
				else
					strcpy(buf, "0 00 00.000");
				return CMPACK_ERR_OK;
			}
		}
		return CMPACK_ERR_OK;
	}
	return CMPACK_ERR_INVALID_PAR;
}


int cmpack_strtolon(const char *buf, double *dec)
/* Converts the string to longitude in degrees */
{
	double x;

	if (rd_parse(buf, &x)!=0 || x<-180.0 || x>180.0) {
		if (dec)
			*dec = 0.0;
		return CMPACK_ERR_INVALID_PAR;
	} else {
		if (dec)
			*dec = x;
		return CMPACK_ERR_OK;
	}
}

int cmpack_lontostr(double lon, char *buf, int buflen)
/* Converts the longitude into the string */
{
	if (lon>=-180.0 && lon<=180.0) {
		if (lon>=0) {
			int x = (int)(lon*3600.0+0.5);
			if (x==0)
				strcpy(buf, "0 00 00");
			else if (x==180*3600)
				strcpy(buf, "180 00 00");
			else
				sprintf(buf, "%d %02d %02d E", (x/3600), ((x/60)%60), (x%60));
		} else {
			int x = (int)(-lon*3600.0+0.5);
			if (x==0)
				strcpy(buf, "0 00 00");
			else if (x==180*3600)
				strcpy(buf, "180 00 00");
			else
				sprintf(buf, "%d %02d %02d W", (x/3600), ((x/60)%60), (x%60));
		}
		return CMPACK_ERR_OK;
	}
	return CMPACK_ERR_INVALID_PAR;
}

int cmpack_strtolat(const char *buf, double *lat)
/* Converts the string to latitude in degrees */
{
	double x;

	if (rd_parse(buf, &x)!=0 || x<-90.0 || x>90.0) {
		if (lat)
			*lat = 0.0;
		return CMPACK_ERR_INVALID_PAR;
	} else {
		if (lat)
			*lat = x;
		return CMPACK_ERR_OK;
	}
}

int cmpack_lattostr(double lat, char *buf, int buflen)
/* Converts the latitude into the string */
{
	if (lat>=-90.0 && lat<=90.0) {
		if (lat>=0) {
			int x = (int)(lat*3600.0+0.5);
			if (x>0)
				sprintf(buf, "%d %02d %02d N", (x/3600), ((x/60)%60), (x%60));
			else
				strcpy(buf, "0 00 00");
		} else {
			int x = (int)(-lat*3600.0+0.5);
			if (x>0)
				sprintf(buf, "%d %02d %02d S", (x/3600), ((x/60)%60), (x%60));
			else
				strcpy(buf, "0 00 00");
		}
		return CMPACK_ERR_OK;
	}
	return CMPACK_ERR_INVALID_PAR;
}

/**********************   DYNAMIC STRINGS   ************************************/

/* Create an empty string */
CmpackString *cmpack_str_create(void)
{
	return (CmpackString*)cmpack_calloc(1, sizeof(CmpackString));
}

/* Create an empty string from nul-terminated buffer */
CmpackString *cmpack_str_from_text(const char *text, int nchar)
{
	CmpackString *str = cmpack_str_create();
	cmpack_str_set_text(str, text, nchar);
	return str;
}

/* Create an empty string from nul-terminated buffer */
CmpackString *cmpack_str_from_string(const CmpackString *from)
{
	CmpackString *str = cmpack_str_create();
	cmpack_str_set_text(str, from->buf, from->count);
	return str;
}

/* Free allocated memory in a string buffer */
void cmpack_str_free(CmpackString *str)
{
	if (str) {
		cmpack_free(str->buf);
		cmpack_free(str);
	}
}

/* Clear a string (do not free the memory) */
void cmpack_str_clear(CmpackString *str)
{
	str->count = 0;
}

void cmpack_str_set_text(CmpackString *str, const char *buf, int len)
{
	cmpack_str_clear(str);
	cmpack_str_add_text(str, buf, len);
}

void cmpack_str_set_string(CmpackString *str, const CmpackString *from)
{
	cmpack_str_set_text(str, from->buf, from->count);
}

/* Add a character to a string buffer */
void cmpack_str_add_text(CmpackString *str, const char *buf, int len)
{
	int size = (len<0 ? (int)strlen(buf) : len);
	if (size>0) {
		if (str->count + size >= str->capacity) {
			str->capacity += (size < ALLOC_BY ? ALLOC_BY : size);
			str->buf = (char*)cmpack_realloc(str->buf, str->capacity*sizeof(char));
		}
		memcpy(str->buf + str->count, buf, size);
		str->count += size;
	}
}

/* Add a character to a string buffer */
void cmpack_str_add_string(CmpackString *str, const CmpackString *from)
{
	cmpack_str_add_text(str, from->buf, from->count);
}

/* Returns length of the string in characters (without trailing null) */
int cmpack_str_length(const CmpackString *str)
{
	return str->count;
}

/* Convert a string buffer to null-terminated string (use free to destroy it) */
const char *cmpack_str_cstr(CmpackString *str)
{
	if (str->count >= str->capacity) {
		str->capacity += ALLOC_BY;
		str->buf = (char*) cmpack_realloc(str->buf, str->capacity*sizeof(char));
	}
	str->buf[str->count] = '\0';
	return str->buf;
}

/* Convert a string buffer to null-terminated string (use free to destroy it) */
int cmpack_str_int(CmpackString *str, int *value)
{
	char *endptr;

	const char *aux = cmpack_str_cstr(str);
	if (aux) {
		*value = strtol(aux, &endptr, 10);
		return (endptr!=aux);
	}
	return 0;
}

/* Convert a string buffer to null-terminated string (use free to destroy it) */
int cmpack_str_dbl(CmpackString *str, double *value)
{
	char *endptr;

	const char *aux = cmpack_str_cstr(str);
	if (aux) {
		*value = strtod(aux, &endptr);
		return (endptr!=aux);
	}
	return 0;
}

/* Trim all trailing white characters from the string */
void cmpack_str_rtrim(CmpackString *str)
{
	int i;

	for (i=str->count-1; i>=0; i--) {
		char ch = str->buf[i];
		if (ch<'\0' || ch>' ')
			break;
		str->count--;
	}
}

/* Remove leading white spaces	*/
static char *str_ltrim(char *str)
{
	if (str) {
		char *ptr = str;
		while (*ptr>0 && *ptr<=' ')
			ptr++;
		if (ptr!=str) 
			memmove(str, ptr, strlen(ptr)+1);
	}
	return str;
}


/* Remove trailing white spaces */
static char *str_rtrim(char *str)
{
	if (str) {
		const char *ptr = strchr(str, '\0');
		while (ptr != str) {
			ptr--;
			if (*ptr<0 || *ptr>' ')
				break;
		}
		str[ptr-str+1] = '\0';
	}
	return str;
}

/* Remove leading and trailing white spaces */
char *cmpack_str_trim(char *str)
{
	return str_rtrim(str_ltrim(str));
}
