/*
******************************************************************************
*
*   Copyright (C) 1999-2001, International Business Machines
*   Corporation and others.  All Rights Reserved.
*
******************************************************************************
*
*
*  ucnv_io.c:
*  initializes global variables and defines functions pertaining to file access,
*  and name resolution aspect of the library.
*
*   new implementation:
*
*   created on: 1999nov22
*   created by: Markus W. Scherer
*
*   Use the binary cnvalias.dat (created from convrtrs.txt) to work
*   with aliases for converter names.
*******************************************************************************
*/

#include "unicode/utypes.h"
#include "unicode/putil.h"
#include "unicode/ucnv.h"           /* This file implements ucnv_xXXX() APIs */
#include "umutex.h"
#include "cstring.h"
#include "cmemory.h"
#include "ucnv_io.h"
#include "unicode/udata.h"
#include "ucln_cmn.h"

/* Format of cnvalias.dat ------------------------------------------------------
 *
 * cnvalias.dat is a binary, memory-mappable form of convrtrs.txt .
 * It contains two sorted tables and a block of zero-terminated strings.
 * Each table is preceded by the number of table entries.
 *
 * The first table maps from aliases to converter indexes.
 * The converter names themselves are listed as aliases in this table.
 * Each entry in this table has an offset to the alias and
 * an index of the converter in the converter table.
 *
 * The second table lists only the converters themselves.
 * Each entry in this table has an offset to the converter name and
 * the number of aliases, including the converter itself.
 * A count of 1 means that there is no alias, only the converter name.
 *
 * In the block of strings after the tables, each converter name is directly
 * followed by its aliases. All offsets to strings are offsets from the
 * beginning of the data.
 *
 * More formal file data structure (data format 2.1):
 *
 * uint16_t aliasCount;
 * uint16_t aliasOffsets[aliasCount];
 * uint16_t converterIndexes[aliasCount];
 *
 * uint16_t converterCount;
 * struct {
 *     uint16_t converterOffset;
 *     uint16_t aliasCount;
 * } converters[converterCount];
 *
 * uint16_t tagCount;
 * uint16_t taggedAliasesOffsets[tagCount][converterCount];
 * char tags[] = { "Tag0\Tag1\0..." };
 *
 * char strings[]={
 *     "Converter0\0Alias1\0Alias2\0...Converter1\0Converter2\0Alias0\Alias1\0..."
 * };
 *
 * The code included here can read versions 2 and 2.1 of the data format.
 * Version 2 does not have tag information, but since the code never refers
 * to strings[] by its base offset, it's okay.
 *
 */

static const char DATA_NAME[] = "cnvalias";
static const char DATA_TYPE[] = "dat";

static UDataMemory *aliasData=NULL;
static const uint16_t *aliasTable=NULL;

static const uint16_t *converterTable = NULL;
static const uint16_t *tagTable = NULL;

static char defaultConverterNameBuffer[100];
static const char *defaultConverterName = NULL;

static UBool
isAcceptable(void *context,
             const char *type, const char *name,
             const UDataInfo *pInfo) {
    return (UBool)(
        pInfo->size>=20 &&
        pInfo->isBigEndian==U_IS_BIG_ENDIAN &&
        pInfo->charsetFamily==U_CHARSET_FAMILY &&
        pInfo->dataFormat[0]==0x43 &&   /* dataFormat="CvAl" */
        pInfo->dataFormat[1]==0x76 &&
        pInfo->dataFormat[2]==0x41 &&
        pInfo->dataFormat[3]==0x6c &&
        pInfo->formatVersion[0]==2);
}

static UBool
haveAliasData(UErrorCode *pErrorCode) {
    if(pErrorCode==NULL || U_FAILURE(*pErrorCode)) {
        return FALSE;
    }

    /* load converter alias data from file if necessary */
    if(aliasData==NULL) {
        UDataMemory *data;
        UDataInfo info;
        const uint16_t *table=NULL;

        /* open the data outside the mutex block */
        data=udata_openChoice(NULL, DATA_TYPE, DATA_NAME, isAcceptable, NULL, pErrorCode);
        if(U_FAILURE(*pErrorCode)) {
            return FALSE;
        }

        table=(const uint16_t *)udata_getMemory(data);
        info.size=sizeof(UDataInfo);
        udata_getInfo(data, &info);

        /* in the mutex block, set the data for this process */
        umtx_lock(NULL);
        if(aliasData==NULL) {
            aliasData=data;
            data=NULL;
            aliasTable=table;
            table=NULL;
            converterTable = aliasTable + 1 + 2 * *aliasTable;

            if (info.formatVersion[0] == 2 && info.formatVersion[1] > 0) {
                tagTable = converterTable + 1 + 2 * *converterTable;
            }
        }
        umtx_unlock(NULL);

        /* if a different thread set it first, then close the extra data */
        if(data!=NULL) {
            udata_close(data); /* NULL if it was set correctly */
        }
    }

    return TRUE;
}

static UBool
isAlias(const char *alias, UErrorCode *pErrorCode) {
    if(alias==NULL) {
        *pErrorCode=U_ILLEGAL_ARGUMENT_ERROR;
        return FALSE;
    } else if(*alias==0) {
        return FALSE;
    } else {
        return TRUE;
    }
}

UBool 
ucnv_io_cleanup()
{
    if (aliasData) {
        udata_close(aliasData);
    }

    aliasData = NULL;
    aliasTable = NULL;

    converterTable = NULL;
    tagTable = NULL;

    defaultConverterName = NULL;

    return TRUE;                   /* Everything was cleaned up */
}


static int16_t getTagNumber(const char *tagname) {
    if (tagTable) {
        int16_t tag, count = (int16_t) *tagTable;
        const char *tags = (const char *) (tagTable + 1 + count * *converterTable);

#if 0

        char name[100];
        int i;

        /* convert the tag name to lowercase to do case-insensitive comparisons */
        for(i = 0; i < sizeof(name) - 1 && *tagname; ++i) {
            name[i] = (char)uprv_tolower(*tagname++);
        }
        name[i] = 0;

#else

        const char *name = tagname;

#endif

        for (tag = 0; count--; ++tag) {
            if (!uprv_stricmp(name, tags)) {
                return tag;
            }
            tags += strlen(tags) + 1;
        }
    }

    return -1;
}

/**
 * Do a fuzzy compare of a two converter/alias names.  The comparison
 * is case-insensitive.  It also ignores the characters '-', '_', and
 * ' ' (dash, underscore, and space).  Thus the strings "UTF-8",
 * "utf_8", and "Utf 8" are exactly equivalent.
 * 
 * This is a symmetrical (commutative) operation; order of arguments
 * is insignificant.  This is an important property for sorting the
 * list (when the list is preprocessed into binary form) and for
 * performing binary searches on it at run time.
 * 
 * @param name1 a converter name or alias, zero-terminated
 * @param name2 a converter name or alias, zero-terminated
 * @return 0 if the names match, or a negative value if the name1
 * lexically precedes name2, or a positive value if the name1
 * lexically follows name2.
 */
U_CAPI int U_EXPORT2
ucnv_compareNames(const char *name1, const char *name2) {
    int rc;
    unsigned char c1, c2;

    for (;;) {
        /* Ignore delimiters '-', '_', and ' ' */
        while ((c1 = (unsigned char)*name1) == '-'
               || c1 == '_' || c1 == ' ') ++name1;
        while ((c2 = (unsigned char)*name2) == '-'
               || c2 == '_' || c2 == ' ') ++name2;

        /* If we reach the ends of both strings then they match */
        if ((c1|c2)==0) {
            return 0;
        }
        
        /* Case-insensitive comparison */
        rc = (int)(unsigned char)uprv_tolower(c1) -
             (int)(unsigned char)uprv_tolower(c2);
        if (rc!=0) {
            return rc;
        }
        ++name1;
        ++name2;
    }
}

/*
 * search for an alias
 * return NULL or a pointer to the converter table entry
 */
static const uint16_t *
findAlias(const char *alias) {
    char name[100];
    const uint16_t *p=aliasTable;
    uint16_t i, start, limit;

    limit=*p++;
    if(limit==0) {
        /* there are no aliases */
        return NULL;
    }

    /* convert the alias name to lowercase to do case-insensitive comparisons */
    for(i=0; i<sizeof(name)-1 && *alias!=0; ++i) {
        name[i]=(char)uprv_tolower(*alias++);
    }
    name[i]=0;

    /* do a binary search for the alias */
    start=0;
    while(start<limit-1) {
        i=(uint16_t)((start+limit)/2);
        if(ucnv_compareNames(name, (const char *)aliasTable+p[i])<0) {
            limit=i;
        } else {
            start=i;
        }
    }

    /* did we really find it? */
    if(ucnv_compareNames(name, (const char *)aliasTable+p[start])==0) {
        limit=*(p-1);       /* aliasCount */
        p+=limit;           /* advance to the second column of the alias table */
        i=p[start];         /* converter index */
        return
            p+limit+        /* beginning of converter table */
            1+              /* skip its count */
            2*i;            /* go to this converter's entry and return a pointer to it */
    } else {
        return NULL;
    }
}

U_CFUNC const char *
ucnv_io_getConverterName(const char *alias, UErrorCode *pErrorCode) {
    if(haveAliasData(pErrorCode) && isAlias(alias, pErrorCode)) {
        const uint16_t *p=findAlias(alias);
        if(p!=NULL) {
            return (const char *)aliasTable+*p;
        }
    }
    return NULL;
}

U_CFUNC uint16_t
ucnv_io_getAliases(const char *alias, const char **aliases, UErrorCode *pErrorCode) {
    if(haveAliasData(pErrorCode) && isAlias(alias, pErrorCode)) {
        const uint16_t *p=findAlias(alias);
        if(p!=NULL) {
            *aliases=(const char *)aliasTable+*p;
            return *(p+1);
        }
    }
    return 0;
}

U_CFUNC const char *
ucnv_io_getAlias(const char *alias, uint16_t n, UErrorCode *pErrorCode) {
    if(haveAliasData(pErrorCode) && isAlias(alias, pErrorCode)) {
        const uint16_t *p=findAlias(alias);
        if(p!=NULL) {
            uint16_t count=*(p+1);
            if(n<count) {
                const char *aliases=(const char *)aliasTable+*p;
                while(n>0) {
                    /* skip a name, first the canonical converter name */
                    aliases+=uprv_strlen(aliases)+1;
                    --n;
                }
                return aliases;
            }
        }
    }
    return NULL;
}

U_CFUNC uint16_t
ucnv_io_countStandards(UErrorCode *pErrorCode) {
    if (haveAliasData(pErrorCode)) {
        if (!tagTable) {
            *pErrorCode = U_INVALID_FORMAT_ERROR;
            return 0;
        }

        return *tagTable;
    }

    return 0;
}

U_CAPI const char * U_EXPORT2
ucnv_getStandard(uint16_t n, UErrorCode *pErrorCode) {
    if (haveAliasData(pErrorCode) && tagTable) {
        int16_t count = (int16_t) *tagTable;
        const char *tags = (const char *) (tagTable + 1 + count * *converterTable);

        while (n-- && count--) {
            tags += strlen(tags) + 1;
        }

        return count ? tags : NULL;
    }

    return NULL;
}

U_CFUNC const char * U_EXPORT2
ucnv_getStandardName(const char *alias, const char *standard, UErrorCode *pErrorCode) {
    if (haveAliasData(pErrorCode) && isAlias(alias, pErrorCode)) {
        const uint16_t *p = findAlias(alias);
        if(p != NULL) {
            int16_t tag = getTagNumber(standard);

            if (tag > -1) {
                uint16_t offset = tagTable[1 + tag * *converterTable + (p - converterTable) / 2];
                return offset ? (const char *) aliasTable + offset : NULL;
            }
        }
    }

   return NULL;
}

U_CFUNC uint16_t
ucnv_io_countAvailableConverters(UErrorCode *pErrorCode) {
    if(haveAliasData(pErrorCode)) {
        return *converterTable;
    }
    return 0;
}

U_CFUNC const char *
ucnv_io_getAvailableConverter(uint16_t n, UErrorCode *pErrorCode) {
    if(haveAliasData(pErrorCode)) {
        const uint16_t *p=converterTable;
        if(n<*p) {
            return (const char *)aliasTable+p[1+2*n];
        }
    }
    return NULL;
}

U_CFUNC void
ucnv_io_fillAvailableConverters(const char **aliases, UErrorCode *pErrorCode) {
    if(haveAliasData(pErrorCode)) {
        const uint16_t *p=converterTable;
        uint16_t count=*p++;
        while(count>0) {
            *aliases++=(const char *)aliasTable+*p;
            p+=2;
            --count;
        }
    }
}

U_CFUNC uint16_t
ucnv_io_countAvailableAliases(UErrorCode *pErrorCode) {
    if(haveAliasData(pErrorCode)) {
        return *aliasTable;
    }
    return 0;
}

#if 0
/*
 * We are not currently using these functions, so I am commenting them out
 * to reduce the binary file size and improve the code coverage;
 * I do not currently want to remove this entirely because it may be useful
 * in the future and also serves to some degree as another piece of
 * documentation of the data structure.
 */
U_CFUNC const char *
ucnv_io_getAvailableAlias(uint16_t n, UErrorCode *pErrorCode) {
    if(haveAliasData(pErrorCode) && n<*aliasTable) {
        return (const char *)aliasTable+*(aliasTable+1+n);
    }
    return NULL;
}

U_CFUNC void
ucnv_io_fillAvailableAliases(const char **aliases, UErrorCode *pErrorCode) {
    if(haveAliasData(pErrorCode)) {
        const uint16_t *p=aliasTable;
        uint16_t count=*p++;
        while(count>0) {
            *aliases++=(const char *)aliasTable+*p;
            ++p;
            --count;
        }
    }
}
#endif

/* default converter name --------------------------------------------------- */

/*
 * In order to be really thread-safe, the get function would have to take
 * a buffer parameter and copy the current string inside a mutex block.
 * This implementation only tries to be really thread-safe while
 * setting the name.
 * It assumes that setting a pointer is atomic.
 */

U_CFUNC const char *
ucnv_io_getDefaultConverterName() {
    /* local variable to be thread-safe */
    const char *name=defaultConverterName;
    if(name==NULL) {
        const char *codepage=0;
        umtx_lock(NULL);        
        codepage = uprv_getDefaultCodepage();
        umtx_unlock(NULL);
        if(codepage!=NULL) {
            UErrorCode errorCode=U_ZERO_ERROR;
            name=ucnv_io_getConverterName(codepage, &errorCode);
            if(U_FAILURE(errorCode) || name==NULL) {
                name=codepage;
            }
        }

        /* if the name is there, test it out */
        if(name != NULL) {
          UErrorCode errorCode = U_ZERO_ERROR;
          UConverter *cnv;
          cnv = ucnv_open(name, &errorCode);
          if(U_FAILURE(errorCode) || (cnv == NULL)) {
            /* Panic time, let's use a fallback. */
#if (U_CHARSET_FAMILY == U_ASCII_FAMILY) 
            name = "US-ASCII";
            /* there is no 'algorithmic' converter for EBCDIC */
#elif defined(OS390)
            name = "ibm-1047-s390";
#else
            name = "ibm-37";
#endif
          }
          ucnv_close(cnv);
        }

        if(name != NULL) {
           /* Did find a name. And it works.*/
          defaultConverterName=name;
        }
    }

    return name;
}

U_CFUNC void
ucnv_io_setDefaultConverterName(const char *converterName) {
    if(converterName==NULL) {
        /* reset to the default codepage */
        defaultConverterName=NULL;
    } else {
        UErrorCode errorCode=U_ZERO_ERROR;
        const char *name=ucnv_io_getConverterName(converterName, &errorCode);
        if(U_SUCCESS(errorCode) && name!=NULL) {
            defaultConverterName=name;
        } else {
            /* do not set the name if the alias lookup failed and it is too long */
            int32_t length=uprv_strlen(converterName);
            if(length<sizeof(defaultConverterNameBuffer)) {
                /* it was not found as an alias, so copy it - accept an empty name */
                UBool didLock;
                if(defaultConverterName==defaultConverterNameBuffer) {
                    umtx_lock(NULL);
                    didLock=TRUE;
                } else {
                    didLock=FALSE;
                }
                uprv_memcpy(defaultConverterNameBuffer, converterName, length);
                defaultConverterNameBuffer[length]=0;
                defaultConverterName=defaultConverterNameBuffer;
                if(didLock) {
                    umtx_unlock(NULL);
                }
            }
        }
    }
}

/*
 * Hey, Emacs, please set the following:
 *
 * Local Variables:
 * indent-tabs-mode: nil
 * End:
 *
 */