/*
*******************************************************************************
*   Copyright (C) 1996-1999, International Business Machines
*   Corporation and others.  All Rights Reserved.
*******************************************************************************
*/

#include "unicode/umsg.h"
#include "mutex.h"
#include "unicode/uloc.h"
#include "unicode/ustring.h"
#include "unicode/fmtable.h"
#include "cpputils.h"
#include "unicode/msgfmt.h"
#include "unicode/unistr.h"
#include "unicode/numfmt.h"


// MessageFormat Type List  Number, Date, Time or Choice
const UnicodeString fgTypeList[] = {
  UnicodeString(), UnicodeString(), UNICODE_STRING("number", 6), UnicodeString(), UNICODE_STRING("date", 4), UnicodeString(), UNICODE_STRING("time", 4), UnicodeString(), UNICODE_STRING("choice", 6)
};

// NumberFormat modifier list, default, currency, percent or integer
const UnicodeString fgModifierList[] = {
  UnicodeString(), UnicodeString(), UNICODE_STRING("currency", 8), UnicodeString(), UNICODE_STRING("percent", 7), UnicodeString(), UNICODE_STRING("integer", 7), UnicodeString(), UnicodeString()
};

// DateFormat modifier list, default, short, medium, long or full
const UnicodeString fgDateModifierList[] = {
  UnicodeString(), UnicodeString(), UNICODE_STRING("short", 5), UnicodeString(), UNICODE_STRING("medium", 6), UnicodeString(), UNICODE_STRING("long", 4), UnicodeString(), UNICODE_STRING("full", 4)
};

// Number of items in the lists
const int32_t fgListLength = 9;

// Determine if a keyword belongs to a list of keywords
int32_t
findKeyword(const     UnicodeString&     s, 
        const     UnicodeString    *list,
                int32_t&    kwLen)
{
  UnicodeString buffer = s;

  // Trims the space characters and turns all characters
  // in s to lower case.
  buffer.trim().toLower();
  for(int32_t i = 0; i < fgListLength; ++i) {

    // Determine if there is a ','
    // If so, the string contains a modifier, and we only want to 
    // parse the type
    int32_t commaPos = buffer.indexOf((UChar)0x002C);
    commaPos = (commaPos == -1 ? buffer.length() : commaPos);
    buffer.truncate(commaPos);
    if(buffer == list[i]) {
      kwLen = list[i].length();
      return i;
    }
  }
  
  kwLen = 0;
  return - 1;
}

// Match the type of argument in a message format pattern
// The type consists of a type indicator and an optional modifier
// Possible types : number, date, time, choice
// Possible modifiers : currency, percent, integer, full, long, short
// We only worry about parsing the types and the "integer" modifier
Formattable::Type
matchType(const UChar         *pat,
          int32_t     openBrace,
          int32_t     closeBrace)
{
  int32_t len = (closeBrace - openBrace) - 1;
  Formattable::Type result = Formattable::kString;

  // Strings like "{0}" are strings
  if(len == 1) {
    result = Formattable::kString;
    return result;
  }
  // Assume the input is well-formed
  else {
    UnicodeString type((UChar*)pat + openBrace + 1 + 2, len - 2, len - 2);
    int32_t matchLen, kw;

    kw = findKeyword(type, fgTypeList, matchLen);

    // there is a modifier if type contains a ','
    UBool hasModifier = (type.indexOf((UChar)0x002C) != -1);
    
    switch(kw) {

      // number
    case 1: case 2:

      if(hasModifier) {
    UnicodeString modifier((UChar*)pat + openBrace + 1 + 1 + 2 + matchLen, 
                   len - 2 - matchLen - 1, 
                   len - 2 - matchLen - 1);
    
    switch(findKeyword(modifier, fgModifierList, matchLen)) {
    
      // default
    case 0:
      // currency
    case 1: case 2:
      // percent
    case 3: case 4:
      result = Formattable::kDouble;
      break;
      
      // integer
    case 5: case 6:
      result = Formattable::kLong;
      break;
    }
      }
      else {
    result = Formattable::kDouble;
      }
      break;
      
      // date
    case 3: case 4:
      // time
    case 5: case 6:
      result = Formattable::kDate;
      break;

      // choice      
    case 7: case 8:
      result = Formattable::kDouble;
      break;
    }
  }
  
  return result;
}

  
// ==========
// This code section is entirely bogus.  I just need an eeasy way to
// convert from string to an int, and I can't use the standard library

static NumberFormat *fgNumberFormat = 0;

NumberFormat* 
umsg_getNumberFormat(UErrorCode& status)
{
  NumberFormat *theFormat = 0;
  
  if(fgNumberFormat != 0) {
    Mutex lock;
    
    if(fgNumberFormat != 0) {
      theFormat = fgNumberFormat;
      fgNumberFormat = 0; // We have exclusive right to this formatter.
    }
  }
  
  if(theFormat == 0) {
    theFormat = NumberFormat::createInstance(Locale::US, status);
    if(U_FAILURE(status))
      return 0;
    theFormat->setParseIntegerOnly(TRUE);
  }
  
  return theFormat;
}

void          
umsg_releaseNumberFormat(NumberFormat *adopt)
{
  if(fgNumberFormat == 0) {
    Mutex lock;
    
    if(fgNumberFormat == 0) {
      fgNumberFormat = adopt;
      adopt = 0;
    }
  }
  
  delete adopt;
}

int32_t
umsg_stoi(const UnicodeString& string,
      UErrorCode& status)
{
  NumberFormat *myFormat = umsg_getNumberFormat(status);
  
  if(U_FAILURE(status))
    return -1; // OK?
  
  Formattable result;
  // Uses the global number formatter to parse the string.
  // Note: We assume here that parse() is thread-safe.
  myFormat->parse(string, result, status);
  umsg_releaseNumberFormat(myFormat);
  
  int32_t value = 0;
  if(U_SUCCESS(status) && result.getType() == Formattable::kLong)
    value = result.getLong();
  
  return value;
}

UnicodeString&
umsg_itos(int32_t i,
      UnicodeString& string)
{
  UErrorCode status = U_ZERO_ERROR;
  NumberFormat *myFormat = umsg_getNumberFormat(status);
  
  if(U_FAILURE(status))
    return (string = "<ERROR>");
  
  myFormat->format(i, string);
  umsg_releaseNumberFormat(myFormat);
  
  return string;
}

// ==========

#define MAX_ARGS 10

// Eventually, message format should be rewritten natively in C.
// For now, this is a hack that should work:
//  1. Parse the pattern, determining the argument types
//  2. Create a Formattable array with the varargs
//  3. Call through to the existing C++ code
//
// Right now this imposes the same limit as MessageFormat in C++
// Namely, only MAX_ARGS arguments are supported
U_CAPI int32_t
u_formatMessage(    const    char        *locale,
            const    UChar        *pattern,
                int32_t        patternLength,
                UChar        *result,
                int32_t        resultLength,
                UErrorCode    *status,
                ...)
{
  va_list    ap;
  int32_t actLen;
  if(U_FAILURE(*status)) return -1;

  // start vararg processing
  va_start(ap, status);

  actLen = u_vformatMessage(locale,pattern,patternLength,result,resultLength,ap,status);

  // end vararg processing
  va_end(ap);

  return actLen;
}

U_CAPI int32_t
u_vformatMessage(    const    char        *locale,
            const    UChar        *pattern,
                int32_t        patternLength,
                UChar        *result,
                int32_t        resultLength,
                va_list       ap,
                UErrorCode    *status)

{
  if(U_FAILURE(*status)) return -1;

  int32_t patLen = (patternLength == -1 ? u_strlen(pattern) : patternLength);
  int32_t actLen;

  // ========================================
  // Begin pseudo-parser

  // This is a simplified version of the C++ pattern parser
  // All it does is look for an unquoted '{' and read the type

  int32_t     part         = 0;
  UBool     inQuote     = FALSE;
  int32_t     braceStack     = 0;
  const UChar     *pat         = pattern;
  const UChar     *patLimit     = pattern + patLen;
  int32_t    bracePos    = 0;
  int32_t    count        = 0;
  Formattable    args        [ MAX_ARGS ];
  Formattable::Type argTypes     [ MAX_ARGS ];


  // set the types to a bogus value initially (no such type as kArray from C)
  for(int32_t j = 0; j < MAX_ARGS; ++j)
    argTypes[j] = Formattable::kArray;

  // pseudo-parse the pattern
  while(pat < patLimit) {
    if(part == 0) {
      if(*pat == 0x0027 /*'\''*/) {
        // handle double quotes
        if( (pat + 1) < patLimit && *(pat + 1) == 0x0027 /*'\''*/)
          pat++;
        else
          inQuote = ! inQuote;
      }  
      else if(*pat == 0x007B /*'{'*/ && ! inQuote) {
        part = 1;
        bracePos = (pat - pattern);
      }
    }
    else if(inQuote) {              // just copy quotes in parts
      if(*pat == 0x0027 /*'\''*/)
    inQuote = FALSE;
    } 
    else {
      switch (*pat) {
    
      case 0x002C /*','*/:
        if(part < 3)
          part += 1;
        break;

      case 0x007B /*'{'*/:
        ++braceStack;
        break;

      case 0x007D /*'}'*/:
        if(braceStack == 0) {
          part = 0;
          // found a close brace, determine the argument type enclosed
          // and the numeric ID of the argument
          Formattable::Type type = 
            matchType(pattern, bracePos, (pat - pattern));

          // the numeric ID is important, because if the pattern has a 
          // section like "{0} {0} {0}" we only want to get one argument
          // from the variable argument list, despite the fact that
          // it is in the pattern three times
          int32_t argNum = umsg_stoi(pattern + bracePos + 1, *status);

          if(argNum >= MAX_ARGS) {
            *status = U_INTERNAL_PROGRAM_ERROR;
            return -1;
          }
      
          // register the type of this argument in our list
          argTypes[argNum] = type;
      
          // adjust argument count
          count = ( (argNum + 1) > count ? (argNum + 1) : count);
        }
        else
          --braceStack;
        break;
    
      case 0x0027 /*'\''*/:
        inQuote = TRUE;
        break;
      }
    }
    
    // increment position in pattern
    pat++;
  } 

  // detect any unmatched braces in the pattern
  if(braceStack == 0 && part != 0) {
    *status = U_INVALID_FORMAT_ERROR;
    return -1;
  }

  // iterate through the vararg list, and get the arguments out
  for(int32_t i = 0; i < count; ++i) {
    
    UChar *stringVal;
    
    switch(argTypes[i]) {
    case Formattable::kDate:
      args[i].setDate(va_arg(ap, UDate));
      break;
      
    case Formattable::kDouble:
      args[i].setDouble(va_arg(ap, double));
      break;
      
    case Formattable::kLong:
      args[i].setLong(va_arg(ap, int32_t));
      break;
      
    case Formattable::kString:
      // For some reason, a temporary is needed
      stringVal = va_arg(ap, UChar*);
      args[i].setString(stringVal);
      break;
      
    case Formattable::kArray:
      // throw away this argument
      // this is highly platform-dependent, and probably won't work
      // so, if you try to skip arguments in the list (and not use them)
      // you'll probably crash
      va_arg(ap, int);
      break;
    }
  }
  
  // End pseudo-parser
  // ========================================

  // just call through to the C++ implementation
  UnicodeString patString((UChar*)pattern, patLen, patLen);
  MessageFormat fmt(patString, Locale(locale), *status);
  UnicodeString res(result, 0, resultLength);
  FieldPosition fp;
  fmt.format(args, count, res, fp, *status);

  T_fillOutputParams(&res, result, resultLength, &actLen, status);
  return actLen;
}

// For parse, do the reverse of format:
//  1. Call through to the C++ APIs
//  2. Just assume the user passed in enough arguments.
//  3. Iterate through each formattable returned, and assign to the arguments
U_CAPI void 
u_parseMessage(    const    char        *locale,
        const    UChar        *pattern,
            int32_t        patternLength,
        const    UChar        *source,
            int32_t        sourceLength,
            UErrorCode    *status,
            ...)
{
  va_list    ap;

  if(U_FAILURE(*status)) return;

  // start vararg processing
  va_start(ap, status);

  u_vparseMessage(locale,pattern,patternLength,source,sourceLength,ap,status);

  // end vararg processing
  va_end(ap);

}

U_CAPI void 
u_vparseMessage(    const    char        *locale,
        const    UChar        *pattern,
            int32_t        patternLength,
        const    UChar        *source,
            int32_t        sourceLength,
            va_list       ap,
            UErrorCode    *status)
{
  if(U_FAILURE(*status)) return;

  int32_t patLen = (patternLength == -1 ? u_strlen(pattern) : patternLength);
  int32_t srcLen = (sourceLength == -1 ? u_strlen(source) : sourceLength);
  
  UnicodeString patString((UChar*)pattern, patLen, patLen);
  MessageFormat fmt(patString, Locale(locale), *status);
  UnicodeString srcString((UChar*)source, srcLen, srcLen);
  int32_t count = 0;
  Formattable *args = fmt.parse(srcString, count, *status);

  UDate *aDate;
  double *aDouble;
  UChar *aString;
  UnicodeString temp;

  // assign formattables to varargs
  for(int32_t i = 0; i < count; i++) {
    switch(args[i].getType()) {
      
    case Formattable::kDate:
      aDate = va_arg(ap, UDate*);
      *aDate = args[i].getDate();
      break;
      
    case Formattable::kDouble:
      aDouble = va_arg(ap, double*);
      *aDouble = args[i].getDouble();
      break;
      
    case Formattable::kLong:
      // always assume doubles for parsing
      aDouble = va_arg(ap, double*);
      *aDouble = (double) args[i].getLong();
      break;
      
    case Formattable::kString:
      aString = va_arg(ap, UChar*);
      args[i].getString(temp);
      u_strcpy(aString, temp.getUChars());
      break;
      
      // better not happen!
    case Formattable::kArray:
      // DIE
      break;
    }
  }
  
  // clean up
  delete [] args;
}