/* <decimal> for libdfp and redirect to system <decimal>

   Copyright (C) 2011-2015 Free Software Foundation, Inc.

   This file is part of the Decimal Floating Point C Library.

   Author(s): Ryan S. Arnold <rsa@us.ibm.com>

   The Decimal Floating Point C Library is free software; you can
   redistribute it and/or modify it under the terms of the GNU Lesser
   General Public License version 2.1.

   The Decimal Floating Point C Library is distributed in the hope that
   it will be useful, but WITHOUT ANY WARRANTY; without even the implied
   warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
   the GNU Lesser General Public License version 2.1 for more details.

   You should have received a copy of the GNU Lesser General Public
   License version 2.1 along with the Decimal Floating Point C Library;
   if not, write to the Free Software Foundation, Inc., 59 Temple Place,
   Suite 330, Boston, MA 02111-1307 USA.

   Please see libdfp/COPYING.txt for more information.  */

#ifndef _LIBDFP_DECIMAL_H
#define _LIBDFP_DECIMAL_H 1

#ifdef _GLIBCXX_DECIMAL_IMPL
# error "dfp/<decimal/decimal> should be #included before the system <decimal/decimal> header."
#endif

#ifdef __cplusplus

/* This needs to be declared before #include_next <decimal/decimal> so that
 * _Decimal types are declared when the system <decimal/decimal> pulls in
 * other headers which are overloaded by libdfp, e.g., wchar.h.  */
#include <float.h> /* Pick up _Decimal[32|64|128] typedefs.  */

/* Pick up the system <decimal>. Since C++ DFP support is currently only a
 * technical report the decimal header is in the non-default search path so
 * <decimal/decimal> must be used.  */
#include_next <decimal/decimal>
using namespace std::decimal;

#pragma GCC system_header

#include <ostream>
using std::ostream;

#include <istream>
using std::istream;

#include <string>
using std::string;

#include <iostream>
using namespace std;

#include <stdlib.h> /* Pick up the strtod[32|64|128] prototypes.  */
#include <stdio.h>  /* Pick up the sprintf prototype.  */
#include <limits.h> /* For CHAR_MAX.  */
#include <string.h> /* For memset.  */

template<unsigned int size>
struct FIND_DEC_MANT_DIG
{
  enum {RESULT = 0};

  static inline std::string get_fmt(char conv)
  {
     std::string spec = "";
     return spec;
  }
};

template <>
struct FIND_DEC_MANT_DIG<4>
{
  enum {RESULT = (__DEC32_MANT_DIG__) };
  static inline std::string get_fmt(char conv)
  {
     std::string spec = "H";
     return spec.append(1,conv);
  }
};
template <>
struct FIND_DEC_MANT_DIG<8>
{
  enum {RESULT = (__DEC64_MANT_DIG__) };
  static inline std::string get_fmt(char conv)
  {
     std::string spec = "D";
     return spec.append(1,conv);
  }

};
template <>
struct FIND_DEC_MANT_DIG<16>
{
  enum {RESULT = (__DEC128_MANT_DIG__) };
  static inline std::string get_fmt(char conv)
  {
     std::string spec = "DD";
     return spec.append(1,conv);
  }
};

/* Template meta-programming so we only have to write this code once for use
 * with each of the _Decimal[32|64|128] types.  */
template<class decimal_type>
class LIBDFP_META {
private:
public:
  static inline ostream & decimal_to_string(std::ostream &os, decimal_type &d)
    {
      /* If strbuf is big enough for a _Decimal128, it is big enough for the
       * other types as well, so just use the same size for all of them.
       *   1  (leading zero)
       * + 1  (.)
       * + 34 (__DEC128_MANT_DIG__)
       * + 1  (e)
       * + 1  (+/-)
       * + 4  (digits in __DEC128_MAX_EXP__)
       * + 1  "\n"
       * = 43 -> round up to a power of 2 = 64.  */

      char strbuf[64];

      ios_base::fmtflags flags = os.flags();
      unsigned int precision = os.precision();

      /* Anything over DEC_MANT_DIG can't be represented anyway.  */
      if (precision > DEC_MANT_DIG)
        precision = DEC_MANT_DIG;

      char conv = 'g';

      std::string fmtstr = "%";

      /* A strict reading of the draft DFP C++ spec indicates that a/A conv
         specifier can't be accompanied by a precision specifier.  */

      if (flags & ios::fixed && flags & ios::scientific)
	  conv = flags & ios::uppercase ? 'A' : 'a';
      else
        {
	  if (flags & ios::fixed)
	      /* Only used for "NAN" and "INF" rather than "nan" and "inf" for
	       * "%.*Df".  */
	      conv = flags & ios::uppercase ? 'F' : 'f';
	  else if (flags & ios::scientific)
	      conv = flags & ios::uppercase ? 'E' : 'e';
	  else if (flags & ios::uppercase)
	      conv = 'G';
	  fmtstr.append(".*");
	}

      /* Get the conv spec and length modifier based on size of the type.  */
      fmtstr.append(FIND_DEC_MANT_DIG<(sizeof(decimal_type))>::get_fmt(conv));

      /* Per the confusing reading of the draft DFP C++ Specification, a/A type
         are never passed with an accompanying precision.  This causes the
	 implicit precision in the type to be used for printing.  */
      if (flags & ios::fixed && flags & ios::scientific)
        sprintf (strbuf, fmtstr.c_str(), d.__getval());
      else
        sprintf (strbuf, fmtstr.c_str(), precision, d.__getval());
      os << strbuf;
      return os;
    }

  enum {DEC_MANT_DIG = FIND_DEC_MANT_DIG<(sizeof(decimal_type))>::RESULT};
};

/* Per ISO/IEC JTC1 SC22 WG21 N2732 - TR 24733: "Extension for the programming
 * language C++ to support decimal floating point arithmetic" define the
 * ostream and istream operators.  These are included in libdfp and NOT
 * libstdc++ because the ostream and istream operators rely upon libdfp
 * printf and strtod[32|64|128] support provided by libdfp.  */

namespace std
{
namespace decimal
{
  //ISO/IEC TR 24733 - 3.2.11 Formatted output:

  template <class charT, class traits>
  inline std::basic_ostream<charT, traits> &
    operator<<(std::basic_ostream<charT, traits> & os, decimal32 d)
    {
      LIBDFP_META<std::decimal::decimal32>::decimal_to_string(os, d);
      return os;
    }

  template <class charT, class traits>
  inline std::basic_ostream<charT, traits> &
    operator<<(std::basic_ostream<charT, traits> & os, decimal64 d)
    {
      LIBDFP_META<std::decimal::decimal64>::decimal_to_string(os, d);
      return os;
    }

  template <class charT, class traits>
  inline std::basic_ostream<charT, traits> &
    operator<<(std::basic_ostream<charT, traits> & os, decimal128 d)
    {
      LIBDFP_META<std::decimal::decimal128>::decimal_to_string(os, d);
      return os;
    }

//  ISO/IEC TR 27433 - 3.2.11 Formatted input:

  template <class charT, class traits>
    inline std::basic_istream<charT, traits> &
      operator>>(std::basic_istream<charT, traits> & is, decimal32 & d)
      {
	char buf[CHAR_MAX];
	memset(buf, '\0', CHAR_MAX);
	is.read(buf,CHAR_MAX);
	d.__setval(strtod32(buf, NULL));
	return is;
      }

  template <class charT, class traits>
    inline std::basic_istream<charT, traits> &
      operator>>(std::basic_istream<charT, traits> & is, decimal64 & d)
      {
	char buf[CHAR_MAX];
	memset(buf, '\0', CHAR_MAX);
	is.read(buf,CHAR_MAX);
	d.__setval(strtod64(buf, NULL));
	return is;
      }

  template <class charT, class traits>
    inline std::basic_istream<charT, traits> &
      operator>>(std::basic_istream<charT, traits> & is, decimal128 & d)
      {
	char buf[CHAR_MAX];
	memset(buf, '\0', CHAR_MAX);
	is.read(buf,CHAR_MAX);
	d.__setval(strtod128(buf, NULL));
	return is;
      }

} /* namespace decimal  */
} /* namespace std  */

#else
# warning "dfp/<decimal/decimal> should only be #included by C++ programs."
#endif /* __cplusplus  */

#endif /* _LIBDFP_DECIMAL_H  */
