// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
******************************************************************************
*
*   Copyright (C) 1998-2014, International Business Machines
*   Corporation and others.  All Rights Reserved.
*
******************************************************************************
*
* File ufmt_cmn.c
*
* Modification History:
*
*   Date        Name        Description
*   12/02/98    stephen     Creation.
*   03/12/99    stephen     Modified for new C API.
*   03/15/99    stephen     Added defaultCPToUnicode, unicodeToDefaultCP
*   07/19/99    stephen     Fixed bug in defaultCPToUnicode
******************************************************************************
*/

#include "cstring.h"
#include "cmemory.h"
#include "ufmt_cmn.h"
#include "unicode/uchar.h"
#include "unicode/ucnv.h"
#include "ustr_cnv.h"

#if !UCONFIG_NO_CONVERSION


#define DIGIT_0     0x0030
#define DIGIT_9     0x0039
#define LOWERCASE_A 0x0061
#define UPPERCASE_A 0x0041
#define LOWERCASE_Z 0x007A
#define UPPERCASE_Z 0x005A

int
ufmt_digitvalue(char16_t c)
{
    if( ((c>=DIGIT_0)&&(c<=DIGIT_9)) ||
        ((c>=LOWERCASE_A)&&(c<=LOWERCASE_Z)) ||
        ((c>=UPPERCASE_A)&&(c<=UPPERCASE_Z))  )
    {
      return c - DIGIT_0 - (c >= 0x0041 ? (c >= 0x0061 ? 39 : 7) : 0);
    }
    else
    {
      return -1;
    }
}

UBool
ufmt_isdigit(char16_t  c,
             int32_t     radix)
{
    int digitVal = ufmt_digitvalue(c);

    return digitVal < radix && digitVal >= 0;
}

#define TO_UC_DIGIT(a) a <= 9 ? (DIGIT_0 + a) : (0x0037 + a)
#define TO_LC_DIGIT(a) a <= 9 ? (DIGIT_0 + a) : (0x0057 + a)

void 
ufmt_64tou(char16_t  *buffer,
          int32_t   *len,
          uint64_t  value, 
          uint8_t  radix,
          UBool     uselower,
          int32_t   minDigits)
{
    int32_t  length = 0;
    uint32_t digit;
    char16_t *left, *right, temp;
    
    do {
        digit = static_cast<uint32_t>(value % radix);
        value = value / radix;
        buffer[length++] = static_cast<char16_t>(uselower ? TO_LC_DIGIT(digit)
            : TO_UC_DIGIT(digit));
    } while(value);

    /* pad with zeroes to make it minDigits long */
    if(minDigits != -1 && length < minDigits) {
        while(length < minDigits && length < *len)
            buffer[length++] = DIGIT_0;  /*zero padding */
    }

    /* reverse the buffer */
    left     = buffer;
    right = buffer + length;
    while(left < --right) {
        temp     = *left;
        *left++     = *right;
        *right     = temp;
    }
    
    *len = length;
}

void 
ufmt_ptou(char16_t *buffer,
          int32_t   *len,
          void      *value, 
          UBool     uselower)
{
    int32_t i;
    int32_t length = 0;
    uint8_t* ptrIdx = reinterpret_cast<uint8_t*>(&value);

#if U_IS_BIG_ENDIAN
    for (i = 0; i < (int32_t)sizeof(void *); i++)
#else
    for (i = static_cast<int32_t>(sizeof(void*)) - 1; i >= 0; i--)
#endif
    {
        uint8_t byteVal = ptrIdx[i];
        uint16_t firstNibble = static_cast<uint16_t>(byteVal >> 4);
        uint16_t secondNibble = static_cast<uint16_t>(byteVal & 0xF);
        if (uselower) {
            buffer[length++]=TO_LC_DIGIT(firstNibble);
            buffer[length++]=TO_LC_DIGIT(secondNibble);
        }
        else {
            buffer[length++]=TO_UC_DIGIT(firstNibble);
            buffer[length++]=TO_UC_DIGIT(secondNibble);
        }
    }

    *len = length;
}

int64_t
ufmt_uto64(const char16_t  *buffer,
          int32_t     *len,
          int8_t     radix)
{
    const char16_t  *limit;
    int32_t         count;
    uint64_t        result;
    
    
    /* initialize parameters */
    limit     = buffer + *len;
    count     = 0;
    result    = 0;
    
    /* iterate through buffer */
    while(ufmt_isdigit(*buffer, radix) && buffer < limit) {
        
        /* read the next digit */
        result *= radix;
        result += ufmt_digitvalue(*buffer++);
        
        /* increment our count */
        ++count;
    }
    
    *len = count;
    return static_cast<int64_t>(result);
}

#define NIBBLE_PER_BYTE 2
void *
ufmt_utop(const char16_t  *buffer,
          int32_t     *len)
{
    int32_t count, resultIdx, incVal, offset;
    /* This union allows the pointer to be written as an array. */
    union {
        void *ptr;
        uint8_t bytes[sizeof(void*)];
    } result;
    
    /* initialize variables */
    count      = 0;
    offset     = 0;
    result.ptr = nullptr;

    /* Skip the leading zeros */
    while(buffer[count] == DIGIT_0 || u_isspace(buffer[count])) {
        count++;
        offset++;
    }

    /* iterate through buffer, stop when you hit the end */
    while(count < *len && ufmt_isdigit(buffer[count], 16)) {
        /* increment the count consumed */
        ++count;
    }

    /* detect overflow */
    if (count - offset > static_cast<int32_t>(sizeof(void*) * NIBBLE_PER_BYTE)) {
        offset = count - static_cast<int32_t>(sizeof(void*) * NIBBLE_PER_BYTE);
    }
    
    /* Initialize the direction of the input */
#if U_IS_BIG_ENDIAN
    incVal = -1;
    resultIdx = (int32_t)(sizeof(void*) - 1);
#else
    incVal = 1;
    resultIdx = 0;
#endif
    /* Write how much was consumed. */
    *len = count;
    while(--count >= offset) {
        /* Get the first nibble of the byte */
        uint8_t byte = static_cast<uint8_t>(ufmt_digitvalue(buffer[count]));

        if (count > offset) {
            /* Get the second nibble of the byte when available */
            byte = static_cast<uint8_t>(byte + (ufmt_digitvalue(buffer[--count]) << 4));
        }
        /* Write the byte into the array */
        result.bytes[resultIdx] = byte;
        resultIdx += incVal;
    }

    return result.ptr;
}

char16_t*
ufmt_defaultCPToUnicode(const char *s, int32_t sSize,
                        char16_t *target, int32_t tSize)
{
    char16_t *alias;
    UErrorCode status = U_ZERO_ERROR;
    UConverter *defConverter = u_getDefaultConverter(&status);

    if (U_FAILURE(status) || defConverter == nullptr)
        return nullptr;

    if(sSize <= 0) {
        sSize = static_cast<int32_t>(uprv_strlen(s)) + 1;
    }
    
    /* perform the conversion in one swoop */
    if (target != nullptr) {
        alias = target;
        ucnv_toUnicode(defConverter, &alias, alias + tSize, &s, s + sSize - 1, 
            nullptr, true, &status);
        
        
        /* add the null terminator */
        *alias = 0x0000;
    }
    
    u_releaseDefaultConverter(defConverter);
    
    return target;
}


#endif
