/* Test istream formatted input.

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

This file is part of the GNU MP Library test suite.

The GNU MP Library test suite is free software; you can redistribute it
and/or modify it under the terms of the GNU 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 test suite 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 General
Public License for more details.

You should have received a copy of the GNU General Public License along with
the GNU MP Library test suite.  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.

bool option_check_standard = false;


// 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.

bool putback_tellg_works = true;

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 = false;
    }
}


#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;
  bool       got_ok, want_ok;
  bool       got_eof, want_eof;
  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++)
    {
      size_t input_length = strlen (data[i].input);
      want_pos = (data[i].want_pos == -1
                  ? input_length : data[i].want_pos);
      want_eof = (want_pos == streampos(input_length));

      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.fail();
          got_eof = input.eof();
          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 (want_ok && got_eof != want_eof)
            {
              WRONG ("stdc++ operator>> wrong EOF state, check_mpz");
              cout << "  got_eof:  " << got_eof << "\n";
              cout << "  want_eof: " << want_eof << "\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.fail();
	got_eof = input.eof();
        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 (want_ok && got_eof != want_eof)
          {
            WRONG ("mpz operator>> wrong EOF state");
            cout << "  want_eof: " << want_eof << "\n";
            cout << "  got_eof:  " << got_eof << "\n";
            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 },

    { "123 /456",    3, "123",  (ios::fmtflags) 0 },
    { "123/ 456",    4,  NULL,  (ios::fmtflags) 0 },
    { "123/"    ,   -1,  NULL,  (ios::fmtflags) 0 },
    { "123 /456",    3, "123",  ios::skipws },
    { "123/ 456",    4,  NULL,  ios::skipws },
  };

  mpq_t      got, want;
  bool       got_ok, want_ok;
  bool       got_eof, want_eof;
  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++)
    {
      size_t input_length = strlen (data[i].input);
      want_pos = (data[i].want_pos == -1
                  ? input_length : data[i].want_pos);
      want_eof = (want_pos == streampos(input_length));

      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
          && strchr (data[i].input, '/') == NULL)
        {
          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.fail();
          got_eof = input.eof();
          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 (want_ok && got_eof != want_eof)
            {
              WRONG ("stdc++ operator>> wrong EOF state, check_mpq");
              cout << "  got_eof:  " << got_eof << "\n";
              cout << "  want_eof: " << want_eof << "\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.fail();
	got_eof = input.eof();
        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 (want_ok && got_eof != want_eof)
          {
            WRONG ("mpq operator>> wrong EOF state");
            cout << "  want_eof: " << want_eof << "\n";
            cout << "  got_eof:  " << got_eof << "\n";
            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;
  bool       got_ok, want_ok;
  bool       got_eof, want_eof;
  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++)
    {
      size_t input_length = strlen (data[i].input);
      want_pos = (data[i].want_pos == -1
                  ? input_length : data[i].want_pos);
      want_eof = (want_pos == streampos(input_length));

      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.fail();
          got_eof = input.eof();
          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 (want_ok && got_eof != want_eof)
            {
              WRONG ("stdc++ operator>> wrong EOF state, check_mpf");
              cout << "  got_eof:  " << got_eof << "\n";
              cout << "  want_eof: " << want_eof << "\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.fail();
	got_eof = input.eof();
        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 (want_ok && got_eof != want_eof)
          {
            WRONG ("mpf operator>> wrong EOF state");
            cout << "  want_eof: " << want_eof << "\n";
            cout << "  got_eof:  " << got_eof << "\n";
            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 = true;

  tests_start ();

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

  tests_end ();
  return 0;
}
