/*
    This file is part of TON Blockchain Library.
    TON Blockchain 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 2 of the License, or
    (at your option) any later version.
    TON Blockchain 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 TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#pragma once
#include "common/refint.h"
namespace td {
class NegExpBinTable {
  int precision, maxpw2, minpw2;
  std::vector exp_pw2_table;      // table of 2^precision * exp(- 2^k) for k = max_pw2-1 .. min_pw2
  std::vector exp_pw2_ref_table;  // same data
  td::BigInt256 One;
 public:
  NegExpBinTable(int _precision, int _maxpw2, int _minpw2) : precision(255), maxpw2(_maxpw2), minpw2(_minpw2) {
    (_precision > 0 && _precision < 256 && _minpw2 <= 0 && _maxpw2 > 0 && _maxpw2 <= 256 && _minpw2 >= -256 && init() &&
     adjust_precision(_precision)) ||
        invalidate();
  }
  bool is_valid() const {
    return minpw2 < maxpw2;
  }
  int get_precision() const {
    return precision;
  }
  int get_exponent_precision() const {
    return -minpw2;
  }
  int get_exponent_max_log2() const {
    return maxpw2;
  }
  const td::BigInt256* exp_pw2(int k) const {  // returns 2^precision * exp(-2^k) or null
    return (k >= minpw2 && k < maxpw2) ? &exp_pw2_table[k - minpw2] : nullptr;
  }
  td::RefInt256 exp_pw2_ref(int k) const {
    if (k >= minpw2 && k < maxpw2) {
      return exp_pw2_ref_table[k - minpw2];
    } else {
      return {};
    }
  }
  bool nexpf(td::BigInt256& res, long long x, int k) const;  // res := 2^precision * exp(-x * 2^k)
  td::RefInt256 nexpf(long long x, int k) const;
 private:
  bool init();
  bool init_one();
  bool adjust_precision(int new_precision, int rmode = 0);
  bool invalidate() {
    minpw2 = maxpw2 = 0;
    return false;
  }
  td::BigInt256 series_exp(int k) const;  // returns 2^precision * exp(-2^(-k)), k >= 0
};
struct SuperFloat {
  struct SetZero {};
  struct SetOne {};
  struct SetNan {};
  td::uint128 m;
  int s;
  SuperFloat() = default;
  SuperFloat(SetZero) : m(0, 0), s(0) {
  }
  SuperFloat(SetOne) : m(0, 1), s(0) {
  }
  SuperFloat(SetNan) : m(0, 0), s(std::numeric_limits::min()) {
  }
  SuperFloat(td::uint128 _m, int _s = 0) : m(_m), s(_s) {
  }
  SuperFloat(td::uint64 _m, int _s = 0) : m(0, _m), s(_s) {
  }
  explicit SuperFloat(BigInt256 x);
  static SuperFloat Zero() {
    return SetZero{};
  }
  static SuperFloat One() {
    return SetOne{};
  }
  static SuperFloat NaN() {
    return SetNan{};
  }
  void set_zero() {
    m = td::uint128(0, 0);
    s = 0;
  }
  void set_one() {
    m = td::uint128(0, 1);
    s = 0;
  }
  void set_nan() {
    s = std::numeric_limits::min();
  }
  bool is_nan() const {
    return s == std::numeric_limits::min();
  }
  bool is_zero() const {
    return m.is_zero();
  }
  bool normalize();
  td::uint64 top() const {
    return m.rounded_hi();
  }
  static td::uint128 as_uint128(const td::BigInt256& x);
  static td::uint64 as_uint64(const td::BigInt256& x);
};
class NegExpInt64Table {
  enum { max_exp = 45 };
  unsigned char table0_shift[max_exp + 1];
  td::uint64 table0[max_exp + 1], table1[256], table2[256];
 public:
  NegExpInt64Table();
  // compute x * exp(-k / 2^16);
  // more precisely: computes 0 <= y <= x for 0 <= x < 2^60, s.that |y - x * exp(-k / 2^16)| < 1
  // two different implementations of this functions would return values differing by at most one
  td::uint64 umulnexps32(td::uint64 x, unsigned k, bool trunc = false) const;
  td::int64 mulnexps32(td::int64 x, unsigned k, bool trunc = false) const;
  static const NegExpInt64Table& table();
 private:
};
td::uint64 umulnexps32(td::uint64 x, unsigned k, bool trunc = false);  // compute x * exp(-k / 2^16)
td::int64 mulnexps32(td::int64 x, unsigned k, bool trunc = false);
}  // namespace td