/*-------------------------------------------------------------------------
 *
 * isn.c
 *	  PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC)
 *
 * Copyright (c) 2004-2006, Germán Méndez Bravo (Kronuz)
 * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  $PostgreSQL: pgsql/contrib/isn/isn.c,v 1.3 2006/09/22 21:39:57 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "fmgr.h"
#include "utils/builtins.h"

PG_MODULE_MAGIC;

#include "isn.h"

#include "EAN13.h"
#include "ISBN.h"
#include "ISMN.h"
#include "ISSN.h"
#include "UPC.h"

#define MAXEAN13LEN 18

enum isn_type { INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC };

static const char *isn_names[] = { "EAN13/UPC/ISxN", "EAN13/UPC/ISxN", "EAN13", "ISBN", "ISMN", "ISSN", "UPC" };

static bool g_weak = false;
static bool g_initialized = false;

/* Macros for converting TEXT to and from c-string */
#define GET_TEXT(cstrp) DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(cstrp)))
#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp)))


/***********************************************************************
 **
 **		Routines for EAN13/UPC/ISxNs.
 **
 ** Note:
 **  In this code, a normalized string is one that is known to be a valid 
 **  ISxN number containing only digits and hyphens and with enough space 
 **  to hold the full 13 digits plus the maximum of four hyphens.
 ***********************************************************************/

/*----------------------------------------------------------
 * Debugging routines.
 *---------------------------------------------------------*/

/*
 * Check if the table and its index is correct (just for debugging)
 */
#ifdef ISN_DEBUG
static
bool check_table(const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
{
	const char *aux1, *aux2;
	int a, b, x=0, y=-1, i=0, j, cnt=0, init=0;
	
	if(TABLE == NULL || TABLE_index == NULL) return true;

	while(TABLE[i][0] && TABLE[i][1]) {
		aux1 = TABLE[i][0];
		aux2 = TABLE[i][1];

		/* must always start with a digit: */
		if (!isdigit((unsigned char) *aux1) || !isdigit((unsigned char) *aux2))
			goto invalidtable;
		a = *aux1 - '0';
		b = *aux2 - '0';

		/* must always have the same format and length: */
		while(*aux1 && *aux2) {
			if (!(isdigit((unsigned char) *aux1) &&
				  isdigit((unsigned char) *aux2)) &&
				(*aux1 != *aux2 || *aux1 != '-')) 
				goto invalidtable;
			aux1++;
			aux2++;
		}
		if(*aux1!=*aux2) goto invalidtable;

		/* found a new range */
		if(a>y) {
			/* check current range in the index: */
			for(j=x;j<=y;j++) {
				if(TABLE_index[j][0] != init) goto invalidindex;
				if(TABLE_index[j][1] != i-init) goto invalidindex;
			}
			init = i;
			x = a;
		} 

		/* Always get the new limit */
		y = b;
		if(y<x) goto invalidtable;
		i++;
	}

	return true;

invalidtable:
	elog(DEBUG1, "invalid table near {\"%s\", \"%s\"} (pos: %d)",
		 TABLE[i][0], TABLE[i][1], i);
	return false;

invalidindex:
	elog(DEBUG1, "index %d is invalid", j);
	return false;
}
#endif /* ISN_DEBUG */

/*----------------------------------------------------------
 * Formatting and conversion routines.
 *---------------------------------------------------------*/

static
unsigned dehyphenate(char *bufO, char *bufI)
{
	unsigned ret = 0;
	while(*bufI) {
		if(isdigit((unsigned char) *bufI)) {
			*bufO++ = *bufI;
			ret++;
		}
		bufI++;
	}
	*bufO = '\0';
	return ret;
}

/*
 * hyphenate --- Try to hyphenate, in-place, the string starting at bufI 
 *                into bufO using the given hyphenation range TABLE.
 *                Assumes the input string to be used is of only digits.
 *
 * Returns the number of characters acctually hyphenated.
 */
static
unsigned hyphenate(char *bufO, char *bufI, const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
{
	unsigned ret = 0;
	const char *ean_aux1, *ean_aux2, *ean_p;
	char *firstdig, *aux1, *aux2;
	unsigned search, upper, lower, step;
	bool ean_in1, ean_in2;

	/* just compress the string if no further hyphenation is required */
	if(TABLE == NULL || TABLE_index == NULL) {
		while(*bufI) {
			*bufO++ = *bufI++;
			ret++;
		}
		*bufO = '\0';
		return (ret+1);
	}

	/* add remaining hyphenations */

	search = *bufI - '0';
	upper = lower = TABLE_index[search][0];
	upper += TABLE_index[search][1];
	lower--;

	step = (upper - lower) / 2;
	if(step == 0) return 0;
	search = lower + step;

	firstdig = bufI;
	ean_in1 = ean_in2 = false;
	ean_aux1 = TABLE[search][0];
	ean_aux2 = TABLE[search][1];
	do {
		if((ean_in1 || *firstdig>=*ean_aux1) && (ean_in2 || *firstdig<=*ean_aux2)) {
			if(*firstdig > *ean_aux1) ean_in1 = true;
			if(*firstdig < *ean_aux2) ean_in2 = true;
			if(ean_in1 && ean_in2) break;

			firstdig++, ean_aux1++, ean_aux2++;
			if(!(*ean_aux1 && *ean_aux2 && *firstdig)) break;
			if(!isdigit((unsigned char) *ean_aux1)) ean_aux1++, ean_aux2++;
		} else {
			/* check in what direction we should go and move the pointer accordingly */
			if(*firstdig < *ean_aux1 && !ean_in1) upper = search;
			else lower = search;

			step = (upper - lower) / 2;
			search = lower + step;

			/* Initialize stuff again: */
			firstdig = bufI;
			ean_in1 = ean_in2 = false;
			ean_aux1 = TABLE[search][0];
			ean_aux2 = TABLE[search][1];
		}
	} while(step);
	
	if(step) {
		aux1 = bufO;
		aux2 = bufI;
		ean_p = TABLE[search][0];
		while(*ean_p && *aux2) {
			if(*ean_p++!='-') *aux1++ = *aux2++;
			else *aux1++ = '-';
			ret++;
		}
		*aux1++='-';
		*aux1 = *aux2; /* add a lookahead char */
		return (ret+1);
	}
	return ret;
}

/*
 * weight_checkdig -- Receives a buffer with a normalized ISxN string number, 
 *                     and the length to weight.
 *
 * Returns the weight of the number (the check digit value, 0-10)
 */
static
unsigned weight_checkdig(char *isn, unsigned size)
{
	unsigned weight = 0;
	while(*isn && size>1) {
		if(isdigit((unsigned char) *isn)) {
			weight += size-- * (*isn - '0');
		}
		isn++;
	}
	weight = weight % 11;
	if(weight != 0) weight = 11 - weight;
	return weight;
}


/*
 * checkdig --- Receives a buffer with a normalized ISxN string number, 
 *               and the length to check.
 *
 * Returns the check digit value (0-9)
 */
static
unsigned checkdig(char *num, unsigned size)
{
	unsigned check=0, check3=0;
	unsigned pos = 0;
	if(*num == 'M') { /* ISMN start with 'M' */
		check3 = 3;
		pos = 1;
	}
	while(*num && size>1) {
		if(isdigit((unsigned char) *num)) {
			if(pos++%2) check3 += *num - '0';
			else check += *num - '0';
			size--;
		}
		num++;
	}
	check = (check + 3*check3) % 10;
	if(check != 0) check = 10 - check;
	return check;
}

/*
 * ean2isn --- Try to convert an ean13 number to a UPC/ISxN number.
 *             This doesn't verify for a valid check digit.
 *
 * If errorOK is false, ereport a useful error message if the ean13 is bad.
 * If errorOK is true, just return "false" for bad input.
 */
static
bool ean2isn(ean13 ean, bool errorOK, ean13 *result, enum isn_type accept)
{
	enum isn_type type = INVALID;

	char buf[MAXEAN13LEN + 1];
	char *firstdig, *aux;
	unsigned digval;
	unsigned search;
	ean13 ret = ean;
	
	ean >>= 1;
	/* verify it's in the EAN13 range */
	if(ean > UINT64CONST(9999999999999))
		goto eantoobig;

	/* convert the number */
	search = 0;
	firstdig = aux = buf + 13;
	*aux = '\0';	    /* terminate string; aux points to last digit */
	do {
	    digval = (unsigned)(ean % 10);		/* get the decimal value */ 
	    ean /= 10;												/* get next digit */
		*--aux = (char)(digval + '0');			/* convert to ascii and store */
	} while(ean && search++<12);
	while(search++<12) *--aux = '0';			/* fill the remaining EAN13 with '0' */
	
	/* find out the data type: */
	if(!strncmp("978", buf, 3)) { /* ISBN */
		type = ISBN;
	} else if(!strncmp("977", buf, 3)) { /* ISSN */
		type = ISSN;
	} else if(!strncmp("9790", buf, 4)) { /* ISMN */
		type = ISMN;
	} else if(!strncmp("979", buf, 3)) { /* ISBN-13 */
		type = ISBN;
	} else if(*buf == '0') { /* UPC */
		type = UPC;
	} else {
		type = EAN13;
	}
	if(accept != ANY && accept != EAN13 && accept != type) goto eanwrongtype;

	*result = ret;
	return true;
	
eanwrongtype:
	if(!errorOK) {
		if(type!=EAN13) {
			ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("cannot cast EAN13(%s) to %s for number: \"%s\"",
						isn_names[type], isn_names[accept], buf)));
		} else {
			ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("cannot cast %s to %s for number: \"%s\"",
						isn_names[type], isn_names[accept], buf)));
		}
	}
	return false;

eantoobig:
	if(!errorOK) {
		char	eanbuf[64];

		/*
		 * Format the number separately to keep the machine-dependent
		 * format code out of the translatable message text
		 */
		snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("value \"%s\" is out of range for %s type",
						eanbuf, isn_names[type])));
	}
	return false;
}

/*
 * ean2UPC/ISxN --- Convert in-place a normalized EAN13 string to the corresponding 
 *                  UPC/ISxN string number. Assumes the input string is normalized.
 */
static inline
void ean2ISBN(char *isn)
{
	char *aux;
	unsigned check;
	/* the number should come in this format: 978-0-000-00000-0 */
	/* Strip the first part and calculate the new check digit */
	hyphenate(isn, isn+4, NULL, NULL);
	check = weight_checkdig(isn, 10);
	aux = strchr(isn, '\0');
	while(!isdigit((unsigned char) *--aux));
	if(check == 10) *aux = 'X';
	else *aux = check + '0';
}
static inline
void ean2ISMN(char *isn)
{
	/* the number should come in this format: 979-0-000-00000-0 */
	/* Just strip the first part and change the first digit ('0') to 'M' */
	hyphenate(isn, isn+4, NULL, NULL);
	isn[0] = 'M';
}
static inline
void ean2ISSN(char *isn)
{
	unsigned check;
	/* the number should come in this format: 977-0000-000-00-0 */
	/* Strip the first part, crop, and calculate the new check digit */
	hyphenate(isn, isn+4, NULL, NULL);
	check = weight_checkdig(isn, 8);
	if(check == 10) isn[8] = 'X';
	else isn[8] = check + '0';
	isn[9] = '\0';
}
static inline
void ean2UPC(char *isn)
{
	/* the number should come in this format: 000-000000000-0 */
	/* Strip the first part, crop, and dehyphenate */
	dehyphenate(isn, isn+1);
	isn[12] = '\0';
}

/*
 * ean2* --- Converts a string of digits into an ean13 number.
 *            Assumes the input string is a string with only digits 
 *            on it, and that it's within the range of ean13.
 *
 * Returns the ean13 value of the string.
 */
static
ean13 str2ean(const char *num) 
{
	ean13 ean = 0;	/* current ean */
	while(*num) {
		if(isdigit((unsigned char) *num)) ean = 10 * ean + (*num - '0');
		num++;
	}
    return (ean<<1); /* also give room to a flag */
}

/*
 * ean2string --- Try to convert an ean13 number to an hyphenated string.
 *                Assumes there's enough space in result to hold
 *                the string (maximum MAXEAN13LEN+1 bytes)
 *                This doesn't verify for a valid check digit.
 *
 * If shortType is true, the returned string is in the old ISxN short format.
 * If errorOK is false, ereport a useful error message if the string is bad.
 * If errorOK is true, just return "false" for bad input.
 */
static
bool ean2string(ean13 ean, bool errorOK, char *result, bool shortType)
{
	const char *(*TABLE)[2];
	const unsigned (*TABLE_index)[2];
	enum isn_type type = INVALID;

	char *firstdig, *aux;
	unsigned digval;
	unsigned search;
	char valid = '\0';				/* was the number initially written with a valid check digit? */

	TABLE_index = ISBN_index;

	if((ean & 1)!=0) valid = '!';
	ean >>= 1;
	/* verify it's in the EAN13 range */
	if(ean > UINT64CONST(9999999999999))
		goto eantoobig;

	/* convert the number */
	search = 0;
	firstdig = aux = result + MAXEAN13LEN;
	*aux = '\0';	    /* terminate string; aux points to last digit */
	*--aux = valid;		/* append '!' for numbers with invalid but corrected check digit */
	do {
	    digval = (unsigned)(ean % 10);		/* get the decimal value */ 
	    ean /= 10;												/* get next digit */
		*--aux = (char)(digval + '0');			/* convert to ascii and store */
		if(search == 0) *--aux = '-';			/* the check digit is always there */
	} while(ean && search++<13);
	while(search++<13) *--aux = '0';			/* fill the remaining EAN13 with '0' */

	/* The string should be in this form: ???DDDDDDDDDDDD-D" */
	search = hyphenate(result, result+3, EAN13_range, EAN13_index);
	
	/* verify it's a logically valid EAN13 */
	if(search == 0) {
		search = hyphenate(result, result+3, NULL, NULL);
		goto okay;
	}

	/* find out what type of hyphenation is needed: */
	if(!strncmp("978-", result, search)) { /* ISBN */
		/* The string should be in this form: 978-??000000000-0" */
		type = ISBN;
		TABLE = ISBN_range;
		TABLE_index = ISBN_index;
	} else if(!strncmp("977-", result, search)) { /* ISSN */
		/* The string should be in this form: 977-??000000000-0" */
		type = ISSN;
		TABLE = ISSN_range;
		TABLE_index = ISSN_index;
	} else if(!strncmp("979-0", result, search+1)) { /* ISMN */
		/* The string should be in this form: 979-0?000000000-0" */
		type = ISMN;
		TABLE = ISMN_range;
		TABLE_index = ISMN_index;
	} else if(*result == '0') { /* UPC */
		/* The string should be in this form: 000-00000000000-0" */
		type = UPC;
		TABLE = UPC_range;
		TABLE_index = UPC_index;
	} else {
		type = EAN13;
		TABLE = NULL;
		TABLE_index = NULL;
	}

	/* verify it's a logically valid EAN13/UPC/ISxN */
	digval = search;
	search = hyphenate(result+digval, result+digval+2, TABLE, TABLE_index);

	/* verify it's a valid EAN13 */
	if(search == 0) {
		search = hyphenate(result+digval, result+digval+2, NULL, NULL);
		goto okay;
	}

okay:
	/* convert to the old short type: */
	if(shortType) 
		switch(type) {
			case ISBN:
				ean2ISBN(result);
				break;
			case ISMN:
				ean2ISMN(result);
				break;
			case ISSN:
				ean2ISSN(result);
				break;
			case UPC:
				ean2UPC(result);
				break;
			default:
				break;
		}
	return true;

eantoobig:
	if(!errorOK)
	{
		char	eanbuf[64];

		/*
		 * Format the number separately to keep the machine-dependent
		 * format code out of the translatable message text
		 */
		snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
				 errmsg("value \"%s\" is out of range for %s type",
						eanbuf, isn_names[type])));
	}
	return false;
}

/*
 * string2ean --- try to parse a string into an ean13.
 *
 * If errorOK is false, ereport a useful error message if the string is bad.
 * If errorOK is true, just return "false" for bad input.
 *
 * if the input string ends with '!' it will always be treated as invalid
 * (even if the check digit is valid)
 */
static
bool string2ean(const char *str, bool errorOK, ean13 *result,
				enum isn_type accept)
{
	bool digit, last;
	char buf[17] = "                ";
	char *aux1 = buf + 3; /* leave space for the first part, in case it's needed */
	const char *aux2 = str;
	enum isn_type type = INVALID;
	unsigned check = 0, rcheck = (unsigned)-1;
	unsigned length = 0;
	bool magic = false, valid = true;

	/* recognize and validate the number: */
	while(*aux2 && length <= 13) {
		last = (*(aux2+1) == '!' || *(aux2+1) == '\0'); /* is the last character */
		digit = (isdigit((unsigned char) *aux2)!=0); /* is current character a digit? */
		if(*aux2=='?' && last) /* automagically calculate check digit if it's '?' */
			magic = digit = true;
		if(length == 0 &&  (*aux2=='M' || *aux2=='m')) {
			/* only ISMN can be here */
			if(type != INVALID) goto eaninvalid;
			type = ISMN;
			*aux1++ = 'M';
			length++;
		} else if(length == 7 && (digit || *aux2=='X' || *aux2=='x') && last) {
			/* only ISSN can be here */
			if(type != INVALID) goto eaninvalid;
			type = ISSN;
			*aux1++ = toupper((unsigned char) *aux2);
			length++;
		} else if(length == 9 && (digit || *aux2=='X' || *aux2=='x') && last) {
			/* only ISBN and ISMN can be here */
			if(type != INVALID && type != ISMN) goto eaninvalid;
			if(type == INVALID) type = ISBN; /* ISMN must start with 'M' */
			*aux1++ = toupper((unsigned char) *aux2);
			length++;
		} else if(length == 11 && digit && last) {
			/* only UPC can be here */
			if(type != INVALID) goto eaninvalid;
			type = UPC;
			*aux1++ = *aux2;
			length++;
		} else if(*aux2 == '-' || *aux2 == ' ') {
			/* skip, we could validate but I think it's worthless */
		} else if(*aux2 == '!' && *(aux2+1) == '\0') {
			/* the invalid check digit sufix was found, set it */
			if(!magic) valid = false;
			magic = true;
		} else if(!digit) {
			goto eaninvalid;
		} else {
			*aux1++ = *aux2;
			if(++length > 13) goto eantoobig;
		}
		aux2++;
	}
	*aux1 = '\0'; /* terminate the string */

	/* find the current check digit value */
	if(length == 13) {
		/* only EAN13 can be here */
		if(type != INVALID) goto eaninvalid;
		type = EAN13;
		check = buf[15]-'0';
	} else if(length == 12) {
		/* only UPC can be here */
		if(type != UPC) goto eaninvalid;
		check = buf[14]-'0';
	} else if(length == 10) {
		if(type != ISBN && type != ISMN) goto eaninvalid;
		if(buf[12] == 'X') check = 10;
		else check = buf[12]-'0';
	} else if(length == 8) {
		if(type != INVALID && type != ISSN) goto eaninvalid;
		type = ISSN;
		if(buf[10] == 'X') check = 10;
		else check = buf[10]-'0';
	} else goto eaninvalid;

	if(type == INVALID) goto eaninvalid;

	/* obtain the real check digit value, validate, and convert to ean13: */ 
	if(accept == EAN13 && type != accept) goto eanwrongtype;
	if(accept != ANY && type != EAN13 && type != accept) goto eanwrongtype;
	switch(type) {
		case EAN13:
			valid = (valid && ((rcheck=checkdig(buf+3, 13)) == check || magic));
			/* now get the subtype of EAN13: */
			if(buf[3] == '0') type = UPC;
			else if(!strncmp("977", buf+3, 3)) type = ISSN;
			else if(!strncmp("978", buf+3, 3)) type = ISBN;
			else if(!strncmp("9790", buf+3, 4)) type = ISMN;
			else if(!strncmp("979", buf+3, 3)) type = ISBN;
			if(accept != EAN13 && accept != ANY && type != accept) goto eanwrongtype;
			break;
		case ISMN:
			strncpy(buf, "9790", 4);  /* this isn't for sure yet, for now ISMN it's only 9790 */
			valid = (valid && ((rcheck=checkdig(buf+3, 10)) == check || magic));
			break;
		case ISBN:
			strncpy(buf, "978", 3);
			valid = (valid && ((rcheck=weight_checkdig(buf+3, 10)) == check || magic));
			break;
		case ISSN:
			strncpy(buf+10, "00", 2); /* append 00 as the normal issue publication code */
			strncpy(buf, "977", 3);
			valid = (valid && ((rcheck=weight_checkdig(buf+3, 8)) == check || magic));
			break;
		case UPC:
			buf[2] = '0';
			valid = (valid && ((rcheck=checkdig(buf+2, 13)) == check || magic));
		default:
			break;
	}

  /* fix the check digit: */
	for(aux1 = buf; *aux1 && *aux1 <= ' '; aux1++);
	aux1[12] = checkdig(aux1, 13) + '0';
	aux1[13] = '\0';
	
	if(!valid && !magic) goto eanbadcheck;

	*result = str2ean(aux1);
	*result |= valid?0:1;
	return true;

eanbadcheck: 
	if(g_weak) { /* weak input mode is activated: */
	  /* set the "invalid-check-digit-on-input" flag */ 
		*result = str2ean(aux1);
		*result |= 1;
	return true;
	}

		if(!errorOK) {
			if(rcheck == (unsigned)-1) {
				ereport(ERROR,
					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
					errmsg("invalid %s number: \"%s\"",
							isn_names[accept], str)));
			} else {
				ereport(ERROR,
					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
					errmsg("invalid check digit for %s number: \"%s\", should be %c",
							isn_names[accept], str, (rcheck==10)?('X'):(rcheck+'0'))));
			}
		}
		return false;

eaninvalid:
	if(!errorOK)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid input syntax for %s number: \"%s\"",
						isn_names[accept], str)));
	return false;

eanwrongtype:
	if(!errorOK)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("cannot cast %s to %s for number: \"%s\"",
						isn_names[type], isn_names[accept], str)));
	return false;

eantoobig:
	if(!errorOK)
		ereport(ERROR,
			(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
			 errmsg("value \"%s\" is out of range for %s type",
					str, isn_names[accept])));
	return false;
}

/*----------------------------------------------------------
 * Exported routines.
 *---------------------------------------------------------*/

void initialize(void)
{
#ifdef ISN_DEBUG
	if(!check_table(EAN13, EAN13_index))
		elog(LOG, "EAN13 failed check");
	if(!check_table(ISBN, ISBN_index))
		elog(LOG, "ISBN failed check");
	if(!check_table(ISMN, ISMN_index))
		elog(LOG, "ISMN failed check");
	if(!check_table(ISSN, ISSN_index))
		elog(LOG, "ISSN failed check");
	if(!check_table(UPC, UPC_index))
		elog(LOG, "UPC failed check");
#endif
	g_initialized = true;
}

/* isn_out
 */
PG_FUNCTION_INFO_V1(isn_out);
Datum
isn_out(PG_FUNCTION_ARGS)
{
	ean13		val = PG_GETARG_EAN13(0);
	char	   *result;
	char		buf[MAXEAN13LEN + 1];

	(void) ean2string(val, false, buf, true);

	result = pstrdup(buf);
	PG_RETURN_CSTRING(result);
}

/* ean13_out
 */
PG_FUNCTION_INFO_V1(ean13_out);
Datum
ean13_out(PG_FUNCTION_ARGS)
{
	ean13		val = PG_GETARG_EAN13(0);
	char	   *result;
	char		buf[MAXEAN13LEN + 1];

	(void) ean2string(val, false, buf, false);

	result = pstrdup(buf);
	PG_RETURN_CSTRING(result);
}

/* ean13_in
 */
PG_FUNCTION_INFO_V1(ean13_in);
Datum
ean13_in(PG_FUNCTION_ARGS)
{
	const char	*str = PG_GETARG_CSTRING(0);
	ean13		result;

	(void) string2ean(str, false, &result, EAN13);
	PG_RETURN_EAN13(result);
}

/* isbn_in
 */
PG_FUNCTION_INFO_V1(isbn_in);
Datum
isbn_in(PG_FUNCTION_ARGS)
{
	const char	*str = PG_GETARG_CSTRING(0);
	ean13		result;

	(void) string2ean(str, false, &result, ISBN);
	PG_RETURN_EAN13(result);
}

/* ismn_in
 */
PG_FUNCTION_INFO_V1(ismn_in);
Datum
ismn_in(PG_FUNCTION_ARGS)
{
	const char	*str = PG_GETARG_CSTRING(0);
	ean13		result;

	(void) string2ean(str, false, &result, ISMN);
	PG_RETURN_EAN13(result);
}

/* issn_in
 */
PG_FUNCTION_INFO_V1(issn_in);
Datum
issn_in(PG_FUNCTION_ARGS)
{
	const char	*str = PG_GETARG_CSTRING(0);
	ean13		result;

	(void) string2ean(str, false, &result, ISSN);
	PG_RETURN_EAN13(result);
}

/* upc_in
 */
PG_FUNCTION_INFO_V1(upc_in);
Datum
upc_in(PG_FUNCTION_ARGS)
{
	const char	*str = PG_GETARG_CSTRING(0);
	ean13		result;

	(void) string2ean(str, false, &result, UPC);
	PG_RETURN_EAN13(result);
}

/* casting functions
*/
PG_FUNCTION_INFO_V1(ean13_cast_to_text);
Datum
ean13_cast_to_text(PG_FUNCTION_ARGS)
{
	ean13		val = PG_GETARG_EAN13(0);
	char		buf[MAXEAN13LEN + 1];

	(void) ean2string(val, false, buf, false);

    PG_RETURN_TEXT_P(GET_TEXT(buf));
}

PG_FUNCTION_INFO_V1(isn_cast_to_text);
Datum
isn_cast_to_text(PG_FUNCTION_ARGS)
{
	ean13		val = PG_GETARG_EAN13(0);
	char		buf[MAXEAN13LEN + 1];

	(void) ean2string(val, false, buf, true);

    PG_RETURN_TEXT_P(GET_TEXT(buf));
}

PG_FUNCTION_INFO_V1(isbn_cast_from_ean13);
Datum
isbn_cast_from_ean13(PG_FUNCTION_ARGS)
{
	ean13		val = PG_GETARG_EAN13(0);
	ean13		result;

	(void) ean2isn(val, false, &result, ISBN);

	PG_RETURN_EAN13(result);
}

PG_FUNCTION_INFO_V1(ismn_cast_from_ean13);
Datum
ismn_cast_from_ean13(PG_FUNCTION_ARGS)
{
	ean13		val = PG_GETARG_EAN13(0);
	ean13		result;

	(void) ean2isn(val, false, &result, ISMN);

	PG_RETURN_EAN13(result);
}

PG_FUNCTION_INFO_V1(issn_cast_from_ean13);
Datum
issn_cast_from_ean13(PG_FUNCTION_ARGS)
{
	ean13		val = PG_GETARG_EAN13(0);
	ean13		result;

	(void) ean2isn(val, false, &result, ISSN);

	PG_RETURN_EAN13(result);
}

PG_FUNCTION_INFO_V1(upc_cast_from_ean13);
Datum
upc_cast_from_ean13(PG_FUNCTION_ARGS)
{
	ean13		val = PG_GETARG_EAN13(0);
	ean13		result;

	(void) ean2isn(val, false, &result, UPC);

	PG_RETURN_EAN13(result);
}


PG_FUNCTION_INFO_V1(ean13_cast_from_text);
Datum
ean13_cast_from_text(PG_FUNCTION_ARGS)
{
	const char	*str = GET_STR(PG_GETARG_TEXT_P(0));
	ean13		result;

	(void) string2ean(str, false, &result, EAN13);
	PG_RETURN_EAN13(result);
}

PG_FUNCTION_INFO_V1(isbn_cast_from_text);
Datum
isbn_cast_from_text(PG_FUNCTION_ARGS)
{
	const char	*str = GET_STR(PG_GETARG_TEXT_P(0));
	ean13		result;

	(void) string2ean(str, false, &result, ISBN);
	PG_RETURN_EAN13(result);
}

PG_FUNCTION_INFO_V1(ismn_cast_from_text);
Datum
ismn_cast_from_text(PG_FUNCTION_ARGS)
{
	const char	*str = GET_STR(PG_GETARG_TEXT_P(0));
	ean13		result;

	(void) string2ean(str, false, &result, ISMN);
	PG_RETURN_EAN13(result);
}

PG_FUNCTION_INFO_V1(issn_cast_from_text);
Datum
issn_cast_from_text(PG_FUNCTION_ARGS)
{
	const char	*str = GET_STR(PG_GETARG_TEXT_P(0));
	ean13		result;

	(void) string2ean(str, false, &result, ISSN);
	PG_RETURN_EAN13(result);
}

PG_FUNCTION_INFO_V1(upc_cast_from_text);
Datum
upc_cast_from_text(PG_FUNCTION_ARGS)
{
	const char	*str = GET_STR(PG_GETARG_TEXT_P(0));
	ean13		result;

	(void) string2ean(str, false, &result, UPC);
	PG_RETURN_EAN13(result);
}

/* is_valid - returns false if the "invalid-check-digit-on-input" is set
 */
PG_FUNCTION_INFO_V1(is_valid);
Datum
is_valid(PG_FUNCTION_ARGS)
{
	ean13 val = PG_GETARG_EAN13(0);
	PG_RETURN_BOOL((val & 1) == 0);
}

/* make_valid - unsets the "invalid-check-digit-on-input" flag
 */
PG_FUNCTION_INFO_V1(make_valid);
Datum
make_valid(PG_FUNCTION_ARGS)
{
	ean13 val = PG_GETARG_EAN13(0);
	val &= ~((ean13) 1);
	PG_RETURN_EAN13(val);
}

#ifdef ISN_WEAK_MODE
/* this function temporarily sets weak input flag 
 * (to lose the strictness of check digit acceptance)
 * It's a helper function, not intended to be used!!
 */
PG_FUNCTION_INFO_V1(accept_weak_input);
Datum
accept_weak_input(PG_FUNCTION_ARGS)
{
	g_weak = PG_GETARG_BOOL(0);
	PG_RETURN_BOOL(g_weak);
}
#else
PG_FUNCTION_INFO_V1(accept_weak_input);
Datum
accept_weak_input(PG_FUNCTION_ARGS)
{
	/* function has no effect */
	PG_RETURN_BOOL(false);
}
#endif /* ISN_WEAK_MODE */

PG_FUNCTION_INFO_V1(weak_input_status);
Datum
weak_input_status(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(g_weak);
}
