12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565
/*******************************************************************************

        copyright:      Copyright (c) 2005 John Chapman. All rights reserved

        license:        BSD style: $(LICENSE)

        version:        Initial release: 2005

        author:         John Chapman

******************************************************************************/

module tango.text.locale.Convert;

private import  tango.time.WallClock;

private import  tango.core.Exception;

private import  tango.text.locale.Core;

private import  tango.time.chrono.Calendar;

private import  Integer = tango.text.convert.Integer;

/******************************************************************************

******************************************************************************/

private struct Result
{
        private size_t    index;
        private char[]  target_;

        /**********************************************************************

        **********************************************************************/

        private static Result opCall (char[] target)
        {
                Result result;

                result.target_ = target;
                return result;
        }

        /**********************************************************************

        **********************************************************************/

        private void opOpAssign(immutable(char)[] s : "~") (const(char)[] rhs)
        {
                auto end = index + rhs.length;

                target_[index .. end] = rhs;
                index = end;
        }

        /**********************************************************************

        **********************************************************************/

        private void opOpAssign(immutable(char)[] s : "~") (char rhs)
        {
                target_[index++] = rhs;
        }

        /**********************************************************************

        **********************************************************************/

        private char[] get ()
        {
                return target_[0 .. index];
        }

        /**********************************************************************

        **********************************************************************/

        private char[] scratch ()
        {
                return target_;
        }
}


/******************************************************************************

   * Converts the value of this instance to its equivalent string representation using the specified _format and culture-specific formatting information.
   * Params: 
   *   format = A _format string.
   *   formatService = An IFormatService that provides culture-specific formatting information.
   * Returns: A string representation of the value of this instance as specified by format and formatService.
   * Remarks: See $(LINK2 datetimeformat.html, Time Formatting) for more information about date and time formatting.
   * Examples:
   * ---
   * import tango.io.Print, tango.text.locale.Core, tango.time.WallClock;
   *
   * void main() {
   *   Culture culture = Culture.current;
   *   Time now = WallClock.now;
   *
   *   Println("Current date and time: %s", now.toString());
   *   Println();
   *
   *   // Format the current date and time in a number of ways.
   *   Println("Culture: %s", culture.englishName);
   *   Println();
   *
   *   Println("Short date:              %s", now.toString("d"));
   *   Println("Long date:               %s", now.toString("D"));
   *   Println("Short time:              %s", now.toString("t"));
   *   Println("Long time:               %s", now.toString("T"));
   *   Println("General date short time: %s", now.toString("g"));
   *   Println("General date long time:  %s", now.toString("G"));
   *   Println("Month:                   %s", now.toString("M"));
   *   Println("RFC1123:                 %s", now.toString("R"));
   *   Println("Sortable:                %s", now.toString("s"));
   *   Println("Year:                    %s", now.toString("Y"));
   *   Println();
   *
   *   // Display the same values using a different culture.
   *   culture = Culture.getCulture("fr-FR");
   *   Println("Culture: %s", culture.englishName);
   *   Println();
   *
   *   Println("Short date:              %s", now.toString("d", culture));
   *   Println("Long date:               %s", now.toString("D", culture));
   *   Println("Short time:              %s", now.toString("t", culture));
   *   Println("Long time:               %s", now.toString("T", culture));
   *   Println("General date short time: %s", now.toString("g", culture));
   *   Println("General date long time:  %s", now.toString("G", culture));
   *   Println("Month:                   %s", now.toString("M", culture));
   *   Println("RFC1123:                 %s", now.toString("R", culture));
   *   Println("Sortable:                %s", now.toString("s", culture));
   *   Println("Year:                    %s", now.toString("Y", culture));
   *   Println();
   * }
   *
   * // Produces the following output:
   * // Current date and time: 26/05/2006 10:04:57 AM
   * //
   * // Culture: English (United Kingdom)
   * //
   * // Short date:              26/05/2006
   * // Long date:               26 May 2006
   * // Short time:              10:04
   * // Long time:               10:04:57 AM
   * // General date short time: 26/05/2006 10:04
   * // General date long time:  26/05/2006 10:04:57 AM
   * // Month:                   26 May
   * // RFC1123:                 Fri, 26 May 2006 10:04:57 GMT
   * // Sortable:                2006-05-26T10:04:57
   * // Year:                    May 2006
   * //
   * // Culture: French (France)
   * //
   * // Short date:              26/05/2006
   * // Long date:               vendredi 26 mai 2006
   * // Short time:              10:04
   * // Long time:               10:04:57
   * // General date short time: 26/05/2006 10:04
   * // General date long time:  26/05/2006 10:04:57
   * // Month:                   26 mai
   * // RFC1123:                 ven., 26 mai 2006 10:04:57 GMT
   * // Sortable:                2006-05-26T10:04:57
   * // Year:                    mai 2006
   * ---

******************************************************************************/

public char[] formatDateTime (char[] output, Time dateTime, const(char)[] format, IFormatService formatService = null) 
{
    return formatDateTime (output, dateTime, format, DateTimeFormat.getInstance(formatService));
}

char[] formatDateTime (char[] output, Time dateTime, const(char)[] format, DateTimeFormat dtf)
{
        /**********************************************************************

        **********************************************************************/

        const(char)[] expandKnownFormat(const(char)[] format, ref Time dateTime)
        {
                const(char)[] f;

                switch (format[0])
                       {
                       case 'd':
                            f = dtf.shortDatePattern;
                            break;
                       case 'D':
                            f = dtf.longDatePattern;
                            break;
                       case 'f':
                            f = dtf.longDatePattern ~ " " ~ dtf.shortTimePattern;
                            break;
                       case 'F':
                            f = dtf.fullDateTimePattern;
                            break;
                       case 'g':
                            f = dtf.generalShortTimePattern;
                            break;
                       case 'G':
                            f = dtf.generalLongTimePattern;
                            break;
                       case 'm':
                       case 'M':
                            f = dtf.monthDayPattern;
                            break;
                       case 'r':
                       case 'R':
                            f = dtf.rfc1123Pattern;
                            break;
                       case 's':
                            f = dtf.sortableDateTimePattern;
                            break;
                       case 't':
                            f = dtf.shortTimePattern;
                            break;
                       case 'T':
                            f = dtf.longTimePattern;
                            break;
version (Full)
{
                       case 'u':
                            dateTime = dateTime.toUniversalTime();
                            dtf = DateTimeFormat.invariantFormat;
                            f = dtf.universalSortableDateTimePattern;
                            break;
                       case 'U':
                            dtf = cast(DateTimeFormat) dtf.clone();
                            dateTime = dateTime.toUniversalTime();
                            if (typeid(typeof(dtf.calendar)) !is typeid(Gregorian))
                                dtf.calendar = Gregorian.generic;
                            f = dtf.fullDateTimePattern;
                            break;
}
                       case 'y':
                       case 'Y':
                            f = dtf.yearMonthPattern;
                            break;
                       default:
                           throw new IllegalArgumentException("Invalid date format.");
                       }

                return f;
        }

        /**********************************************************************

        **********************************************************************/

        char[] formatCustom (ref Result result, Time dateTime, const(char)[] format)
        {

                int parseRepeat(const(char)[] format, int pos, char c)
                {
                        int n = pos + 1;
                        while (n < format.length && format[n] is c)
                                n++;
                        return n - pos;
                }

                const(char)[] formatDayOfWeek(Calendar.DayOfWeek dayOfWeek, int rpt)
                {
                        if (rpt is 3)
                                return dtf.getAbbreviatedDayName(dayOfWeek);
                        return dtf.getDayName(dayOfWeek);
                }

                const(char)[] formatMonth(int month, int rpt)
                {
                        if (rpt is 3)
                                return dtf.getAbbreviatedMonthName(month);
                        return dtf.getMonthName(month);
                }

                char[] formatInt (char[] tmp, int v, int minimum)
                {
                        auto num = Integer.format (tmp, v, "u");
                        if ((minimum -= num.length) > 0)
                           {
                           auto p = tmp.ptr + tmp.length - num.length;
                           while (minimum--)
                                  *--p = '0';
                           num = tmp [p-tmp.ptr .. $];
                           }
                        return num;
                }

                int parseQuote(const(char)[] format, int pos, out char[] result)
                {
                        int start = pos;
                        char chQuote = format[pos++];
                        bool found;
                        while (pos < format.length)
                              {
                              char c = format[pos++];
                              if (c is chQuote)
                                 {
                                 found = true;
                                 break;
                                 }
                              else
                                 if (c is '\\')
                                    { // escaped
                                    if (pos < format.length)
                                        result ~= format[pos++];
                                    }
                                 else
                                    result ~= c;
                              }
                        return pos - start;
                }


                Calendar calendar = dtf.calendar;
                bool justTime = true;
                int index, len;
                char[10] tmp;

                if (format[0] is '%')
                    {
                    // specifiers for both standard format strings and custom ones
                    enum immutable(char)[] commonSpecs = "dmMsty";
                    foreach (c; commonSpecs)
                        if (format[1] is c)
                            {
                            index += 1;
                            break;
                            }
                    }

                while (index < format.length)
                      {
                      char c = format[index];
                      auto time = dateTime.time;

                      switch (c)
                             {
                             case 'd':  // day
                                  len = parseRepeat(format, index, c);
                                  if (len <= 2)
                                     {
                                     int day = calendar.getDayOfMonth(dateTime);
                                     result ~= formatInt (tmp, day, len);
                                     }
                                  else
                                     result ~= formatDayOfWeek(calendar.getDayOfWeek(dateTime), len);
                                  justTime = false;
                                  break;

                             case 'M':  // month
                                  len = parseRepeat(format, index, c);
                                  int month = calendar.getMonth(dateTime);
                                  if (len <= 2)
                                      result ~= formatInt (tmp, month, len);
                                  else
                                     result ~= formatMonth(month, len);
                                  justTime = false;
                                  break;
                             case 'y':  // year
                                  len = parseRepeat(format, index, c);
                                  int year = calendar.getYear(dateTime);
                                  // Two-digit years for Japanese
                                  if (calendar.id is Calendar.JAPAN)
                                      result ~= formatInt (tmp, year, 2);
                                  else
                                     {
                                     if (len <= 2)
                                         result ~= formatInt (tmp, year % 100, len);
                                     else
                                        result ~= formatInt (tmp, year, len);
                                     }
                                  justTime = false;
                                  break;
                             case 'h':  // hour (12-hour clock)
                                  len = parseRepeat(format, index, c);
                                  int hour = time.hours % 12;
                                  if (hour is 0)
                                      hour = 12;
                                  result ~= formatInt (tmp, hour, len);
                                  break;
                             case 'H':  // hour (24-hour clock)
                                  len = parseRepeat(format, index, c);
                                  result ~= formatInt (tmp, time.hours, len);
                                  break;
                             case 'm':  // minute
                                  len = parseRepeat(format, index, c);
                                  result ~= formatInt (tmp, time.minutes, len);
                                  break;
                             case 's':  // second
                                  len = parseRepeat(format, index, c);
                                  result ~= formatInt (tmp, time.seconds, len);
                                  break;
                             case 't':  // AM/PM
                                  len = parseRepeat(format, index, c);
                                  if (len is 1)
                                     {
                                     if (time.hours < 12)
                                        {
                                        if (dtf.amDesignator.length != 0)
                                            result ~= dtf.amDesignator[0];
                                        }
                                     else
                                        {
                                        if (dtf.pmDesignator.length != 0)
                                            result ~= dtf.pmDesignator[0];
                                        }
                                     }
                                  else
                                     result ~= (time.hours < 12) ? dtf.amDesignator : dtf.pmDesignator;
                                  break;
                             case 'z':  // timezone offset
                                  len = parseRepeat(format, index, c);
version (Full)
{
                                  TimeSpan offset = (justTime && dateTime.ticks < TICKS_PER_DAY)
                                                     ? TimeZone.current.getUtcOffset(WallClock.now)
                                                     : TimeZone.current.getUtcOffset(dateTime);
                                  int hours = offset.hours;
                                  int minutes = offset.minutes;
                                  result ~= (offset.backward) ? '-' : '+';
}
else
{
                                  auto minutes = cast(int) (WallClock.zone.minutes);
                                  if (minutes < 0)
                                      minutes = -minutes, result ~= '-';
                                  else
                                     result ~= '+';
                                  int hours = minutes / 60;
                                  minutes %= 60;
}
                                  if (len is 1)
                                      result ~= formatInt (tmp, hours, 1);
                                  else
                                     if (len is 2)
                                         result ~= formatInt (tmp, hours, 2);
                                     else
                                        {
                                        result ~= formatInt (tmp, hours, 2);
                                        result ~= ':';
                                        result ~= formatInt (tmp, minutes, 2);
                                        }
                                  break;
                             case ':':  // time separator
                                  len = 1;
                                  result ~= dtf.timeSeparator;
                                  break;
                             case '/':  // date separator
                                  len = 1;
                                  result ~= dtf.dateSeparator;
                                  break;
                             case '\"':  // string literal
                             case '\'':  // char literal
                                  char[] quote;
                                  len = parseQuote(format, index, quote);
                                  result ~= quote;
                                  break;
                             default:
                                 len = 1;
                                 result ~= c;
                                 break;
                             }
                      index += len;
                      }
                return result.get();
        }


        auto result = Result (output);

        if (format is null)
            format = "G"; // Default to general format.

        if (format.length is 1) // It might be one of our shortcuts.
            format = expandKnownFormat (format, dateTime);

        return formatCustom (result, dateTime, format);
}



/*******************************************************************************

*******************************************************************************/

private extern (C) char* ecvt(double d, int digits, out int decpt, out bool sign);

/*******************************************************************************

*******************************************************************************/

// Must match NumberFormat.decimalPositivePattern
package enum immutable(char)[] positiveNumberFormat = "#";

// Must match NumberFormat.decimalNegativePattern
package enum immutable(char)[][] negativeNumberFormats =
                [
                "(#)", "-#", "- #", "#-", "# -"
                ];

// Must match NumberFormat.currencyPositivePattern
package enum immutable(char)[][] positiveCurrencyFormats =
                [
                "$#", "#$", "$ #", "# $"
                ];

// Must match NumberFormat.currencyNegativePattern
package enum immutable(char)[][] negativeCurrencyFormats =
                [
                "($#)", "-$#", "$-#", "$#-", "(#$)",
                "-#$", "#-$", "#$-", "-# $", "-$ #",
                "# $-", "$ #-", "$ -#", "#- $", "($ #)", "(# $)"
                ];

/*******************************************************************************

*******************************************************************************/

package template charTerm (T)
{
        package int charTerm(T* s)
        {
                int i;
                while (*s++ != '\0')
                        i++;
                return i;
        }
}

/*******************************************************************************

*******************************************************************************/

char[] longToString (char[] buffer, long value, int digits, const(char)[] negativeSign)
{
        if (digits < 1)
            digits = 1;

        auto n = buffer.length;
        ulong uv = (value >= 0) ? value : cast(ulong) -value;

        if (uv > uint.max)
           {
           while (--digits >= 0 || uv != 0)
                 {
                 buffer[--n] = cast(char)(uv % 10 + '0');
                 uv /= 10;
                 }
           }
        else
           {
           uint v = cast(uint) uv;
           while (--digits >= 0 || v != 0)
                 {
                 buffer[--n] = cast(char)(v % 10 + '0');
                 v /= 10;
                 }
           }


        if (value < 0)
           {
           for (size_t i = negativeSign.length; i > 0;)
                buffer[--n] = negativeSign[--i];
           }

        return buffer[n .. $];
}

/*******************************************************************************

*******************************************************************************/

char[] longToHexString (char[] buffer, ulong value, int digits, char format)
{
        if (digits < 1)
            digits = 1;

        auto n = buffer.length;
        while (--digits >= 0 || value != 0)
              {
              auto v = cast(uint) value & 0xF;
              buffer[--n] = cast(char)((v < 10) ? v + '0' : v + format - ('X' - 'A' + 10));
              value >>= 4;
              }

        return buffer[n .. $];
}

/*******************************************************************************

*******************************************************************************/

char[] longToBinString (char[] buffer, ulong value, int digits)
{
        if (digits < 1)
            digits = 1;

        auto n = buffer.length;
        while (--digits >= 0 || value != 0)
              {
              buffer[--n] = cast(char)((value & 1) + '0');
              value >>= 1;
              }

        return buffer[n .. $];
}

/*******************************************************************************

*******************************************************************************/

char parseFormatSpecifier (const(char)[] format, out int length)
{
        int     i = -1;
        char    specifier;

        if (format.length)
           {
           auto s = format[0];

           if (s >= 'A' && s <= 'Z' || s >= 'a' && s <= 'z')
              {
              specifier = s;

              foreach (char c; format [1..$])
                       if (c >= '0' && c <= '9')
                          {
                          c -= '0';
                          if (i < 0)
                             i = c;
                          else
                             i = i * 10 + c;
                          }
                       else
                          break;
              }
           }
        else
           specifier = 'G';

        length = i;
        return specifier;
}

/*******************************************************************************

*******************************************************************************/

char[] formatInteger (char[] output, long value, const(char)[] format, NumberFormat nf)
{
        int     length;
        auto    specifier = parseFormatSpecifier (format, length);

        switch (specifier)
               {
               case 'g':
               case 'G':
                    if (length > 0)
                        break;
                    // Fall through.
                    goto case;
               case 'd':
               case 'D':
                    return longToString (output, value, length, nf.negativeSign);

               case 'x':
               case 'X':
                    return longToHexString (output, cast(ulong)value, length, specifier);

               case 'b':
               case 'B':
                    return longToBinString (output, cast(ulong)value, length);

               default:
                    break;
               }

        Result result = Result (output);
        Number number = Number (value);
        if (specifier != char.init)
            return toString (number, result, specifier, length, nf);

        return number.toStringFormat (result, format, nf);
}

/*******************************************************************************

*******************************************************************************/

private enum {
             EXP = 0x7ff,
             NAN_FLAG = 0x80000000,
             INFINITY_FLAG = 0x7fffffff,
             }

char[] formatDouble (char[] output, double value, const(char)[] format, NumberFormat nf)
{
        int length;
        int precision = 6;
        Result result = Result (output);
        char specifier = parseFormatSpecifier (format, length);

        switch (specifier)
               {
               case 'r':
               case 'R':
                    Number number = Number (value, 15);

                    if (number.scale == NAN_FLAG)
                        // Bad dup?
                        return nf.nanSymbol.dup;

                    if (number.scale == INFINITY_FLAG)
                         // Bad dup?
                        return number.sign ? nf.negativeInfinitySymbol.dup
                                           : nf.positiveInfinitySymbol.dup;

                    double d;
                    number.toDouble(d);
                    if (d == value)
                        return toString (number, result, 'G', 15, nf);

                    number = Number(value, 17);
                    return toString (number, result, 'G', 17, nf);

               case 'g':
               case 'G':
                    if (length > 15)
                        precision = 17;
                    // Fall through.
                    goto default;
               default:
                    break;
        }

        Number number = Number(value, precision);

        if (number.scale == NAN_FLAG)
            // Bad dup?
            return nf.nanSymbol.dup;

        if (number.scale == INFINITY_FLAG)
            // Bad dup?
            return number.sign ? nf.negativeInfinitySymbol.dup
                               : nf.positiveInfinitySymbol.dup;

        if (specifier != char.init)
            return toString (number, result, specifier, length, nf);

        return number.toStringFormat (result, format, nf);
}

/*******************************************************************************

*******************************************************************************/

void formatGeneral (ref Number number, ref Result target, int length, char format, NumberFormat nf)
{
        int pos = number.scale;

        auto p = number.digits.ptr;
        if (pos > 0)
           {
           while (pos > 0)
                 {
                 target ~= (*p != '\0') ? *p++ : '0';
                 pos--;
                 }
           }
        else
           target ~= '0';

        if (*p != '\0')
           {
           target ~= nf.numberDecimalSeparator;
           while (pos < 0)
                 {
                 target ~= '0';
                 pos++;
                 }

           while (*p != '\0')
                  target ~= *p++;
           }
}

/*******************************************************************************

*******************************************************************************/

void formatNumber (ref Number number, ref Result target, int length, NumberFormat nf)
{
        const(char)[] format = number.sign ? negativeNumberFormats[nf.numberNegativePattern]
                                           : positiveNumberFormat;

        // Parse the format.
        foreach (char c; format)
                {
                switch (c)
                       {
                       case '#':
                            formatFixed (number, target, length, nf.numberGroupSizes,
                                         nf.numberDecimalSeparator, nf.numberGroupSeparator);
                            break;

                       case '-':
                            target ~= nf.negativeSign;
                            break;

                       default:
                            target ~= c;
                            break;
                       }
                }
}

/*******************************************************************************

*******************************************************************************/

void formatCurrency (ref Number number, ref Result target, int length, NumberFormat nf)
{
        const(char)[] format = number.sign ? negativeCurrencyFormats[nf.currencyNegativePattern]
                                           : positiveCurrencyFormats[nf.currencyPositivePattern];

        // Parse the format.
        foreach (char c; format)
                {
                switch (c)
                       {
                       case '#':
                            formatFixed (number, target, length, nf.currencyGroupSizes,
                                         nf.currencyDecimalSeparator, nf.currencyGroupSeparator);
                            break;

                       case '-':
                            target ~= nf.negativeSign;
                            break;

                       case '$':
                            target ~= nf.currencySymbol;
                            break;

                       default:
                            target ~= c;
                            break;
                       }
                }
}

/*******************************************************************************

*******************************************************************************/

void formatFixed (ref Number number, ref Result target, int length,
                  const(int)[] groupSizes, const(char)[] decimalSeparator, const(char)[] groupSeparator)
{
        int pos = number.scale;
        auto p = number.digits.ptr;

        if (pos > 0)
           {
           if (groupSizes.length != 0)
              {
              // Calculate whether we have enough digits to format.
              int count = groupSizes[0];
              int index, size;

              while (pos > count)
                    {
                    size = groupSizes[index];
                    if (size == 0)
                        break;

                    if (index < groupSizes.length - 1)
                       index++;

                    count += groupSizes[index];
                    }

              size = (count == 0) ? 0 : groupSizes[0];

              // Insert the separator according to groupSizes.
              int end = charTerm(p);
              int start = (pos < end) ? pos : end;


              const(char)[] separator = groupSeparator;
              index = 0;

              // questionable: use the back end of the output buffer to
              // format the separators, and then copy back to start
              char[] temp = target.scratch();
              auto ii = temp.length;

              for (int c, i = pos - 1; i >= 0; i--)
                  {
                  temp[--ii] = (i < start) ? number.digits[i] : '0';
                  if (size > 0)
                     {
                     c++;
                     if (c == size && i != 0)
                        {
                        size_t iii = ii - separator.length;
                        temp[iii .. ii] = separator;
                        ii = iii;

                        if (index < groupSizes.length - 1)
                            size = groupSizes[++index];

                        c = 0;
                        }
                     }
                  }
              target ~= temp[ii..$];
              p += start;
              }
           else
              {
              while (pos > 0)
                    {
                    target ~= (*p != '\0') ? *p++ : '0';
                    pos--;
                    }
              }
           }
        else
           // Negative scale.
           target ~= '0';

        if (length > 0)
           {
           target ~= decimalSeparator;
           while (pos < 0 && length > 0)
                 {
                 target ~= '0';
                 pos++;
                 length--;
                 }

           while (length > 0)
                 {
                 target ~= (*p != '\0') ? *p++ : '0';
                 length--;
                 }
           }
}

/******************************************************************************

******************************************************************************/

char[] toString (ref Number number, ref Result result, char format, int length, NumberFormat nf)
{
        switch (format)
               {
               case 'c':
               case 'C':
                     // Currency
                     if (length < 0)
                         length = nf.currencyDecimalDigits;

                     number.round(number.scale + length);
                     formatCurrency (number, result, length, nf);
                     break;

               case 'f':
               case 'F':
                     // Fixed
                     if (length < 0)
                         length = nf.numberDecimalDigits;

                     number.round(number.scale + length);
                     if (number.sign)
                         result ~= nf.negativeSign;

                     formatFixed (number, result, length, null, nf.numberDecimalSeparator, null);
                     break;

               case 'n':
               case 'N':
                     // Number
                        if (length < 0)
                            length = nf.numberDecimalDigits;

                     number.round (number.scale + length);
                     formatNumber (number, result, length, nf);
                     break;

               case 'g':
               case 'G':
                     // General
                     if (length < 1)
                         length = number.precision;

                     number.round(length);
                     if (number.sign)
                         result ~= nf.negativeSign;

                     formatGeneral (number, result, length, (format == 'g') ? 'e' : 'E', nf);
                     break;

               default:
                     return "{invalid FP format specifier '".dup ~ format ~ "'}".dup;
               }
        return result.get();
}


/*******************************************************************************

*******************************************************************************/

private struct Number
{
        int scale;
        bool sign;
        int precision;
        char[32] digits = void;

        /**********************************************************************

        **********************************************************************/

        private static Number opCall (long value)
        {
                Number number;
                number.precision = 20;

                if (value < 0)
                   {
                   number.sign = true;
                   value = -value;
                   }

                char[20] buffer = void;
                int n = buffer.length;

                while (value != 0)
                      {
                      buffer[--n] = cast(char)(value % 10 + '0');
                      value /= 10;
                      }

                int end = number.scale = -(n - cast(int)buffer.length);
                number.digits[0 .. end] = buffer[n .. n + end];
                number.digits[end] = '\0';

                return number;
        }

        /**********************************************************************

        **********************************************************************/

        private static Number opCall (double value, int precision)
        {
                Number number;
                number.precision = precision;

                auto p = number.digits.ptr;
                long bits = *cast(long*) & value;
                long mant = bits & 0x000FFFFFFFFFFFFFL;
                int exp = cast(int)((bits >> 52) & EXP);

                if (exp == EXP)
                   {
                   number.scale = (mant != 0) ? NAN_FLAG : INFINITY_FLAG;
                   if (((bits >> 63) & 1) != 0)
                         number.sign = true;
                   }
                else
                   {
                   // Get the digits, decimal point and sign.
                   char* chars = ecvt(value, number.precision, number.scale, number.sign);
                   if (*chars != '\0')
                      {
                      while (*chars != '\0')
                             *p++ = *chars++;
                      }
                   }

                *p = '\0';
                return number;
        }

        /**********************************************************************

        **********************************************************************/

        private bool toDouble(out double value)
        {
                enum   ulong[] pow10 =
                        [
                        0xa000000000000000UL,
                        0xc800000000000000UL,
                        0xfa00000000000000UL,
                        0x9c40000000000000UL,
                        0xc350000000000000UL,
                        0xf424000000000000UL,
                        0x9896800000000000UL,
                        0xbebc200000000000UL,
                        0xee6b280000000000UL,
                        0x9502f90000000000UL,
                        0xba43b74000000000UL,
                        0xe8d4a51000000000UL,
                        0x9184e72a00000000UL,
                        0xb5e620f480000000UL,
                        0xe35fa931a0000000UL,
                        0xcccccccccccccccdUL,
                        0xa3d70a3d70a3d70bUL,
                        0x83126e978d4fdf3cUL,
                        0xd1b71758e219652eUL,
                        0xa7c5ac471b478425UL,
                        0x8637bd05af6c69b7UL,
                        0xd6bf94d5e57a42beUL,
                        0xabcc77118461ceffUL,
                        0x89705f4136b4a599UL,
                        0xdbe6fecebdedd5c2UL,
                        0xafebff0bcb24ab02UL,
                        0x8cbccc096f5088cfUL,
                        0xe12e13424bb40e18UL,
                        0xb424dc35095cd813UL,
                        0x901d7cf73ab0acdcUL,
                        0x8e1bc9bf04000000UL,
                        0x9dc5ada82b70b59eUL,
                        0xaf298d050e4395d6UL,
                        0xc2781f49ffcfa6d4UL,
                        0xd7e77a8f87daf7faUL,
                        0xefb3ab16c59b14a0UL,
                        0x850fadc09923329cUL,
                        0x93ba47c980e98cdeUL,
                        0xa402b9c5a8d3a6e6UL,
                        0xb616a12b7fe617a8UL,
                        0xca28a291859bbf90UL,
                        0xe070f78d39275566UL,
                        0xf92e0c3537826140UL,
                        0x8a5296ffe33cc92cUL,
                        0x9991a6f3d6bf1762UL,
                        0xaa7eebfb9df9de8aUL,
                        0xbd49d14aa79dbc7eUL,
                        0xd226fc195c6a2f88UL,
                        0xe950df20247c83f8UL,
                        0x81842f29f2cce373UL,
                        0x8fcac257558ee4e2UL,
                        ];

                enum   uint[] pow10Exp =
                        [
                        4, 7, 10, 14, 17, 20, 24, 27, 30, 34,
                        37, 40, 44, 47, 50, 54, 107, 160, 213, 266,
                        319, 373, 426, 479, 532, 585, 638, 691, 745, 798,
                        851, 904, 957, 1010, 1064, 1117
                        ];

                uint getDigits(char* p, int len)
                {
                        char* end = p + len;
                        uint r = *p - '0';
                        p++;
                        while (p < end)
                              {
                              r = 10 * r + *p - '0';
                              p++;
                              }
                        return r;
                }

                ulong mult64(uint val1, uint val2)
                {
                        return cast(ulong)val1 * cast(ulong)val2;
                }

                ulong mult64L(ulong val1, ulong val2)
                {
                        ulong v = mult64(cast(uint)(val1 >> 32), cast(uint)(val2 >> 32));
                        v += mult64(cast(uint)(val1 >> 32), cast(uint)val2) >> 32;
                        v += mult64(cast(uint)val1, cast(uint)(val2 >> 32)) >> 32;
                        return v;
                }

                auto p = digits.ptr;
                int count = charTerm(p);
                int left = count;

                while (*p == '0')
                      {
                      left--;
                      p++;
                      }

                // If the digits consist of nothing but zeros...
                if (left == 0)
                   {
                   value = 0.0;
                   return true;
                   }

                // Get digits, 9 at a time.
                int n = (left > 9) ? 9 : left;
                left -= n;
                ulong bits = getDigits(p, n);
                if (left > 0)
                   {
                   n = (left > 9) ? 9 : left;
                   left -= n;
                   bits = mult64(cast(uint)bits, cast(uint)(pow10[n - 1] >>> (64 - pow10Exp[n - 1])));
                   bits += getDigits(p + 9, n);
                   }

                int scale = this.scale - (count - left);
                int s = (scale < 0) ? -scale : scale;

                if (s >= 352)
                   {
                   *cast(long*)&value = (scale > 0) ? 0x7FF0000000000000 : 0;
                   return false;
                   }

                // Normalise mantissa and bits.
                int bexp = 64;
                int nzero;
                if ((bits >> 32) != 0)
                     nzero = 32;

                if ((bits >> (16 + nzero)) != 0)
                     nzero += 16;

                if ((bits >> (8 + nzero)) != 0)
                     nzero += 8;

                if ((bits >> (4 + nzero)) != 0)
                     nzero += 4;

                if ((bits >> (2 + nzero)) != 0)
                     nzero += 2;

                if ((bits >> (1 + nzero)) != 0)
                     nzero++;

                if ((bits >> nzero) != 0)
                     nzero++;

                bits <<= 64 - nzero;
                bexp -= 64 - nzero;

                // Get decimal exponent.
                if ((s & 15) != 0)
                   {
                   int expMult = pow10Exp[(s & 15) - 1];
                   bexp += (scale < 0) ? ( -expMult + 1) : expMult;
                   bits = mult64L(bits, pow10[(s & 15) + ((scale < 0) ? 15 : 0) - 1]);
                   if ((bits & 0x8000000000000000L) == 0)
                      {
                      bits <<= 1;
                      bexp--;
                      }
                   }

                if ((s >> 4) != 0)
                   {
                   int expMult = pow10Exp[15 + ((s >> 4) - 1)];
                   bexp += (scale < 0) ? ( -expMult + 1) : expMult;
                   bits = mult64L(bits, pow10[30 + ((s >> 4) + ((scale < 0) ? 21 : 0) - 1)]);
                   if ((bits & 0x8000000000000000L) == 0)
                      {
                      bits <<= 1;
                      bexp--;
                      }
                   }

                // Round and scale.
                if ((cast(uint)bits & (1 << 10)) != 0)
                   {
                   bits += (1 << 10) - 1 + (bits >>> 11) & 1;
                   bits >>= 11;
                   if (bits == 0)
                       bexp++;
                   }
                else
                   bits >>= 11;

                bexp += 1022;
                if (bexp <= 0)
                   {
                   if (bexp < -53)
                       bits = 0;
                   else
                      bits >>= ( -bexp + 1);
                   }
                bits = (cast(ulong)bexp << 52) + (bits & 0x000FFFFFFFFFFFFFL);

                if (sign)
                    bits |= 0x8000000000000000L;

                value = *cast(double*) & bits;
                return true;
        }



        /**********************************************************************

        **********************************************************************/

        private char[] toStringFormat (ref Result result, const(char)[] format, NumberFormat nf)
        {
                bool hasGroups;
                int groupCount;
                int groupPos = -1, pointPos = -1;
                int first = int.max, last, count;
                bool scientific;
                int n;
                char c;

                while (n < format.length)
                      {
                      c = format[n++];
                      switch (c)
                             {
                             case '#':
                                  count++;
                                  break;

                             case '0':
                                  if (first == int.max)
                                      first = count;
                                  count++;
                                  last = count;
                                  break;

                             case '.':
                                  if (pointPos < 0)
                                      pointPos = count;
                                  break;

                             case ',':
                                  if (count > 0 && pointPos < 0)
                                     {
                                     if (groupPos >= 0)
                                        {
                                        if (groupPos == count)
                                           {
                                           groupCount++;
                                           break;
                                           }
                                        hasGroups = true;
                                        }
                                     groupPos = count;
                                     groupCount = 1;
                                     }
                                  break;

                             case '\'':
                             case '\"':
                                   while (n < format.length && format[n++] != c)
                                         {}
                                   break;

                             case '\\':
                                  if (n < format.length)
                                      n++;
                                  break;

                             default:
                                  break;
                             }
                      }

                if (pointPos < 0)
                    pointPos = count;

                int adjust;
                if (groupPos >= 0)
                   {
                   if (groupPos == pointPos)
                       adjust -= groupCount * 3;
                   else
                      hasGroups = true;
                   }

                if (digits[0] != '\0')
                   {
                   scale += adjust;
                   round(scientific ? count : scale + count - pointPos);
                   }

                first = (first < pointPos) ? pointPos - first : 0;
                last = (last > pointPos) ? pointPos - last : 0;

                int pos = pointPos;
                int extra;
                if (!scientific)
                   {
                   pos = (scale > pointPos) ? scale : pointPos;
                   extra = scale - pointPos;
                   }

                const(char)[] groupSeparator = nf.numberGroupSeparator;
                const(char)[] decimalSeparator = nf.numberDecimalSeparator;

                // Work out the positions of the group separator.
                int[] groupPositions;
                int groupIndex = -1;
                if (hasGroups)
                   {
                   if (nf.numberGroupSizes.length == 0)
                       hasGroups = false;
                   else
                      {
                      int groupSizesTotal = nf.numberGroupSizes[0];
                      int groupSize = groupSizesTotal;
                      int digitsTotal = pos + ((extra < 0) ? extra : 0);
                      int digitCount = (first > digitsTotal) ? first : digitsTotal;

                      int sizeIndex;
                      while (digitCount > groupSizesTotal)
                            {
                            if (groupSize == 0)
                                break;

                            groupPositions ~= groupSizesTotal;
                            groupIndex++;

                            if (sizeIndex < nf.numberGroupSizes.length - 1)
                                groupSize = nf.numberGroupSizes[++sizeIndex];

                            groupSizesTotal += groupSize;
                            }
                      }
                }

                //char[] result;
                if (sign)
                    result ~= nf.negativeSign;

                auto p = digits.ptr;
                n = 0;
                bool pointWritten;

                while (n < format.length)
                      {
                      c = format[n++];
                      if (extra > 0 && (c == '#' || c == '0' || c == '.'))
                         {
                         while (extra > 0)
                               {
                               result ~= (*p != '\0') ? *p++ : '0';

                               if (hasGroups && pos > 1 && groupIndex >= 0)
                                  {
                                  if (pos == groupPositions[groupIndex] + 1)
                                     {
                                     result ~= groupSeparator;
                                     groupIndex--;
                                     }
                                  }
                               pos--;
                               extra--;
                               }
                         }

                      switch (c)
                             {
                             case '#':
                             case '0':
                                  if (extra < 0)
                                     {
                                     extra++;
                                     c = (pos <= first) ? '0' : char.init;
                                     }
                                  else
                                     c = (*p != '\0') ? *p++ : pos > last ? '0' : char.init;

                                  if (c != char.init)
                                     {
                                     result ~= c;

                                     if (hasGroups && pos > 1 && groupIndex >= 0)
                                        {
                                        if (pos == groupPositions[groupIndex] + 1)
                                           {
                                           result ~= groupSeparator;
                                           groupIndex--;
                                           }
                                        }
                                     }
                                  pos--;
                                  break;

                             case '.':
                                  if (pos != 0 || pointWritten)
                                      break;
                                  if (last < 0 || (pointPos < count && *p != '\0'))
                                     {
                                     result ~= decimalSeparator;
                                     pointWritten = true;
                                     }
                                  break;

                             case ',':
                                  break;

                             case '\'':
                             case '\"':
                                  if (n < format.length)
                                      n++;
                                  break;

                             case '\\':
                                  if (n < format.length)
                                      result ~= format[n++];
                                  break;

                             default:
                                  result ~= c;
                                  break;
                             }
                      }
                return result.get();
        }

        /**********************************************************************

        **********************************************************************/

        private void round (int pos)
        {
                int index;
                while (index < pos && digits[index] != '\0')
                       index++;

                if (index == pos && digits[index] >= '5')
                   {
                   while (index > 0 && digits[index - 1] == '9')
                          index--;

                   if (index > 0)
                       digits[index - 1]++;
                   else
                      {
                      scale++;
                      digits[0] = '1';
                      index = 1;
                      }
                   }
                else
                   while (index > 0 && digits[index - 1] == '0')
                          index--;

                if (index == 0)
                   {
                   scale = 0;
                   sign = false;
                   }

                digits[index] = '\0';
        }
}