// alphaint.c
// Library functions for alphabetical ordering of UTF-8 and ANSI strings
// Writed by Francisco Lloret López-Nieto, e-mail: pcmstr@terra.es
// Distributed under GPL version 2 license or any later version


#include <string.h>
#include <math.h>


// Letters. Thesse codes are valid in both ANSI and UTF-8 codes,
// but in  UTF-8 it can be stored in two bytes.

#define ALO 224 // lowercase a with open accent
#define ALC 225 // lowercase a with closed accent
#define ALF 226 // lowercase a with circumflex accent
#define ALT 227 // lowercase a with tilde
#define ALD 228 // lowercase a with dieresis
#define ALL 229 // lowercase a with little o
#define AUO 192 // uppercase a with open accent
#define AUC 193 // uppercase a with closed accent
#define AUF 194 // uppercase a with circumflex accent
#define AUT 195 // uppercase a with tilde
#define AUD 196 // uppercase a with dieresis
#define AUL 197 // uppercase a with little o
#define ELO 232 // lowercase e with open accent
#define ELC 233 // lowercase e with closed accent
#define ELF 234 // lowercase e with circumflex accent
#define ELD 235 // lowercase e with dieresis
#define EUO 200 // uppercase e with open accent
#define EUC 201 // uppercase e with closed accent
#define EUF 202 // uppercase e with circumflex accent
#define EUD 203 // uppercase e with dieresis
#define ILO 236 // lowercase i with open accent
#define ILC 237 // lowercase i with closed accent
#define ILF 238 // lowercase i with circumflex accent
#define ILD 239 // lowercase i with dieresis
#define IUO 204 // uppercase i with open accent
#define IUC 205 // uppercase i with closed accent
#define IUF 206 // uppercase i with circumflex accent
#define IUD 207 // uppercase i with dieresis
#define OLO 242 // lowercase o with open accent
#define OLC 243 // lowercase o with closed accent
#define OLF 244 // lowercase o with circumflex accent
#define OLT 245 // lowercase o with tilde
#define OLD 246 // lowercase o with dieresis
#define OUO 210 // uppercase o with open accent
#define OUC 211 // uppercase o with closed accent
#define OUF 212 // uppercase o with circumflex accent
#define OUT 213 // uppercase o with tilde
#define OUD 214 // uppercase o with dieresis
#define ULO 249 // lowercase u with open accent
#define ULC 250 // lowercase u with closed accent
#define ULF 251 // lowercase u with circumflex accent
#define ULD 252 // lowercase u with dieresis
#define UUO 217 // uppercase u with open accent
#define UUC 218 // uppercase u with closed accent
#define UUF 219 // uppercase u with circumflex accent
#define UUD 220 // uppercase u with dieresis
#define YLC 253 // lowercase y with closed accent
#define YUC 221 // uppercase y with closed accent
#define  NL 241 // lowercase n with tilde 
#define  NU 209 // uppercase n with tilde
#define  CL 231 // lowercase cedilla
#define  CU 199 // uppercase cedilla


// Character encodings
#define UTF8 0
#define ANSI 1


// Comparation functions
#define EQUAL_TO 0
#define LESS_THAN 1
#define GREATER_THAN 2


size_t UTF8long (char *s, char encoding) {
// Returns size in caracteres (not bytes) of a UTF-8 or ANSI string
	int n;

	n = 0;
	while (*s  != '\0') {
		if (((*s & 128) == 0) || ((*s & 192) == 192) || (encoding == ANSI)) n++;
		s++;
	}
	return (n);
}


char NumBytes (char *s, char encoding) {
// Return number of bytes needed by an UTF-8 o ANSI string (if ANSI always 1)
	if (encoding == UTF8) {
		return (1 + ((*s & 192) == 192) + ((*s & 224) == 224) + ((*s & 240) == 240));
	} else return 1;
}


int UTFvalid (char *s) {
// Test if a string contains a valid UTF-8 sequence.
	char n, i;

	while (*s == '\0') {
		n = NumBytes (s, UTF8);
		if (n > 1){
			for (i=1; i<n; i++) {
				switch (n) {
					case 2: if ((*s & 224) != 192) return 0;
					case 3: if ((*s & 240) != 224) return 0; 
					case 4: if ((*s & 248) != 240) return 0; 
				}
				s++;
				if (*s == '\0') {
					return 0;
				} else if ((*s & 192) != 128) return 0;
			}
		} else s++;
        }
	return 1;
}


int utf24bit (char *s, char encoding) {
// Copy an UTF-8 or ANSI char to a int
	char n, i;
	int c;
	
	if (((*s & 255) > 127) && (encoding == UTF8)) {
		n = ((*s & 192) == 192) + ((*s & 224) == 224) + ((*s & 240) == 240);
		c = pow (2,4-n) * 4 -1;
		c = (*s & c);
		for (i = 0; i < n; i++) { // n times
			s++;
			c = (c << 6) + (*s & 63);
		}
	} else c = (*s & 255);
	return (c);
}


char PutChar (char *s, int c, char encoding){
// Insert a UTF-8 or ANSI char in a string, and returns the number of bytes used.
	char buffer[4], i, n, t;

	if (((c & 255) > 127) && (encoding == UTF8)) { // > 1 bytes
		n= 2 + (c > 2047) + (c > 65535);// find number of bytes needed
		for (i = 0; i < n; i++) {	// put the char in the bytes
			buffer[i] = ((char)(c & 63));	// and insert it in the temporary buffer
			c = (c >> 6);
		}
		switch (n) {
			case 2: t = 192; break;
			case 3: t = 224; break;
			case 4: t = 250; break;
		}
		*s = (buffer[n-1] | t);	// insert first byte in string
		for (i=n; i>1; i--) {	// insert the other bytes in the string
			s++;
			*s = (buffer [i-2] | 128);
		}
	} else { // 1 byte
		*s = (char) c;
		n = 1; 
	}
	return n;
}	


void UpperCase (char *s, char encoding) {
// Changes to upcase the first character in an UTF-8 o ANSI string
	int c,  n;
	
	c = utf24bit (s, encoding);
	if (((c > 96) && (c < 123 )) || ((c > 223) && (c < 247)) || ((c > 247) && (c < 255))) {
		c = c - 32;
		PutChar (s, c, encoding); 
	}
}


void LowerCase (char *s, char encoding) {
// Changes to lowercase the first character in an UTF-8 o ANSI string
	int c;

	c = utf24bit (s, encoding);
	if (((c > 64) && (c < 91)) || ((c > 191) && (c < 215)) || ((c > 215) && (c < 223))) {
		c = c + 32;
		PutChar (s, c, encoding);	
	}
}


void StringToUpper (char *s, char encoding) {
// Converts to uppercase an UTF-8 or ANSI string
	while (*s != '\0') {
		if (((*s & 128) == 0) || ((*s & 192) == 192) || (encoding == ANSI))
			UpperCase (s, encoding);
		s++;
	}
}


void StringToLower (char *s, char encoding) {
// Converts to lowercase an UTF-8 o ANSI string
	while (*s != '\0') {
		if (((*s & 128) == 0) || ((*s & 192) == 192) || (encoding == ANSI))
			LowerCase (s, encoding);
		s++;
	}
}


void EraseAccents (char *s, char *e, char encoding) {
// Copy e in s, changing extended vocals with normal vocals: ex. á -> a
	int c;
	char n,i;
	
	while (*e != '\0') {
		if (((*e & 128) == 0) || ((*e & 192) == 192) || (encoding == ANSI)) {
			c = utf24bit (e, encoding);
			if ((c>191) && (c<198)) c=65;
			if ((c>199) && (c<204)) c=69;
			if ((c>203) && (c<208)) c=73;
			if ((c>209) && (c<215)) c=79;
			if ((c>216) && (c<221)) c=85;
			if ((c>223) && (c<230)) c=97;
			if ((c>231) && (c<236)) c=101;
			if ((c>235) && (c<240)) c=105;
			if ((c>241) && (c<247)) c=111;
			if ((c>248) && (c<253)) c=117;
			n = PutChar (s, c, encoding);
			for (i=1; i<=n; i++) s++;
		}
		e++;
	}
	*s = '\0';
}



void convert (char *ss, char *se, char *temp, char encoding, char mode) {
// Change letter codes to allow alphabetical ordering instead of ANSI ordering
	int c;
	char *t, *s, i, n;

	strcpy (temp,se);
	StringToUpper (temp, encoding);
	EraseAccents (ss, temp, encoding);
	// Reordering of Ñ and Ç letters
	t = temp;
	s = ss;
	while (*s != '\0') {
		c = utf24bit (s, encoding);
		if ((c > 67) && (c < 79) && (mode == 0)) c = c+1;
		if ((c > 78) && (c < 97)) c = c+2-mode;			
		switch (c) {
			case CU: c = 68-mode; s++; break;		
			case NU: c = 80-mode; s++; break;
		}
		n = PutChar (t, c, encoding);
		for (i=1; i<=n; i++) t++;
		s++;
	}
	*t = '\0';
	strcpy (ss,temp); // Returns the result
}


int max (int a, int b) {
	if (a > b){
		return a;
	}
	else return b;
}


int AlphaComp (char *a, char *b, char encoding, char cond) {
// Compares two UTF-8 or ANSI strings
// Returns 1 if the condition (GREATER_THAN, LESS_THAN or EQUAL_TO) is true, and 0 if not.
// If error, returns -1 for no valid function or -2 for memory error
	char *aa, *bb, *tt;
	int ln1, ln2, ln3 ,r;

	// Memory allocation
	ln1 = strlen (a);
	if ((aa = malloc(ln1 + 1)) == NULL) return -2;
	ln2 = strlen (b);
	if ((bb = malloc(ln2 + 1)) == NULL) {
		free (aa);
		return -2;
	}
	ln3=max (ln1, ln2);
	if ((tt = malloc(ln3 + 1)) == NULL) {
		free (aa);
		free (bb);
		return -2;
	}
	// Compare strings
	convert (aa, a, tt, encoding, 1);
	convert (bb, b, tt, encoding, 1);
	r = strcmp (aa, bb);
	if ((r == 0) && (cond != EQUAL_TO)) {
		convert (aa, a, tt, encoding, 0);
		convert (bb, b, tt, encoding, 0);
		r = strcmp (aa, bb);
	}
	// Free allocated memory
	free (aa);
	free (bb);
	free (tt);
	// returns result
	switch (cond) {
		case EQUAL_TO: return (r == 0); break;
		case GREATER_THAN: return (r > 0); break;
		case LESS_THAN: return (r < 0); break;
		default: return -1; // error
	}
}

