/*
   <p>Function to return country information.</p>

   <kbd>
   <b>environ - C programming support functions</b><br>
   Copyright (C) 2005  Martin C Gregorie
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public License.
   
   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 Lesser 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.
   </kbd>
  
   <p>Contact Martin Gregorie at <i>martin@gregorie.org</i></p>
*/

#include <stdio.h>
#include <ctype.h>
#include "environ.h"

#ifdef OS9
#include <strings.h>
#else
#include <string.h>
#endif

#define _NAME   progname()
#define _FILE   "/usr/local/etc/country"
#define _SEP    ':'
#define _EQUALS '='
#define _CONT   '\\'

char* _keyname[] = {    "datefmt", "datesep", 
                        "currsym", "currdec", "currsty",
                        "thousep", "decpt",
                        "timefmt", "timesep",
                        "casemap",
                        "datasep",
                        "",
                        "junk" };
int   _strlen[] = {     1, 1,
                        4, 1, 1,
                        1, 1,
                        1, 1,
                        18,
                        1,
                        MAXLINE,
                        MAXLINE };
#define _DATEFMT    0
#define _DATESEP    1
#define _CURRSYM    2
#define _CURRDEC    3
#define _CURRSTY    4
#define _THOUSEP    5
#define _DECPT      6
#define _TIMEFMT    7
#define _TIMESEP    8
#define _CASEMAP    9
#define _DATASEP    10
#define _EMPTY      11
#define _JUNK       12
#define _LIMIT      13

/*
   <h3>The <i>country()</i> function</h3>
   
   <i>country()</i> provides a degree of international independance to a
   program. It started as an attempt to implement the Borland DOS
   <i>country()</i> function for OS/9. The result was subsequently
   ported to Linux. This explains the interface and omissions from the
   information returned, as these exactly match the Borland definition.
   The implementation is derived from the <i>termcap</i> system.

   If <i>country()</i> is called with <u>xcode</u>=0 the default country
   will be processed. If it is called with <u>cp</u> set to -1 the
   contents of <u>xcode</u> becomes the default for the rest of the
   program run. Calls with a pointer to the struct and a non-zero country
   code access details for the requested country.

   Information from the database is returned in the following structure:
   <pre>
   struct  COUNTRY {
      int  co_date;         0 - US format: MM.DD.CCYY
                            1 - European:  DD.MM.CCYY
                            2 - Japanese:  CCYY.MM.DD 
      char co_curr[5];      The currency symbol. Up to 4 characters. 
      char co_thsep[2];     The symbol used to delimit thousands. 
      char co_desep[2];     The symbol used for a decimal point. 
      char co_dtsep[2];     The separator used within dates. 
      char co_tmsep[2];     The separator used within times. 
      char co_currstyle;    0 - symbol preceeds value w/o gap.
                            1 - symbol follows value w/o gap.
                            2 - symbol preceeds value w/space between.
                            3 - symbol follows value w/space between. 
      char co_digits;       1-3        Number of decimal places 
      char co_time;         0 = 12 hour time format: HH.MM.SS am
                            1 = 24 hour time format: HH.MM.SS 
      long co_case;         not used - conformity with Borland C 
      char co_dasep[2];     The character used in delimited data files
                            to separate fields. Usually a comma. 
      char co_fill[10];
   };
   </pre>

   <h3>The country database</h3>

   National information is held in a database file.
   This is <kbd>/dd/sys/country</kbd> in the OS-9 implementation
   and <kbd>/usr/local/etc/country</kbd> in the Linux implementation. 

   If the database cannot be found <i>country()</i> returns values for
   the UK and will do so regardless of the country code input.

   If the database file does exist it must be correctly formatted and the
   first line in the file that is not blank or a comment must be the
   default country definition, e.g.
        
   <kbd>default=44</kbd>

   Any line starting with an asterisk is treated as a comment.

   The order of other entries is not important; a country data
   definition block must be correctly formatted. A missed default
   definition, a reference to a missing country, or a malformed
   country definition block will all cause errors. A country data
   block has the following format:

   <kbd>dialcode:[keyword=value:]...</kbd>

   where <b>dialcode</b> is the international dialing code for the country.
   Each data block must be contiguous, must not contain comments or
   blank lines, and must be separated from the next data block by at
   least one blank line or comment. The minimum UK/US database is:
   <pre>
   default=44
   *UK
   44:datefmt=1:datesep=/:currsym=x9c:currdec=2:currsty=0:thousep=,:\
      decpt=.:timefmt=1:timesep=::casemap=18222325:datasep=,:
   *USA
   01:datefmt=0:datesep=/:currsym=$:currdec=2:currsty=0:thousep=,:\
      decpt=.:timefmt=0:timesep=::datasep=,:
   </pre>
   The parameters describing the country are all of the form

   <kbd>keyword=value</kbd>

   and may not contain spaces. If necessary, values may be supplied as
   hexadecimal strings, which will be converted to the equivalent 
   character string before validation and length checking. A hex string
   is made of the repeating group 'xhh', e.g. x30x31x32 is '012'.

   The parameters are:
   <pre>
   casemap - Exists for conformity with DOS. Not used by OS/9.
   currdec - 1 - 3 Number of decimal places in the currency.
   currsty - 0 - Currency symbol preceeds value without a gap.
             1 - Currency symbol follows value without a gap.
             2 - Currency symbol preceeds value with a space between.
             3 - Currency symbol follows value with a space between.
   currsym - The currency symbol. Up to 4 characters.
   datasep - The character used in delimited data files to separate
             fields. Usually a comma.
   datefmt - 0 - US format: MM.DD.CCYY
             1 - European:  DD.MM.CCYY
             2 - Japanese:  CCYY.MM.DD
   datesep - The separator used within dates (shown above as '.').
   decpt   - The symbol used for a decimal point
   thousep - The symbol used to delimit thousands.
   timefmt - 0 - 12 hour time format: HH.MM.SS xx (xx = am or pm))
             1 - 24 hour time format: HH.MM.SS
   timesep - The separator used within times (shown above as '.'). 
   </pre>

   <h3>Files</h3>
   OS9: /dd/sys/country<br>
   Linux: /usr/local/etc/country

*/
struct COUNTRY *country(int xcode, struct COUNTRY *cp)
{
    static int  current = 0;
    FILE*       cf;
    char        num[10];
    char        hex[MAXLINE+1];
    char        temp[3];
    char        key[MAXLINE+1];
    char        val[MAXLINE+1];
    int         r;
    int         search;
    int         target;
    int         candidate;
    int         i;
    int         j;
    int         k;
    
    char _getstr(FILE *f, char *buff, char end, int n);
    void _skipspace(FILE *f);

    /*
        Reset country code function
    */
    if (cp == (struct COUNTRY *)-1)
    {
        current = xcode;
        return (NULL);
    }
    
    /*
        Open the file.
    */
    cf = fopen(_FILE,"r");
    if (cf == NULL)
    {
        cp->co_date = 1;            /* no /etc/country         */
        strcpy(cp->co_dtsep,"/");   /* default to UK values    */
        strcpy(cp->co_curr,"£");
        cp->co_digits = 2;
        cp->co_currstyle = 0;
        strcpy(cp->co_thsep,",");
        strcpy(cp->co_desep,".");
        cp->co_time = 1;
        strcpy(cp->co_tmsep,":");
        cp->co_case = 18222325;
        strcpy(cp->co_dasep,",");
        strcpy(cp->co_fill,"");
        
        return (cp);    
    }

    /*
        Locate the 'default=nn' line
    */
    _skipspace(cf);              /* skip initial comments */
    r = _getstr(cf,key,_EQUALS,10);
    if (r != _EQUALS || strcmp("default",key) != 0)
        error(_NAME,_FILE,"format error: no default");
    r = _getstr(cf,num,'\n',10);
    if (!isspace(r))
        error(_NAME,_FILE,"format error: no default country code");

    /*
        decide which country code to go for
    */
    if (current == 0)            /* set the default if necessary */
        current = atoi(num);
    if (xcode)
        target = xcode;
    else
        target = current;
    ltoa(target,num,10);         /* set up chars for error reporting */
    /*
        find the appropriate country parameter set
    */
    search = TRUE;
    while(search)
    {
        _skipspace(cf);        
        r = _getstr(cf,val,_SEP,10);
        if (r == EOF)
            error(_NAME,"can't find country code",num);
        if (r != _SEP)
            error(_NAME,"bad country code format in",_FILE);
        candidate = atoi(val);
        if (candidate == target)
            search = FALSE;
        else
            while(!isspace(_getstr(cf,val,_SEP,MAXLINE)))
                ;
    }
    /*
        Set defaults
    */
    cp->co_date = 0;
    strcpy(cp->co_dtsep,"");
    strcpy(cp->co_curr,"");
    cp->co_digits = 0;
    cp->co_currstyle = 0;
    strcpy(cp->co_thsep,"");
    strcpy(cp->co_desep,"");
    cp->co_time = 0;
    strcpy(cp->co_tmsep,"");
    cp->co_case = 0;
    strcpy(cp->co_dasep,"");
    strcpy(cp->co_fill,"");

    /*
        Fill the data structure
    */
    search = TRUE;
    while (search)
    {
        r = _getstr(cf,key,_EQUALS,MAXLINE);
        if (isspace(r) || r == EOF) /* end of parameter set */
            break;
        if (r != _EQUALS)
            error(_NAME,"malformed keyword in country code",num);
        r = _getstr(cf,val,_SEP,MAXLINE);
        if (r != _SEP)
            error(_NAME,"malformed parameter in country code",num);
        if (strcmp(val,"") == 0)
        {
            strcpy(val,":");        /* just found : */
            r = getc(cf);           /* and loose its terminator */
            if (r != _SEP)
                error(_NAME,"malformed parameter in country code",num);
        }
        if (tolower(val[0]) == 'x') /* This value is hex */
        {
            for (i = 0, j=0; i<strlen(val); i += 3, j++)
            {
                for (k=0; k<3; k++)
                    temp[k] = val[i+1+k];
                temp[2] = 0;
                sscanf(temp,"%02x",&r);
                hex[j] = r;
                hex[j+1] = 0;
            }
            strcpy(val,hex);
        }
        _keyname[_JUNK] = key;
        for (i = 0; i<_LIMIT; i++)
            if(strcmp(_keyname[i],key) == 0)
                break;
        if (strlen(val)>_strlen[i])
            error(_NAME,"parameter too long in country code",num);
        switch (i)
        {
            case _DATEFMT:    cp->co_date = atoi(val); break;
            case _DATESEP:    strcpy(cp->co_dtsep,val); break;
            case _CURRSYM:    strcpy(cp->co_curr,val); break;
            case _CURRDEC:    cp->co_digits = atoi(val); break;
            case _CURRSTY:    cp->co_currstyle = atoi(val); break;
            case _THOUSEP:    strcpy(cp->co_thsep,val); break;
            case _DECPT:      strcpy(cp->co_desep,val); break;
            case _TIMEFMT:    cp->co_time = atoi(val); break;
            case _TIMESEP:    strcpy(cp->co_tmsep,val); break;
            case _CASEMAP:    cp->co_case = atol(val); break;
            case _DATASEP:    strcpy(cp->co_dasep,val); break;
            case _EMPTY:      break;
            default:          error(_NAME,
                                    "unknown parameter in country code",
                                    num);
        }
    }
    fclose(cf);
    return (cp);    
}

char _getstr(FILE *f, char *buff, char end, int n)
{
    int        i;
    int        c;

    for (i=0; i<n; i++)
    {
        c = getc(f);
        if (isspace(c))
            break;
        if (c == _CONT)
        {
            c = getc(f);
            while (isspace(c))
                c = getc(f);
        } 
        if (c == EOF || c == end)
            break;
        *buff++ = c;
    }
    *buff = 0;
    return(c);
}

void _skipspace(FILE *f)
{
    int     c;
    char    junk[MAXLINE+1];
    
    while(c != EOF)    
    {
        c = getc(f);
        if (!isspace(c))
            if (c == '*')        /* Comment line */
            {
                if (fgets(junk,MAXLINE,f) == NULL)
                {
                    c = EOF;
                    break;
                }
            }
            else
                break;
    }
    ungetc(c,f);
}
