/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * $Id: XMLBigDecimal.cpp 568078 2007-08-21 11:43:25Z amassari $
 */

// ---------------------------------------------------------------------------
//  Includes
// ---------------------------------------------------------------------------
#include <xercesc/util/XMLBigDecimal.hpp>
#include <xercesc/util/XMLBigInteger.hpp>
#include <xercesc/util/TransService.hpp>
#include <xercesc/util/NumberFormatException.hpp>
#include <xercesc/util/XMLChar.hpp>
#include <xercesc/util/OutOfMemoryException.hpp>
#include <xercesc/util/Janitor.hpp>

XERCES_CPP_NAMESPACE_BEGIN

/**
 * Constructs a BigDecimal from a string containing an optional (plus | minus)
 * sign followed by a sequence of zero or more decimal digits, optionally
 * followed by a fraction, which consists of a decimal point followed by
 * zero or more decimal digits.  The string must contain at least one
 * digit in the integer or fractional part.  The scale of the resulting
 * BigDecimal will be the number of digits to the right of the decimal
 * point in the string, or 0 if the string contains no decimal point.
 * Any extraneous characters (including whitespace) will result in
 * a NumberFormatException.

 * since parseBigDecimal()  may throw exception,
 * caller of XMLBigDecimal need to catch it.
//
**/

typedef JanitorMemFunCall<XMLBigDecimal>    CleanupType;

XMLBigDecimal::XMLBigDecimal(const XMLCh* const strValue,
                             MemoryManager* const manager)
: fSign(0)
, fTotalDigits(0)
, fScale(0)
, fRawDataLen(0)
, fRawData(0)
, fIntVal(0)
, fMemoryManager(manager)
{
    if ((!strValue) || (!*strValue))
        ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_emptyString, fMemoryManager);

    CleanupType cleanup(this, &XMLBigDecimal::cleanUp);

    try
    {
        fRawDataLen = XMLString::stringLen(strValue);
        fRawData = (XMLCh*) fMemoryManager->allocate
        (
            ((fRawDataLen*2) + 2) * sizeof(XMLCh) //fRawData and fIntVal
        );
        memcpy(fRawData, strValue, fRawDataLen * sizeof(XMLCh));
        fRawData[fRawDataLen] = chNull;
        fIntVal = fRawData + fRawDataLen + 1;
        parseDecimal(strValue, fIntVal, fSign, (int&) fTotalDigits, (int&) fScale, fMemoryManager);
    }
    catch(const OutOfMemoryException&)
    {
        cleanup.release();

        throw;
    }

    cleanup.release();
}

XMLBigDecimal::~XMLBigDecimal()
{
    cleanUp();
}

void XMLBigDecimal::cleanUp()
{
    if (fRawData)
        fMemoryManager->deallocate(fRawData); //XMLString::release(&fRawData);
}

void XMLBigDecimal::setDecimalValue(const XMLCh* const strValue)
{
    fScale = fTotalDigits = 0;
    unsigned int valueLen = XMLString::stringLen(strValue);

    if (valueLen > fRawDataLen)
    {
        fMemoryManager->deallocate(fRawData);
        fRawData = (XMLCh*) fMemoryManager->allocate
        (
            ((valueLen * 2) + 4) * sizeof(XMLCh)
        );//XMLString::replicate(strValue, fMemoryManager);
    }

    memcpy(fRawData, strValue, valueLen * sizeof(XMLCh));
    fRawData[valueLen] = chNull;
    fRawDataLen = valueLen;
    fIntVal = fRawData + fRawDataLen + 1;
    parseDecimal(strValue, fIntVal, fSign, (int&) fTotalDigits, (int&) fScale, fMemoryManager);

}

/***
 * 3.2.3 decimal  
 *
 * . the preceding optional "+" sign is prohibited. 
 * . The decimal point is required. 
 * . Leading and trailing zeroes are prohibited subject to the following: 
 *   there must be at least one digit to the right and to the left of the decimal point which may be a zero.
 *
 ***/
XMLCh* XMLBigDecimal::getCanonicalRepresentation(const XMLCh*         const rawData
                                               ,       MemoryManager* const memMgr)
{

    XMLCh* retBuf = (XMLCh*) memMgr->allocate( (XMLString::stringLen(rawData)+1) * sizeof(XMLCh));
    ArrayJanitor<XMLCh> janName(retBuf, memMgr);
    int   sign, totalDigits, fractDigits;

    try
    {
        parseDecimal(rawData, retBuf, sign, totalDigits, fractDigits, memMgr);
    }
    catch (const NumberFormatException&)
    {
        return 0;
    }


    //Extra space reserved in case strLen is zero
    int    strLen = XMLString::stringLen(retBuf);
    XMLCh* retBuffer = (XMLCh*) memMgr->allocate( (strLen + 4) * sizeof(XMLCh));

    if ( (sign == 0) || (totalDigits == 0))
    {
        retBuffer[0] = chDigit_0;
        retBuffer[1] = chPeriod;
        retBuffer[2] = chDigit_0;
        retBuffer[3] = chNull;
    }
    else
    {
        XMLCh* retPtr = retBuffer;

        if (sign == -1)
        {
            *retPtr++ = chDash;
        }

        if (fractDigits == totalDigits)   // no integer
        {           
            *retPtr++ = chDigit_0;
            *retPtr++ = chPeriod;
            XMLString::copyNString(retPtr, retBuf, strLen);
            retPtr += strLen;
            *retPtr = chNull;
        }
        else if (fractDigits == 0)        // no fraction
        {
            XMLString::copyNString(retPtr, retBuf, strLen);
            retPtr += strLen;
            *retPtr++ = chPeriod;
            *retPtr++ = chDigit_0;
            *retPtr   = chNull;
        }
        else  // normal
        {
            int intLen = totalDigits - fractDigits;
            XMLString::copyNString(retPtr, retBuf, intLen);
            retPtr += intLen;
            *retPtr++ = chPeriod;
            XMLString::copyNString(retPtr, &(retBuf[intLen]), fractDigits);
            retPtr += fractDigits;
            *retPtr = chNull;
        }

    }
            
    return retBuffer;
}

void  XMLBigDecimal::parseDecimal(const XMLCh* const toParse
                               ,        XMLCh* const retBuffer
                               ,        int&         sign
                               ,        int&         totalDigits
                               ,        int&         fractDigits
                               ,        MemoryManager* const manager)
{
    //init
    retBuffer[0] = chNull;
    totalDigits = 0;
    fractDigits = 0;

    // Strip leading white space, if any. 
    const XMLCh* startPtr = toParse;
    while (XMLChar1_0::isWhitespace(*startPtr))
        startPtr++;

    // If we hit the end, then return failure
    if (!*startPtr)
        ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_WSString, manager);

    // Strip tailing white space, if any.
    const XMLCh* endPtr = toParse + XMLString::stringLen(toParse);
    while (XMLChar1_0::isWhitespace(*(endPtr - 1)))
        endPtr--;

    // '+' or '-' is allowed only at the first position
    // and is NOT included in the return parsed string
    sign = 1;
    if (*startPtr == chDash)
    {
        sign = -1;
        startPtr++;
        if (startPtr == endPtr)
        {
            ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars, manager);
        }
    }
    else if (*startPtr == chPlus)
    {
        startPtr++;         
        if (startPtr == endPtr)
        {
            ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars, manager);
        }
    }

    // Strip leading zeros
    while (*startPtr == chDigit_0)
        startPtr++;

    // containning zero, only zero, nothing but zero
    // it is a zero, indeed
    if (startPtr >= endPtr)
    {
        sign = 0;
        return;
    }

    XMLCh* retPtr = (XMLCh*) retBuffer;

    // Scan data
    bool   dotSignFound = false;
    while (startPtr < endPtr)
    {
        if (*startPtr == chPeriod)
        {
            if (!dotSignFound)
            {
                dotSignFound = true;
                fractDigits = endPtr - startPtr - 1;
                startPtr++;
                continue;
            }
            else  // '.' is allowed only once
                ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_2ManyDecPoint, manager);
        }

        // If not valid decimal digit, then an error
        if ((*startPtr < chDigit_0) || (*startPtr > chDigit_9))
            ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars, manager);

        // copy over
        *retPtr++ = *startPtr++;
        totalDigits++;
    }

    /***
    E2-44 totalDigits

     ... by restricting it to numbers that are expressible as i x 10^-n
     where i and n are integers such that |i| < 10^totalDigits and 0 <= n <= totalDigits. 

        normalization: remove all trailing zero after the '.'
                       and adjust the scaleValue as well.
    ***/
    while ((fractDigits > 0) && (*(retPtr-1) == chDigit_0))          
    {
        retPtr--;
        fractDigits--;
        totalDigits--;
    }
    // 0.0 got past the check for zero because of the decimal point, so we need to double check it here
    if(totalDigits==0)
        sign = 0;

    *retPtr = chNull;   //terminated
    return;
}

void  XMLBigDecimal::parseDecimal(const XMLCh*         const toParse
                               ,        MemoryManager* const manager)
{

    // Strip leading white space, if any. 
    const XMLCh* startPtr = toParse;
    while (XMLChar1_0::isWhitespace(*startPtr))
        startPtr++;

    // If we hit the end, then return failure
    if (!*startPtr)
        ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_WSString, manager);

    // Strip tailing white space, if any.
    const XMLCh* endPtr = toParse + XMLString::stringLen(toParse);
    while (XMLChar1_0::isWhitespace(*(endPtr - 1)))
        endPtr--;

    // '+' or '-' is allowed only at the first position
    // and is NOT included in the return parsed string

    if (*startPtr == chDash)
    {
        startPtr++;
        if (startPtr == endPtr)
        {
            ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars, manager);
        }
    }
    else if (*startPtr == chPlus)
    {
        startPtr++;
        if (startPtr == endPtr)
        {
            ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars, manager);
        }
    }

    // Strip leading zeros
    while (*startPtr == chDigit_0)
        startPtr++;

    // containning zero, only zero, nothing but zero
    // it is a zero, indeed
    if (startPtr >= endPtr)
    {
        return;
    }

    // Scan data
    bool   dotSignFound = false;
    while (startPtr < endPtr)
    {
        if (*startPtr == chPeriod)
        {
            if (!dotSignFound)
            {
                dotSignFound = true;
                startPtr++;
                continue;
            }
            else  // '.' is allowed only once
                ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_2ManyDecPoint, manager);
        }

        // If not valid decimal digit, then an error
        if ((*startPtr < chDigit_0) || (*startPtr > chDigit_9))
            ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars, manager);

        startPtr++;

    }

    return;
}

int XMLBigDecimal::compareValues( const XMLBigDecimal* const lValue
                                , const XMLBigDecimal* const rValue
                                , MemoryManager* const manager)
{
    if ((!lValue) || (!rValue) )
        ThrowXMLwithMemMgr(NumberFormatException, XMLExcepts::XMLNUM_null_ptr, manager);
        	
    return lValue->toCompare(*rValue);
}                                

/**
 * Returns -1, 0 or 1 as this is less than, equal to, or greater
 * than rValue.  
 * 
 * This method is based on the fact, that parsebigDecimal() would eliminate
 * unnecessary leading/trailing zeros. 
**/
int XMLBigDecimal::toCompare(const XMLBigDecimal& other) const
{
    /***
     * different sign
     */
    int lSign = this->getSign();
    if (lSign != other.getSign())
        return (lSign > other.getSign() ? 1 : -1);

    /***
     * same sign, zero
     */
    if (lSign == 0)    // optimization
        return 0;

    /***
     * same sign, non-zero
     */
    unsigned int lIntDigit = this->getTotalDigit() - this->getScale();
    unsigned int rIntDigit = other.getTotalDigit() - other.getScale();

    if (lIntDigit > rIntDigit)
    {
        return 1 * lSign;
    }
    else if (lIntDigit < rIntDigit)
    {
        return -1 * lSign;
    }
    else  // compare fraction
    {
        int res = XMLString::compareString
        ( this->getValue()
        , other.getValue()
        );

        if (res > 0)
            return 1 * lSign;
        else if (res < 0)
            return -1 * lSign;
        else
            return 0;
    }

}


/***
 * Support for Serialization/De-serialization
 ***/

IMPL_XSERIALIZABLE_TOCREATE(XMLBigDecimal)

XMLBigDecimal::XMLBigDecimal(MemoryManager* const manager)
: fSign(0)
, fTotalDigits(0)
, fScale(0)
, fRawDataLen(0)
, fRawData(0)
, fIntVal(0)
, fMemoryManager(manager)
{
}

void XMLBigDecimal::serialize(XSerializeEngine& serEng)
{
    //REVISIT: may not need to call base since it does nothing
    XMLNumber::serialize(serEng);

    if (serEng.isStoring())
    {
        serEng<<fSign;
        serEng<<fTotalDigits;
        serEng<<fScale;

        serEng.writeString(fRawData);
        serEng.writeString(fIntVal);

    }
    else
    {
        serEng>>fSign;
        serEng>>fTotalDigits;
        serEng>>fScale;

        XMLCh* rawdataStr;
        serEng.readString(rawdataStr);
        ArrayJanitor<XMLCh> rawdataName(rawdataStr, serEng.getMemoryManager());
        fRawDataLen = XMLString::stringLen(rawdataStr);

        XMLCh* intvalStr;
        serEng.readString(intvalStr);
        ArrayJanitor<XMLCh> intvalName(intvalStr, serEng.getMemoryManager());
        unsigned int intvalStrLen = XMLString::stringLen(intvalStr);

        if (fRawData)
            fMemoryManager->deallocate(fRawData);

        fRawData = (XMLCh*) fMemoryManager->allocate
        (
            ((fRawDataLen + intvalStrLen) + 4) * sizeof(XMLCh)
        );

        memcpy(fRawData, rawdataStr, fRawDataLen * sizeof(XMLCh));
        fRawData[fRawDataLen] = chNull;
        fIntVal = fRawData + fRawDataLen + 1;
        memcpy(fIntVal, intvalStr,  intvalStrLen * sizeof(XMLCh));
        fIntVal[intvalStrLen] = chNull;

    }

}

XERCES_CPP_NAMESPACE_END

