/* Test istream formatted input.

Copyright 2001, 2002, 2003, 2004 Free Software Foundation, Inc.

This file is part of the GNU MP Library.

The GNU MP Library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 3 of the License, or (at your
option) any later version.

The GNU MP 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 for more details.

You should have received a copy of the GNU Lesser General Public License
along with the GNU MP Library.  If not, see http://www.gnu.org/licenses/.  */

#include <iostream>
#include <cstdlib>
#include <cstring>

#include "gmp.h"
#include "gmp-impl.h"
#include "tests.h"

using namespace std;


// Under option_check_standard, the various test cases for mpz operator>>
// are put through the standard operator>> for long, and likewise mpf
// operator>> is put through double.
//
// In g++ 3.3 this results in some printouts about the final position
// indicated for something like ".e123".  Our mpf code stops at the "e"
// since there's no mantissa digits, but g++ reads the whole thing and only
// then decides it's bad.

int   option_check_standard = 0;


// On some versions of g++ 2.96 it's been observed that putback() may leave
// tellg() unchanged.  We believe this is incorrect and presumably the
// result of a bug, since for instance it's ok in g++ 2.95 and g++ 3.3.  We
// detect the problem at runtime and disable affected checks.

int putback_tellg_works = 1;

void
check_putback_tellg (void)
{
  istringstream input ("hello");
  streampos  old_pos, new_pos;
  char  c;

  input.get(c);
  old_pos = input.tellg();
  input.putback(c);
  new_pos = input.tellg();

  if (old_pos == new_pos)
    {
      cout << "Warning, istringstream has a bug: putback() doesn't update tellg().\n";;
      cout << "Tests on tellg() will be skipped.\n";
      putback_tellg_works = 0;
    }
}


#define WRONG(str)                                              \
  do {                                                          \
    cout << str ", data[" << i << "]\n";                        \
    cout << "  input: \"" << data[i].input << "\"\n";           \
    cout << "  flags: " << hex << input.flags() << dec << "\n"; \
  } while (0)

void
check_mpz (void)
{
  static const struct {
    const char     *input;
    int            want_pos;
    const char     *want;
    ios::fmtflags  flags;

  } data[] = {

    { "0",      -1, "0",    (ios::fmtflags) 0 },
    { "123",    -1, "123",  (ios::fmtflags) 0 },
    { "0123",   -1, "83",   (ios::fmtflags) 0 },
    { "0x123",  -1, "291",  (ios::fmtflags) 0 },
    { "-123",   -1, "-123", (ios::fmtflags) 0 },
    { "-0123",  -1, "-83",  (ios::fmtflags) 0 },
    { "-0x123", -1, "-291", (ios::fmtflags) 0 },
    { "+123",   -1, "123", (ios::fmtflags) 0 },
    { "+0123",  -1, "83",  (ios::fmtflags) 0 },
    { "+0x123", -1, "291", (ios::fmtflags) 0 },

    { "0",     -1, "0",    ios::dec },
    { "1f",     1, "1",    ios::dec },
    { "011f",   3, "11",   ios::dec },
    { "123",   -1, "123",  ios::dec },
    { "-1f",    2, "-1",   ios::dec },
    { "-011f",  4, "-11",  ios::dec },
    { "-123",  -1, "-123", ios::dec },
    { "+1f",    2, "1",    ios::dec },
    { "+011f",  4, "11",   ios::dec },
    { "+123",  -1, "123",  ios::dec },

    { "0",    -1, "0",   ios::oct },
    { "123",  -1, "83",  ios::oct },
    { "-123", -1, "-83", ios::oct },
    { "+123", -1, "83",  ios::oct },

    { "0",    -1, "0",    ios::hex },
    { "123",  -1, "291",  ios::hex },
    { "ff",   -1, "255",  ios::hex },
    { "FF",   -1, "255",  ios::hex },
    { "-123", -1, "-291", ios::hex },
    { "-ff",  -1, "-255", ios::hex },
    { "-FF",  -1, "-255", ios::hex },
    { "+123", -1, "291",  ios::hex },
    { "+ff",  -1, "255",  ios::hex },
    { "+FF",  -1, "255",  ios::hex },
    { "ab",   -1, "171",  ios::hex },
    { "cd",   -1, "205",  ios::hex },
    { "ef",   -1, "239",  ios::hex },

    { " 123",  0, NULL,  (ios::fmtflags) 0 },   // not without skipws
    { " 123", -1, "123", ios::skipws },
  };

  mpz_t      got, want;
  int        got_ok, want_ok;
  long       got_si, want_si;
  streampos  init_tellg, got_pos, want_pos;

  mpz_init (got);
  mpz_init (want);

  for (size_t i = 0; i < numberof (data); i++)
    {
      want_pos = (data[i].want_pos == -1
                  ? strlen (data[i].input) : data[i].want_pos);

      want_ok = (data[i].want != NULL);

      if (data[i].want != NULL)
        mpz_set_str_or_abort (want, data[i].want, 0);
      else
        mpz_set_ui (want, 0L);

      if (option_check_standard && mpz_fits_slong_p (want))
        {
          istringstream  input (data[i].input);
          input.flags (data[i].flags);
          init_tellg = input.tellg();
          want_si = mpz_get_si (want);

          input >> got_si;
          got_ok = (input ? 1 : 0);
          input.clear();
          got_pos = input.tellg() - init_tellg;

          if (got_ok != want_ok)
            {
              WRONG ("stdc++ operator>> wrong status, check_mpz");
              cout << "  want_ok: " << want_ok << "\n";
              cout << "  got_ok:  " << got_ok << "\n";
            }
          if (want_ok && got_si != want_si)
            {
              WRONG ("stdc++ operator>> wrong result, check_mpz");
              cout << "  got_si:  " << got_si << "\n";
              cout << "  want_si: " << want_si << "\n";
            }
          if (putback_tellg_works && got_pos != want_pos)
            {
              WRONG ("stdc++ operator>> wrong position, check_mpz");
              cout << "  want_pos: " << want_pos << "\n";
              cout << "  got_pos:  " << got_pos << "\n";
            }
        }

      {
        istringstream  input (data[i].input);
        input.flags (data[i].flags);
        init_tellg = input.tellg();

        mpz_set_ui (got, 0xDEAD);
        input >> got;
        got_ok = (input ? 1 : 0);
        input.clear();
        got_pos = input.tellg() - init_tellg;

        if (got_ok != want_ok)
          {
            WRONG ("mpz operator>> wrong status");
            cout << "  want_ok: " << want_ok << "\n";
            cout << "  got_ok:  " << got_ok << "\n";
            abort ();
          }
        if (want_ok && mpz_cmp (got, want) != 0)
          {
            WRONG ("mpz operator>> wrong result");
            mpz_trace ("  got ", got);
            mpz_trace ("  want", want);
            abort ();
          }
        if (putback_tellg_works && got_pos != want_pos)
          {
            WRONG ("mpz operator>> wrong position");
            cout << "  want_pos: " << want_pos << "\n";
            cout << "  got_pos:  " << got_pos << "\n";
            abort ();
          }
      }
    }

  mpz_clear (got);
  mpz_clear (want);
}

void
check_mpq (void)
{
  static const struct {
    const char     *input;
    int            want_pos;
    const char     *want;
    ios::fmtflags  flags;

  } data[] = {

    { "0",   -1, "0", (ios::fmtflags) 0 },
    { "00",  -1, "0", (ios::fmtflags) 0 },
    { "0x0", -1, "0", (ios::fmtflags) 0 },

    { "123/456",   -1, "123/456", ios::dec },
    { "0123/456",  -1, "123/456", ios::dec },
    { "123/0456",  -1, "123/456", ios::dec },
    { "0123/0456", -1, "123/456", ios::dec },

    { "123/456",   -1, "83/302", ios::oct },
    { "0123/456",  -1, "83/302", ios::oct },
    { "123/0456",  -1, "83/302", ios::oct },
    { "0123/0456", -1, "83/302", ios::oct },

    { "ab",   -1, "171",  ios::hex },
    { "cd",   -1, "205",  ios::hex },
    { "ef",   -1, "239",  ios::hex },

    { "0/0",     -1, "0/0", (ios::fmtflags) 0 },
    { "5/8",     -1, "5/8", (ios::fmtflags) 0 },
    { "0x5/0x8", -1, "5/8", (ios::fmtflags) 0 },

    { "123/456",   -1, "123/456",  (ios::fmtflags) 0 },
    { "123/0456",  -1, "123/302",  (ios::fmtflags) 0 },
    { "123/0x456", -1, "123/1110", (ios::fmtflags) 0 },
    { "123/0X456", -1, "123/1110", (ios::fmtflags) 0 },

    { "0123/123",   -1, "83/123", (ios::fmtflags) 0 },
    { "0123/0123",  -1, "83/83",  (ios::fmtflags) 0 },
    { "0123/0x123", -1, "83/291", (ios::fmtflags) 0 },
    { "0123/0X123", -1, "83/291", (ios::fmtflags) 0 },

    { "0x123/123",   -1, "291/123", (ios::fmtflags) 0 },
    { "0X123/0123",  -1, "291/83",  (ios::fmtflags) 0 },
    { "0x123/0x123", -1, "291/291", (ios::fmtflags) 0 },

    { " 123",  0, NULL,  (ios::fmtflags) 0 },   // not without skipws
    { " 123", -1, "123", ios::skipws },
  };

  mpq_t      got, want;
  int        got_ok, want_ok;
  long       got_si, want_si;
  streampos  init_tellg, got_pos, want_pos;

  mpq_init (got);
  mpq_init (want);

  for (size_t i = 0; i < numberof (data); i++)
    {
      want_pos = (data[i].want_pos == -1
                  ? strlen (data[i].input) : data[i].want_pos);

      want_ok = (data[i].want != NULL);

      if (data[i].want != NULL)
        mpq_set_str_or_abort (want, data[i].want, 0);
      else
        mpq_set_ui (want, 0L, 1L);

      if (option_check_standard
          && mpz_fits_slong_p (mpq_numref(want))
          && mpz_cmp_ui (mpq_denref(want), 1L) == 0)
        {
          istringstream  input (data[i].input);
          input.flags (data[i].flags);
          init_tellg = input.tellg();
          want_si = mpz_get_si (mpq_numref(want));

          input >> got_si;
          got_ok = (input ? 1 : 0);
          input.clear();
          got_pos = input.tellg() - init_tellg;

          if (got_ok != want_ok)
            {
              WRONG ("stdc++ operator>> wrong status, check_mpq");
              cout << "  want_ok: " << want_ok << "\n";
              cout << "  got_ok:  " << got_ok << "\n";
            }
          if (want_ok && want_si != got_si)
            {
              WRONG ("stdc++ operator>> wrong result, check_mpq");
              cout << "  got_si:  " << got_si << "\n";
              cout << "  want_si: " << want_si << "\n";
            }
          if (putback_tellg_works && got_pos != want_pos)
            {
              WRONG ("stdc++ operator>> wrong position, check_mpq");
              cout << "  want_pos: " << want_pos << "\n";
              cout << "  got_pos:  " << got_pos << "\n";
            }
        }

      {
        istringstream  input (data[i].input);
        input.flags (data[i].flags);
        init_tellg = input.tellg();
        mpq_set_si (got, 0xDEAD, 0xBEEF);

        input >> got;
        got_ok = (input ? 1 : 0);
        input.clear();
        got_pos = input.tellg() - init_tellg;

        if (got_ok != want_ok)
          {
            WRONG ("mpq operator>> wrong status");
            cout << "  want_ok: " << want_ok << "\n";
            cout << "  got_ok:  " << got_ok << "\n";
            abort ();
          }
        // don't use mpq_equal, since we allow non-normalized values to be
        // read, which can trigger ASSERTs in mpq_equal
        if (want_ok && (mpz_cmp (mpq_numref (got), mpq_numref(want)) != 0
                        || mpz_cmp (mpq_denref (got), mpq_denref(want)) != 0))
          {
            WRONG ("mpq operator>> wrong result");
            mpq_trace ("  got ", got);
            mpq_trace ("  want", want);
            abort ();
          }
        if (putback_tellg_works && got_pos != want_pos)
          {
            WRONG ("mpq operator>> wrong position");
            cout << "  want_pos: " << want_pos << "\n";
            cout << "  got_pos:  " << got_pos << "\n";
            abort ();
          }
      }
    }

  mpq_clear (got);
  mpq_clear (want);
}


void
check_mpf (void)
{
  static const struct {
    const char     *input;
    int            want_pos;
    const char     *want;
    ios::fmtflags  flags;

  } data[] = {

    { "0",      -1, "0", (ios::fmtflags) 0 },
    { "+0",     -1, "0", (ios::fmtflags) 0 },
    { "-0",     -1, "0", (ios::fmtflags) 0 },
    { "0.0",    -1, "0", (ios::fmtflags) 0 },
    { "0.",     -1, "0", (ios::fmtflags) 0 },
    { ".0",     -1, "0", (ios::fmtflags) 0 },
    { "+.0",    -1, "0", (ios::fmtflags) 0 },
    { "-.0",    -1, "0", (ios::fmtflags) 0 },
    { "+0.00",  -1, "0", (ios::fmtflags) 0 },
    { "-0.000", -1, "0", (ios::fmtflags) 0 },
    { "+0.00",  -1, "0", (ios::fmtflags) 0 },
    { "-0.000", -1, "0", (ios::fmtflags) 0 },
    { "0.0e0",  -1, "0", (ios::fmtflags) 0 },
    { "0.e0",   -1, "0", (ios::fmtflags) 0 },
    { ".0e0",   -1, "0", (ios::fmtflags) 0 },
    { "0.0e-0", -1, "0", (ios::fmtflags) 0 },
    { "0.e-0",  -1, "0", (ios::fmtflags) 0 },
    { ".0e-0",  -1, "0", (ios::fmtflags) 0 },
    { "0.0e+0", -1, "0", (ios::fmtflags) 0 },
    { "0.e+0",  -1, "0", (ios::fmtflags) 0 },
    { ".0e+0",  -1, "0", (ios::fmtflags) 0 },

    { "1",  -1,  "1", (ios::fmtflags) 0 },
    { "+1", -1,  "1", (ios::fmtflags) 0 },
    { "-1", -1, "-1", (ios::fmtflags) 0 },

    { " 0",  0,  NULL, (ios::fmtflags) 0 },  // not without skipws
    { " 0",  -1, "0", ios::skipws },
    { " +0", -1, "0", ios::skipws },
    { " -0", -1, "0", ios::skipws },

    { "+-123", 1, NULL, (ios::fmtflags) 0 },
    { "-+123", 1, NULL, (ios::fmtflags) 0 },
    { "1e+-123", 3, NULL, (ios::fmtflags) 0 },
    { "1e-+123", 3, NULL, (ios::fmtflags) 0 },

    { "e123",   0, NULL, (ios::fmtflags) 0 }, // at least one mantissa digit
    { ".e123",  1, NULL, (ios::fmtflags) 0 },
    { "+.e123", 2, NULL, (ios::fmtflags) 0 },
    { "-.e123", 2, NULL, (ios::fmtflags) 0 },

    { "123e",   4, NULL, (ios::fmtflags) 0 }, // at least one exponent digit
    { "123e-",  5, NULL, (ios::fmtflags) 0 },
    { "123e+",  5, NULL, (ios::fmtflags) 0 },
  };

  mpf_t      got, want;
  int        got_ok, want_ok;
  double     got_d, want_d;
  streampos  init_tellg, got_pos, want_pos;

  mpf_init (got);
  mpf_init (want);

  for (size_t i = 0; i < numberof (data); i++)
    {
      want_pos = (data[i].want_pos == -1
                  ? strlen (data[i].input) : data[i].want_pos);

      want_ok = (data[i].want != NULL);

      if (data[i].want != NULL)
        mpf_set_str_or_abort (want, data[i].want, 0);
      else
        mpf_set_ui (want, 0L);

      want_d = mpf_get_d (want);
      if (option_check_standard && mpf_cmp_d (want, want_d) == 0)
        {
          istringstream  input (data[i].input);
          input.flags (data[i].flags);
          init_tellg = input.tellg();

          input >> got_d;
          got_ok = (input ? 1 : 0);
          input.clear();
          got_pos = input.tellg() - init_tellg;

          if (got_ok != want_ok)
            {
              WRONG ("stdc++ operator>> wrong status, check_mpf");
              cout << "  want_ok: " << want_ok << "\n";
              cout << "  got_ok:  " << got_ok << "\n";
            }
          if (want_ok && want_d != got_d)
            {
              WRONG ("stdc++ operator>> wrong result, check_mpf");
              cout << "  got:   " << got_d << "\n";
              cout << "  want:  " << want_d << "\n";
            }
          if (putback_tellg_works && got_pos != want_pos)
            {
              WRONG ("stdc++ operator>> wrong position, check_mpf");
              cout << "  want_pos: " << want_pos << "\n";
              cout << "  got_pos:  " << got_pos << "\n";
            }
        }

      {
        istringstream  input (data[i].input);
        input.flags (data[i].flags);
        init_tellg = input.tellg();

        mpf_set_ui (got, 0xDEAD);
        input >> got;
        got_ok = (input ? 1 : 0);
        input.clear();
        got_pos = input.tellg() - init_tellg;

        if (got_ok != want_ok)
          {
            WRONG ("mpf operator>> wrong status");
            cout << "  want_ok: " << want_ok << "\n";
            cout << "  got_ok:  " << got_ok << "\n";
            abort ();
          }
        if (want_ok && mpf_cmp (got, want) != 0)
          {
            WRONG ("mpf operator>> wrong result");
            mpf_trace ("  got ", got);
            mpf_trace ("  want", want);
            abort ();
          }
        if (putback_tellg_works && got_pos != want_pos)
          {
            WRONG ("mpf operator>> wrong position");
            cout << "  want_pos: " << want_pos << "\n";
            cout << "  got_pos:  " << got_pos << "\n";
            abort ();
          }
      }
    }

  mpf_clear (got);
  mpf_clear (want);
}



int
main (int argc, char *argv[])
{
  if (argc > 1 && strcmp (argv[1], "-s") == 0)
    option_check_standard = 1;

  tests_start ();

  check_putback_tellg ();
  check_mpz ();
  check_mpq ();
  check_mpf ();

  tests_end ();
  return 0;
}
