/*
********************************************************************************
*                                                                              *
* COPYRIGHT:                                                                   *
*   (C) Copyright Taligent, Inc.,  1997                                        *
*   (C) Copyright International Business Machines Corporation,  1997-1998      *
*   Licensed Material - Program-Property of IBM - All Rights Reserved.         *
*   US Government Users Restricted Rights - Use, duplication, or disclosure    *
*   restricted by GSA ADP Schedule Contract with IBM Corp.                     *
*                                                                              *
********************************************************************************
*
* File CHOICFMT.CPP
*
* Modification History:
*
*   Date        Name        Description
*   02/19/97    aliu        Converted from java.
*   03/20/97    helena      Finished first cut of implementation and got rid 
*                           of nextDouble/previousDouble and replaced with
*                           boolean array.
*   4/10/97     aliu        Clean up.  Modified to work on AIX.
*   06/04/97    helena      Fixed applyPattern(), toPattern() and not to include 
*                           wchar.h.
*   07/09/97    helena      Made ParsePosition into a class.
*   08/06/97    nos         removed overloaded constructor, fixed 'format(array)'
*    07/22/98    stephen        JDK 1.2 Sync - removed bool_t array (doubleFlags)
*   02/22/99    stephen     Removed character literals for EBCDIC safety
********************************************************************************
*/

#include "cpputils.h"
#include "choicfmt.h"
#include "numfmt.h"
#include "locid.h"
#include "mutex.h" 

// *****************************************************************************
// class ChoiceFormat
// *****************************************************************************
 
char        ChoiceFormat::fgClassID = 0; // Value is irrelevant

NumberFormat* ChoiceFormat::fgNumberFormat = 0;

// -------------------------------------
// Creates a ChoiceFormat instance based on the pattern.
 
ChoiceFormat::ChoiceFormat(const UnicodeString& newPattern,
                           UErrorCode& status)
: fChoiceLimits(0),
  fChoiceFormats(0),
  fCount(0)
{
    applyPattern(newPattern, status);
}
 
// -------------------------------------
// Creates a ChoiceFormat instance with the limit array and 
// format strings for each limit.

ChoiceFormat::ChoiceFormat(const double* limits, 
                           const UnicodeString* formats, 
                           int32_t cnt )
: fChoiceLimits(0),
  fChoiceFormats(0),
  fCount(0)
{
    setChoices(limits, formats, cnt );
}

// -------------------------------------
// copy constructor

ChoiceFormat::ChoiceFormat(const    ChoiceFormat&   that) 
    : fChoiceLimits(0),
      fChoiceFormats(0)
{
    *this = that;
}

// -------------------------------------

bool_t
ChoiceFormat::operator==(const Format& that) const
{
    if (this == &that) return TRUE;
    if (this->getDynamicClassID() != that.getDynamicClassID()) return FALSE;  // not the same class
    if (!NumberFormat::operator==(that)) return FALSE;
    ChoiceFormat& thatAlias = (ChoiceFormat&)that;
    if (fCount != thatAlias.fCount) return FALSE;
    // Checks the limits, the corresponding format string and LE or LT flags.
    // LE means less than and equal to, LT means less than.
    for (int32_t i = 0; i < fCount; i++) {
        if ((fChoiceLimits[i] != thatAlias.fChoiceLimits[i]) ||
            (fChoiceFormats[i] != thatAlias.fChoiceFormats[i]))
            return FALSE;
    }
    return TRUE;
}

// -------------------------------------
// copy constructor

const ChoiceFormat&
ChoiceFormat::operator=(const   ChoiceFormat& that)
{
    if (this != &that) {
        NumberFormat::operator=(that);
        fCount = that.fCount;
        delete [] fChoiceLimits; fChoiceLimits = 0;
        delete [] fChoiceFormats; fChoiceFormats = 0;
        fChoiceLimits = new double[fCount];
        fChoiceFormats = new UnicodeString[fCount];

        icu_arrayCopy(that.fChoiceLimits, fChoiceLimits, fCount);
        icu_arrayCopy(that.fChoiceFormats, fChoiceFormats, fCount);
    }
    return *this;
}

// -------------------------------------

ChoiceFormat::~ChoiceFormat()
{
    delete [] fChoiceLimits;
    fChoiceLimits = 0;
    delete [] fChoiceFormats;
    fChoiceFormats = 0;
    fCount = 0;
}

// -------------------------------------
// NumberFormat cache management

NumberFormat* 
ChoiceFormat::getNumberFormat(UErrorCode &status)
{
    NumberFormat *theFormat = 0;

    if (fgNumberFormat != 0) // if there's something in the cache
    {
        Mutex lock;

        if (fgNumberFormat != 0) // Someone might have grabbed it.
        {
            theFormat = fgNumberFormat;
            fgNumberFormat = 0; // We have exclusive right to this formatter.
        }
    }

    if(theFormat == 0) // If we weren't able to pull it out of the cache, then we have to create it.
    {
        theFormat = NumberFormat::createInstance(Locale::US, status);
        if(U_FAILURE(status))
            return 0;
        theFormat->setMinimumFractionDigits(1);
    }

    return theFormat;
}

void          
ChoiceFormat::releaseNumberFormat(NumberFormat *adopt)
{
    if(fgNumberFormat == 0) // If the cache is empty we must add it back.
    {
        Mutex lock;

        if(fgNumberFormat == 0)
        {
            fgNumberFormat = adopt;
            adopt = 0;
        }
    }

    delete adopt;
}

/**
 * Convert a string to a double value using a default NumberFormat object
 * which is static (shared by all ChoiceFormat instances).
 */
double
ChoiceFormat::stod(const UnicodeString& string,
                   UErrorCode& status)
{
    // Use a shared global number format to convert a double value to 
    // or string or vice versa.
    NumberFormat *myFormat = getNumberFormat(status);

    if(U_FAILURE(status))
        return -1; // OK?

    Formattable result;
    myFormat->parse(string, result, status);
    releaseNumberFormat(myFormat);
    double value = 0.0;
    if (U_SUCCESS(status))
    {
        switch(result.getType())
        {
            case Formattable::kLong: value = result.getLong(); break;
            case Formattable::kDouble: value = result.getDouble(); break;
        }
    }
    return value;
}

// -------------------------------------

/**
 * Convert a double value to a string using a default NumberFormat object
 * which is static (shared by all ChoiceFormat instances).
 */
UnicodeString&
ChoiceFormat::dtos(double value,
                   UnicodeString& string,
                   UErrorCode& status)
{
    NumberFormat *myFormat = getNumberFormat(status);

    if (U_SUCCESS(status)) {
        FieldPosition fieldPos(0);
        myFormat->format(value, string, fieldPos);
    }
    releaseNumberFormat(myFormat);
    return string;
}

// -------------------------------------
// Applies the pattern to this ChoiceFormat instance.
 
void
ChoiceFormat::applyPattern(const UnicodeString& newPattern,
                           UErrorCode& status)
{
    if (U_FAILURE(status))
        return;

    UnicodeString segments[2];
    double newChoiceLimits[30];  // current limit
    UnicodeString newChoiceFormats[30];   // later, use Vectors
    int32_t count = 0;
    int32_t part = 0;
    double startValue = 0;
    double oldStartValue = icu_getNaN();
    bool_t inQuote = FALSE;
    for(int i = 0; i < newPattern.size(); ++i) {
        UChar ch = newPattern[i];
        if(ch == 0x0027 /*'\''*/) {
            // Check for "''" indicating a literal quote
            if((i+1) < newPattern.size() && newPattern[i+1] == ch) {
                segments[part] += ch;
                ++i;
            }
            else 
                inQuote = !inQuote;
        }
        else if (inQuote) {
            segments[part] += ch;
        }
        else if (ch == 0x003C /*'<'*/ || ch == 0x0023 /*'#'*/ || ch == 0x2264) {
            if (segments[0] == "") {
                status = U_ILLEGAL_ARGUMENT_ERROR;
                return;
            }

            UnicodeString tempBuffer = segments[0];
            tempBuffer.trim();
            UChar posInf = 0x221E;
            UChar negInf [] = {0x002D /*'-'*/, posInf };
            if (tempBuffer == UnicodeString(&posInf, 1, 1)) {
                startValue = icu_getInfinity();
            } 
            else if (tempBuffer == UnicodeString(negInf, 2, 2)) {
                startValue = - icu_getInfinity();
            } 
            else {
                //segments[0].trim();
                startValue = stod(tempBuffer, status);
                if(U_FAILURE(status))
                    return;
            }

            if (ch == 0x003C /*'<'*/ && ! icu_isInfinite(startValue)) {
                startValue = nextDouble(startValue);
            }
            // {sfb} There is a bug in MSVC 5.0 sp3 -- 0.0 <= NaN ==> TRUE
            //if (startValue <= oldStartValue) {
            if (startValue <= oldStartValue && ! icu_isNaN(oldStartValue)) {
                status = U_ILLEGAL_ARGUMENT_ERROR;
                return;
            }
            segments[0].remove();
            part = 1;
        } else if (ch == 0x007C /*'|'*/) {
            newChoiceLimits[count] = startValue;
            newChoiceFormats[count] = segments[1];
            ++count;
            oldStartValue = startValue;
            segments[1].remove();
            part = 0;
        } else {
            segments[part] += ch;
        }
    }
    // clean up last one
    if (part == 1) {
        newChoiceLimits[count] = startValue;
        newChoiceFormats[count] = segments[1];
        ++count;
    }


    delete [] fChoiceLimits; fChoiceLimits = 0;
    delete [] fChoiceFormats; fChoiceFormats = 0;

    fCount = count;
    fChoiceLimits    = new double[fCount];
    fChoiceFormats    = new UnicodeString[fCount];
    
    icu_arrayCopy(newChoiceLimits, fChoiceLimits, fCount);
    icu_arrayCopy(newChoiceFormats, fChoiceFormats, fCount);
}
 
// -------------------------------------
// Reconstruct the original input pattern.
 
UnicodeString&
ChoiceFormat::toPattern(UnicodeString& result) const
{
    result.remove();
    for (int32_t i = 0; i < fCount; ++i) {
        if (i != 0) {
            result += 0x007C /*'|'*/;
        }
        // choose based upon which has less precision
        // approximate that by choosing the closest one to an integer.
        // could do better, but it's not worth it.
        double less = previousDouble(fChoiceLimits[i]);
        double tryLessOrEqual = icu_fabs(icu_IEEEremainder(fChoiceLimits[i], 1.0));
        double tryLess = icu_fabs(icu_IEEEremainder(less, 1.0));

        UErrorCode status = U_ZERO_ERROR;
        UnicodeString buf;
        // {sfb} hack to get this to work on MSVC - NaN doesn't behave as it should
        if (tryLessOrEqual < tryLess && 
            ! (icu_isNaN(tryLessOrEqual) || icu_isNaN(tryLess))) {
            result += dtos(fChoiceLimits[i], buf, status);
            result += 0x0023 /*'#'*/;
        } 
        else {
            if (icu_isPositiveInfinity(fChoiceLimits[i])) {
                result += 0x221E;
            } else if (icu_isNegativeInfinity(fChoiceLimits[i])) {
                result += 0x002D /*'-'*/;
                result += 0x221E;
            } else {
                result += dtos(less, buf, status);
            }
            result += 0x003C /*'<'*/;
        }
        // Append fChoiceFormats[i], using quotes if there are special characters.
        // Single quotes themselves must be escaped in either case.
        UnicodeString text = fChoiceFormats[i];
        bool_t needQuote = text.indexOf(0x003C /*'<'*/) >= 0
            || text.indexOf(0x0023 /*'#'*/) >= 0
            || text.indexOf(0x2264) >= 0
            || text.indexOf(0x007C /*'|'*/) >= 0;
        if (needQuote) 
            result += 0x0027 /*'\''*/;
        if (text.indexOf(0x0027 /*'\''*/) < 0) 
            result += text;
        else {
            for (int j = 0; j < text.size(); ++j) {
                UChar c = text[j];
                result += c;
                if (c == 0x0027 /*'\''*/) 
                    result += c;
            }
        }
        if (needQuote) 
            result += 0x0027 /*'\''*/;
    }
    
    return result;
}
 
// -------------------------------------
// Adopts the limit and format arrays.
 
void
ChoiceFormat::adoptChoices(double *limits, 
                           UnicodeString *formats, 
                           int32_t cnt )
{
    if(limits == 0 || formats == 0)
        return;
        
    delete [] fChoiceLimits;
    fChoiceLimits = 0;
    delete [] fChoiceFormats;
    fChoiceFormats = 0;
    fChoiceLimits = limits;
    fChoiceFormats = formats;
    fCount = cnt;
}
 
// -------------------------------------
// Sets the limit and format arrays. 
void
ChoiceFormat::setChoices(  const double* limits, 
                           const UnicodeString* formats, 
                           int32_t cnt )
{
    if(limits == 0 || formats == 0)
        return;

    delete [] fChoiceLimits; fChoiceLimits = 0;
    delete [] fChoiceFormats; fChoiceFormats = 0;

    // Note that the old arrays are deleted and this owns
    // the created array.
    fCount = cnt;
    fChoiceLimits = new double[fCount];
    fChoiceFormats = new UnicodeString[fCount];

    icu_arrayCopy(limits, fChoiceLimits, fCount);
    icu_arrayCopy(formats, fChoiceFormats, fCount);
}
 
// -------------------------------------
// Gets the limit array.
 
const double*
ChoiceFormat::getLimits(int32_t& cnt) const 
{
    cnt = fCount;
    return fChoiceLimits;
}
 
// -------------------------------------
// Gets the format array.
 
const UnicodeString*
ChoiceFormat::getFormats(int32_t& cnt) const
{
    cnt = fCount;
    return fChoiceFormats;
}
 
// -------------------------------------
// Formats a long number, it's actually formatted as
// a double.  The returned format string may differ
// from the input number because of this.
 
UnicodeString&
ChoiceFormat::format(int32_t number, 
                     UnicodeString& toAppendTo, 
                     FieldPosition& status) const
{
    return format((double) number, toAppendTo, status);
}
 
// -------------------------------------
// Formats a double number.
 
UnicodeString&
ChoiceFormat::format(double number, 
                     UnicodeString& toAppendTo, 
                     FieldPosition& status) const
{
    // find the number
    int32_t i;
    for (i = 0; i < fCount; ++i) {
        if (!(number >= fChoiceLimits[i])) {
            // same as number < fChoiceLimits, except catches NaN
            break;
        }
    }
    --i;
    if (i < 0) 
        i = 0;
    // return either a formatted number, or a string
    return (toAppendTo += fChoiceFormats[i]);
}
 
// -------------------------------------
// Formats an array of objects. Checks if the data type of the objects
// to get the right value for formatting.  

UnicodeString&
ChoiceFormat::format(const Formattable* objs,
                     int32_t cnt,
                     UnicodeString& toAppendTo,
                     FieldPosition& pos,
                     UErrorCode& status) const
{
    if(cnt < 0) {
        status = U_ILLEGAL_ARGUMENT_ERROR;
        return toAppendTo;
    }
    
    UnicodeString buffer;
    for (int32_t i = 0; i < cnt; i++) {
        buffer.remove();
        toAppendTo += format((objs[i].getType() == Formattable::kLong) ? objs[i].getLong() : objs[i].getDouble(), 
                             buffer, pos);
    }

    return toAppendTo;
}

// -------------------------------------
// Formats an array of objects. Checks if the data type of the objects
// to get the right value for formatting.  

UnicodeString&
ChoiceFormat::format(const Formattable& obj, 
                     UnicodeString& toAppendTo, 
                     FieldPosition& pos,
                     UErrorCode& status) const
{
    return NumberFormat::format(obj, toAppendTo, pos, status); 
}
// -------------------------------------
 
void
ChoiceFormat::parse(const UnicodeString& text, 
                    Formattable& result,
                    ParsePosition& status) const
{
    // find the best number (defined as the one with the longest parse)
    int32_t start = status.getIndex();
    int32_t furthest = start;
    double bestNumber = icu_getNaN();
    double tempNumber = 0.0;
    for (int i = 0; i < fCount; ++i) {
        UnicodeString tempString = fChoiceFormats[i];
        if(text.compareBetween(start, tempString.size(), tempString, 0, tempString.size()) == 0) {
            status.setIndex(start + tempString.size());
            tempNumber = fChoiceLimits[i];
            if (status.getIndex() > furthest) {
                furthest = status.getIndex();
                bestNumber = tempNumber;
                if (furthest == text.size()) 
                    break;
            }
        }
    }
    status.setIndex(furthest);
    if (status.getIndex() == start) {
        status.setErrorIndex(furthest);
    }
    result.setDouble(bestNumber);
}

// -------------------------------------
// Parses the text and return the Formattable object.  
 
void
ChoiceFormat::parse(const UnicodeString& text, 
                    Formattable& result,
                    UErrorCode& status) const
{
    NumberFormat::parse(text, result, status);
}

// -------------------------------------
 
Format*
ChoiceFormat::clone() const
{
    ChoiceFormat *aCopy = new ChoiceFormat(*this);
    return aCopy;
}

// -------------------------------------

double 
ChoiceFormat::nextDouble( double d, bool_t positive )
{
    return icu_nextDouble( d, positive );
}

//eof
