/****************************************************************************
 * Core Library Version 1.7, August 2004
 * Copyright (c) 1995-2004 Exact Computation Project
 * All rights reserved.
 *
 * This file is part of CGAL (www.cgal.org).
 *
 * File: CoreIO.cpp
 *
 * Written by
 *       Zilin Du <zilin@cs.nyu.edu>
 *       Chee Yap <yap@cs.nyu.edu>
 *
 * WWW URL: http://cs.nyu.edu/exact/
 * Email: exact@cs.nyu.edu
 *
 * $URL: https://github.com/CGAL/cgal/blob/v5.5.2/CGAL_Core/include/CGAL/CORE/CoreIO_impl.h $
 * $Id: CoreIO_impl.h 8d4aaa9 2022-11-30T11:55:32+00:00 Andreas Fabri
 * SPDX-License-Identifier: LGPL-3.0-or-later
 ***************************************************************************/

#ifndef _COREIO_IMPL_H_
#define _COREIO_IMPL_H_

#ifdef CGAL_HEADER_ONLY
#define CGAL_INLINE_FUNCTION inline
#else
#define CGAL_INLINE_FUNCTION
#endif

#include <CGAL/CORE/BigFloatRep.h>
#include <CGAL/CORE/BigFloat.h>
#include <CGAL/CORE/BigInt.h>

namespace CORE {

CGAL_INLINE_FUNCTION
void core_io_error_handler(const char *f, const char *m) {
  std::cout << "\n error_handler";
  std::cout << "::" << f << "::" << m << "\n";
  std::cout.flush();
  std::abort();
}

CGAL_INLINE_FUNCTION
void core_io_memory_handler(char *t, const char *f, const char *m) {
  if (t == nullptr) {
    std::cout << "\n memory_handler";
    std::cout << "::" << f << "::" << m;
    std::cout << "memory exhausted\n";
    std::cout.flush();
    std::abort();
  }
}

// s has size old_size and will be resized to new_size.
CGAL_INLINE_FUNCTION
void allocate (char * &s, int old_size, int new_size) {
  if (old_size > new_size)
    old_size = new_size;

  if (s == nullptr)
    old_size = 0;

  char *t = new char[new_size];
  core_io_memory_handler(t, "CoreIO", "allocate::out of memory error");

  int i;
  for (i = 0; i < old_size; i++)
    t[i] = s[i];

  delete[] s;
  s = t;
}

// appends c to s at position pos.
// sz is the size of s
CGAL_INLINE_FUNCTION
void append_char (char * &s, int & sz, int pos, char c) {
  if (pos > sz)
    core_io_error_handler("CoreIO", "append_char::invalid argument");

  if (pos == sz) {
    allocate(s, sz, 2*sz);
    sz *= 2;
  }

  s[pos] = c;
}

// skip blanks, tabs, line breaks and comment lines
CGAL_INLINE_FUNCTION
int skip_comment_line (std::istream & in) {
  char c;

  do {
    in.get(c);
    while ( c == '#' ) {
      do {
        in.get(c);
      } while ( c != '\n' );
     in.get(c);
    }
  } while (c == ' ' || c == '\t' || c == '\n');

  if (in.eof())
    core_io_error_handler("CoreIO::read_from_file()","unexpected end of file.");

  in.putback(c);
  return c;
}

// skips '\\' followed by '\n'
CGAL_INLINE_FUNCTION
char skip_backslash_new_line (std::istream & in) {
  char c;
  in.get(c);

  while (c == '\\') {
    in.get(c);

    if (c == '\n')
      in.get(c);
    else
      core_io_error_handler("CoreIO::operator>>", "\\ must be immediately followed by new line.");
  }

  return c;
}

CGAL_INLINE_FUNCTION
void read_string(std::istream& in, char* &buffer, int sz) {
  char c;
  int pos=0;
  skip_comment_line(in);

  while ( in.get(c) ) {
    if ( c == ' ' || c == '\t' || c == '\n' || c == '#')
      break;
    else
      append_char(buffer, sz, pos++, c);
  }
  append_char(buffer, sz, pos, '\0');
}

CGAL_INLINE_FUNCTION
void read_base_number(std::istream& in, BigInt& m, long length, long maxBits) {
  char *buffer;
  int size, offset;
  int base;
  bool is_negate;

  char c;
  int pos = 0;
  skip_comment_line(in);

  // read sign
  in.get(c);
  if (c == '-') {
    is_negate = true;
    in.get(c);
  } else
    is_negate = false;

  // read base and compute digits
  if (c == '0') {
    in.get(c);
    if (c == 'b') {
      base = 2;
      size = (maxBits == 0 || maxBits > length) ? length : maxBits;
      offset = length - size;
    } else if (c == 'x') {
      base = 16;
      size = (maxBits == 0) ? length : (maxBits+3) >> 2;
      size = (size > length) ? length : size;
      offset = (length - size) << 2;
    } else {
      base = 8;
      size = (maxBits == 0) ? length : (maxBits+2) / 3;
      size = (size > length) ? length : size;
      offset = (length - size) * 3;
      in.putback(c);
    }
  } else {
    base = 10;
    size = (maxBits == 0) ? length : (int)std::ceil(maxBits*std::log(2.0)/std::log(10.0));
    size = (size > length) ? length : size;
    offset = length - size;
    in.putback(c);
  }

  buffer = new char[size+2];
  // read digits
  for (int i=0; i<size; i++) {
    c=skip_backslash_new_line(in);
    if(in.eof()){
      break;
    }
    if (c != ' ' && c != '\t' && c != '\n')
      append_char(buffer, size, pos++, c);
  }
  if (base == 10) {
    for(int j=0; j<offset; j++)
      append_char(buffer, size, pos++, '0');
  }
  append_char(buffer, size, pos, '\0');

  // convert string to bigint.
  if (m.set_str(buffer, base) < 0)
    core_io_error_handler("CoreIO::read_from_file()","bad big number format.");
  delete[] buffer;

  // shift left if necessary
  if (offset > 0 && base != 10) {
    m <<= offset;
  }

  if (is_negate)
    negate(m);
}


CGAL_INLINE_FUNCTION
void write_base_number(std::ostream& out, char* buffer, std::size_t length, int base, int charsPerLine) {
  // write big number in a format that gmp's mpz_set_str() can
  // automatically recognize with argument base = 0.
  if (base == 2)
    out << "0b";
  else if (base == 16)
    out << "0x";
  else if (base == 8)
    out << '0';

  // write big number in charsPerLine.
  char* start, *end, c;
  for (std::size_t i=0; i<length; i += charsPerLine) {
    start = buffer + i;
    if (i + charsPerLine >= length)
      out << start;
    else {
      end = start + charsPerLine;
      c = *end;
      *end = '\0';

      out << start << "\\\n";
      *end = c;
    }
  }
}

CGAL_INLINE_FUNCTION
void readFromFile(BigInt& z, std::istream& in, long maxLength) {
  char *buffer;
  long length;

  // check type name whether it is Integer or not.
  buffer = new char[8];
  read_string(in, buffer, sizeof(buffer));
  if ( std::strcmp(buffer, "Integer") != 0)
    core_io_error_handler("BigInt::read_from_file()","type name expected.");
  delete[] buffer;

  // read the bit length field.
  buffer = new char[100];
  read_string(in, buffer, sizeof(buffer));
  length = std::atol(buffer);
  delete[] buffer;

  // read bigint
  read_base_number(in, z, length, maxLength);
}

CGAL_INLINE_FUNCTION
void writeToFile(const BigInt& z, std::ostream& out, int base, int charsPerLine) {
  BigInt c = abs(z);

  // get the absoulte value string
  char* buffer = new char[mpz_sizeinbase(c.get_mp(), base) + 2];
  mpz_get_str(buffer, base, c.get_mp());
  std::size_t length = std::strlen(buffer);

  // write type name of big number and length
  //out << "# This is an experimental big number format.\n";
  out << "Integer " << length << "\n";

  // if bigint is negative, then write an sign '-'.
  if ( sign(z) < 0  )
    out << '-';

  write_base_number(out, buffer, length, base, charsPerLine);
  out << "\n";
  delete[] buffer;
}

CGAL_INLINE_FUNCTION
void readFromFile(BigFloat& bf, std::istream& in, long maxLength) {
  char *buffer;
  long length;
  long exponent;
  BigInt mantissa;

  // check type name whether it is Float
  buffer = new char[6];
  read_string(in, buffer, sizeof(buffer));
  if (std::strcmp(buffer, "Float") != 0)
    core_io_error_handler("BigFloat::read_from_file()", "type name expected");
  delete[] buffer;

  // read base (default is 16384)
  buffer = new char[8];
  read_string(in, buffer, sizeof(buffer));
  if (std::strcmp(buffer, "(16384)") != 0)
    core_io_error_handler("BigFloat::read_from_file()", "base expected");
  delete[] buffer;

  // read the bit length field.
  buffer = new char[100];
  read_string(in, buffer, sizeof(buffer));
  length = std::atol(buffer);
  delete[] buffer;

  // read exponent
  buffer = new char[100];
  read_string(in, buffer, sizeof(buffer));
  exponent = std::atol(buffer);
  delete[] buffer;

  // read mantissa
  read_base_number(in, mantissa, length, maxLength);

  // construct BigFloat
  bf = BigFloat(mantissa, 0, exponent);
}

CGAL_INLINE_FUNCTION
void writeToFile(const BigFloat& bf, std::ostream& out, int base, int charsPerLine) {
  BigInt c(CORE::abs(bf.m()));

  // get the absoulte value string
  char* buffer = new char[mpz_sizeinbase(c.get_mp(), base) + 2];
  mpz_get_str(buffer, base, c.get_mp());
  std::size_t length = std::strlen(buffer);


  // write type name, base, length
  //out << "# This is an experimental Big Float format." << std::endl;
  out << "Float (16384) " << length << std::endl;
  // write exponent
  out << bf.exp() << std::endl;

  // write mantissa
  if ( CORE::sign(bf.m()) < 0 )
    out << '-';

  write_base_number(out, buffer, length, base, charsPerLine);
  out << '\n';
  delete[] buffer;
}

/* Underconstruction ----
void BigFloat::read_from_file2(std::istream& in, long maxLength) {
  long length = 1024;
  char *buffer;

  // check type name whether it is Float
  buffer = new char[7];
  BigInt::read_string(in, buffer, sizeof(buffer));
  if (strcmp(buffer, "NFloat") != 0)
    core_io_error_handler("BigFloat::read_from_file2()", "type name expected");
  delete[] buffer;

  // read base (default is 16)
  buffer = new char[5];
  BigInt::read_string(in, buffer, sizeof(buffer));
  if (strcmp(buffer, "(16)") != 0)
    core_io_error_handler("BigFloat::read_from_file2()", "base expected");
  delete[] buffer;

  // read length field
  buffer = new char[100];
  BigInt::read_string(in, buffer, sizeof(buffer));

  // get the length field if it is not null.
  if (buffer[0] != '\0') {
    length = atol(buffer);
    if (maxLength > 0 && length >= maxLength)
      length = maxLength;
  }
  delete[] buffer;

  // read exponent
  buffer = new char[100];
  BigInt::read_string(in, buffer, sizeof(buffer));
  long exp16 = atol(buffer);
  delete[] buffer;

  // read mantissa
  buffer = new char[length+2];
  //BigInt::read_base_number(in, buffer, length);

  BigInt m16(buffer);
  delete[] buffer;

  // convert to base CHUNK_BIT
  exp16 = exp16 - length + 1;
  if ( m16.is_negative() )
    exp16 ++;

  long tmp_exp = exp16 * 4;
  long q = tmp_exp / CHUNK_BIT;
  long r = tmp_exp % CHUNK_BIT;
  if ( r < 0 ) {
    r += CHUNK_BIT;
    q --;
  }

  BigInt mantissa = m16 << r;
  long exponent = q;

  // construct BigFloat
  if (--rep->refCount == 0)
    delete rep;

  rep = new BigFloatRep(mantissa, 0, exponent);
  rep->refCount++;

}

// write normal float
// now it assumed to write in hex base, i.e. B=2^4=16
// (note: our default base B=2^(CHUNK_BIT)=2^14=16384
void BigFloat::write_to_file2(std::ostream& out, int base, int charsPerLine) {
  // convert to base 16.
  long new_base = 4; // 2^4 = 16
  long tmp_exp = (rep->exp) * CHUNK_BIT;
  long q = tmp_exp / new_base;
  long r = tmp_exp % new_base;
  std::cout << "CORE_DEBUG: q=" << q << ", r=" << r << std::endl;
  if ( r < 0 ) {
    r += new_base;
    q--;
  }
  std::cout << "CORE_DEBUG: q=" << q << ", r=" << r << std::endl;

  BigInt m16 = (rep->m) << r;

  int size = mpz_sizeinbase(m16.I, base) + 2;
  std::cout << "size=" << size << std::endl;
  char* buffer = new char[size];

  int length = bigint_to_string(m16, buffer, base);
  std::cout << "length=" << length << std::endl;

  long exp16 = q + length - 1;
  if ( m16.is_negative() )
    exp16 --;

  // write type name, base, length
  out << "# This is an experimental Big Float format." << std::endl;
  out << "NFloat (16) " << length << std::endl;

  // write exponent
  out << exp16 << std::endl;

  // write mantissa
  if ( m16.is_negative() ) {
    out << '-';
    buffer ++;
  }

  BigInt::write_base_number(out, buffer, length, base, charsPerLine);
  out << '\n';
  delete[] buffer;
}
*/

} //namespace CORE

#endif // _COREIO_IMPL_H_
