#include <poincare/rational.h>
extern "C" {
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
}
#include <poincare/arithmetic.h>
#include <poincare/opposite.h>
#include <poincare/infinity.h>
#include <poincare/fraction_layout.h>
#include <poincare/char_layout.h>

namespace Poincare {

/* Rational Node */

RationalNode::RationalNode(const native_uint_t * numeratorDigits, uint8_t numeratorSize, const native_uint_t * denominatorDigits, uint8_t denominatorSize, bool negative) :
  m_negative(negative),
  m_numberOfDigitsNumerator(numeratorSize),
  m_numberOfDigitsDenominator(denominatorSize)
{
  if (numeratorDigits) {
    memcpy(m_digits, numeratorDigits, numeratorSize*sizeof(native_uint_t));
  }
  if (denominatorDigits) {
    memcpy(m_digits + numeratorSize, denominatorDigits, denominatorSize*sizeof(native_uint_t));
  }
}

Integer RationalNode::signedNumerator() const {
  return Integer::BuildInteger((native_uint_t *)m_digits, m_numberOfDigitsNumerator, m_negative);
}

Integer RationalNode::unsignedNumerator() const {
  return Integer::BuildInteger((native_uint_t *)m_digits, m_numberOfDigitsNumerator, false);
}

Integer RationalNode::denominator() const {
  return Integer::BuildInteger(((native_uint_t *)m_digits+m_numberOfDigitsNumerator), m_numberOfDigitsDenominator, false);
}

// Tree Node

static size_t RationalSize(uint8_t numeratorNumberOfDigits, uint8_t denominatorNumberOfDigits) {
  uint8_t realNumeratorSize = numeratorNumberOfDigits > Integer::k_maxNumberOfDigits ? 0 : numeratorNumberOfDigits;
  uint8_t realDenominatorSize = denominatorNumberOfDigits > Integer::k_maxNumberOfDigits ? 0 : denominatorNumberOfDigits;
  return sizeof(RationalNode) + sizeof(native_uint_t)*(realNumeratorSize + realDenominatorSize);
}

size_t RationalNode::size() const {
  return RationalSize(m_numberOfDigitsNumerator, m_numberOfDigitsDenominator);
}

// Serialization Node

#if POINCARE_TREE_LOG
void RationalNode::logAttributes(std::ostream & stream) const {
  stream << " negative=\"" << m_negative << "\"";
  stream << " numerator=\"";
  this->signedNumerator().logInteger(stream);
  stream << "\"";
  stream << " denominator=\"";
  this->denominator().logInteger(stream);
  stream << "\"";
}
#endif

int RationalNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const {
  if (bufferSize == 0) {
    return -1;
  }
  buffer[bufferSize-1] = 0;
  int numberOfChar = signedNumerator().serialize(buffer, bufferSize);
  if (denominator().isOne()) {
    return numberOfChar;
  }
  if (numberOfChar >= bufferSize-1) {
    return numberOfChar;
  }
  buffer[numberOfChar++] = '/';
  numberOfChar += denominator().serialize(buffer+numberOfChar, bufferSize-numberOfChar);
  return numberOfChar;
}

// Expression subclassing

Expression RationalNode::setSign(Sign s, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target) {
  assert(s == ExpressionNode::Sign::Positive || s == ExpressionNode::Sign::Negative);
  return Rational(this).setSign(s);
}

// Layout

Layout RationalNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const {
  HorizontalLayout numeratorLayout = signedNumerator().createLayout();
  if (denominator().isOne()) {
    return numeratorLayout;
  }
  HorizontalLayout denominatorLayout = denominator().createLayout();
  return FractionLayout::Builder(numeratorLayout, denominatorLayout);
}

// Approximation

template<typename T> T RationalNode::templatedApproximate() const {
  T n = signedNumerator().approximate<T>();
  T d = denominator().approximate<T>();
  return n/d;
}

// Comparison

int RationalNode::NaturalOrder(const RationalNode * i, const RationalNode * j) {
  if (Number(i).sign() == Sign::Negative && Number(j).sign() == Sign::Positive) {
    return -1;
  }
  if (Number(i).sign() == Sign::Positive && Number(j).sign() == Sign::Negative) {
    return 1;
  }
  Integer i1 = Integer::Multiplication(i->signedNumerator(), j->denominator());
  Integer i2 = Integer::Multiplication(i->denominator(), j->signedNumerator());
  return Integer::NaturalOrder(i1, i2);
}

int RationalNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted) const {
  if (!ascending) {
    return e->simplificationOrderSameType(this, true, canBeInterrupted);
  }
  assert(e->type() == ExpressionNode::Type::Rational);
  const RationalNode * other = static_cast<const RationalNode *>(e);
  return NaturalOrder(this, other);
}

// Simplification

Expression RationalNode::shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target) {
  return Rational(this).shallowReduce();
}

Expression RationalNode::shallowBeautify(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target) {
  return Rational(this).shallowBeautify();
}

Expression RationalNode::denominator(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const {
  return Rational(this).denominator(context, complexFormat, angleUnit);
}

/* Rational  */

// Constructors

Rational Rational::Builder(Integer & num, Integer & den) {
  assert(!den.isZero());
  if (!num.isOne() && !den.isOne()) {
    // Avoid computing GCD if possible
    Integer gcd = Arithmetic::GCD(num, den);
    num = Integer::Division(num, gcd).quotient;
    den = Integer::Division(den, gcd).quotient;
  }
  bool negative = (!num.isNegative() && den.isNegative()) || (!den.isNegative() && num.isNegative());
  return Rational::Builder(num.digits(), num.numberOfDigits(), den.digits(), den.numberOfDigits(), negative);
}

Rational Rational::Builder(const Integer & numerator) {
  native_uint_t one = 1;
  return Rational::Builder(numerator.digits(), numerator.numberOfDigits(), &one, 1, numerator.isNegative());
}

Rational Rational::Builder(native_int_t i) {
  native_uint_t one = 1;
  if (i == 0) {
    return Rational::Builder(nullptr, 0, &one, 1, false);
  }
  native_uint_t absI = i < 0 ? -i : i;
  return Rational::Builder(&absI, 1, &one, 1, i < 0);
}

Rational Rational::Builder(native_int_t i, native_int_t j) {
  Integer iInteger(i);
  Integer jInteger(j);
  return Rational::Builder(iInteger, jInteger);
}

Rational Rational::Builder(const char * iString, const char * jString) {
  Integer iInteger(iString);
  Integer jInteger(jString);
  return Rational::Builder(iInteger, jInteger);
}

bool Rational::numeratorOrDenominatorIsInfinity() const {
  return signedIntegerNumerator().isOverflow() || integerDenominator().isOverflow();
}

// Basic operations

Rational Rational::Addition(const Rational & i, const Rational & j) {
  Integer newNumerator = Integer::Addition(Integer::Multiplication(i.signedIntegerNumerator(), j.integerDenominator()), Integer::Multiplication(j.signedIntegerNumerator(), i.integerDenominator()));
  Integer newDenominator = Integer::Multiplication(i.integerDenominator(), j.integerDenominator());
  return Rational::Builder(newNumerator, newDenominator);
}

Rational Rational::Multiplication(const Rational & i, const Rational & j) {
  Integer newNumerator = Integer::Multiplication(i.signedIntegerNumerator(), j.signedIntegerNumerator());
  Integer newDenominator = Integer::Multiplication(i.integerDenominator(), j.integerDenominator());
  return Rational::Builder(newNumerator, newDenominator);
}

Rational Rational::IntegerPower(const Rational & i, const Integer & j) {
  Integer absJ = j;
  absJ.setNegative(false);
  Integer newNumerator = Integer::Power(i.signedIntegerNumerator(), absJ);
  Integer newDenominator = Integer::Power(i.integerDenominator(), absJ);
  if (j.isNegative()) {
    return Rational::Builder(newDenominator, newNumerator);
  }
  return Rational::Builder(newNumerator, newDenominator);
}

Rational Rational::Builder(const native_uint_t * i, uint8_t numeratorSize, const native_uint_t * j, uint8_t denominatorSize, bool negative) {
  void * bufferNode = TreePool::sharedPool()->alloc(RationalSize(numeratorSize, denominatorSize));
  RationalNode * node = new (bufferNode) RationalNode(i, numeratorSize, j, denominatorSize, negative);
  TreeHandle h = TreeHandle::BuildWithGhostChildren(node);
  return static_cast<Rational &>(h);
}

Expression Rational::shallowReduce() {
  // FIXME:
  /* Infinite Rational should not exist as they aren't parsed and are supposed
   * to be turn in Float if they should appear. We assert(false) so far, but
   * the right behaviour would be to find the right float to represent them.
   * However at that point, it is too late to find it. The issue is earlier... */
#if 0
  if (unsignedIntegerNumerator().isOverflow() && integerDenominator().isOverflow()) {
    assert(false);
    return Undefined::Builder();
  }
  // Turn into Infinite if the numerator is too big.
  if (unsignedIntegerNumerator().isOverflow()) {
    assert(false);
    return Infinity::Builder(sign(&context) == ExpressionNode::Sign::Negative);
  }
  // Turn into 0 if the denominator is too big.
  if (integerDenominator().isOverflow()) {
    assert(false);
    return Rational::Builder(0);
  }
#endif
  assert(!numeratorOrDenominatorIsInfinity());
  return *this;
}

Expression Rational::shallowBeautify() {
  if (sign() == ExpressionNode::Sign::Negative) {
    Expression abs = setSign(ExpressionNode::Sign::Positive);
    Opposite o = Opposite::Builder();
    replaceWithInPlace(o);
    o.replaceChildAtIndexInPlace(0, abs);
    return o;
  }
  return *this;
}

Expression Rational::denominator(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const {
  Integer d = integerDenominator();
  if (d.isOne()) {
    return Expression();
  }
  if (d.isOverflow()) {
    return Infinity::Builder(false);
  }
  return Rational::Builder(d);
}

Expression Rational::setSign(ExpressionNode::Sign s) {
  assert(s == ExpressionNode::Sign::Positive || s == ExpressionNode::Sign::Negative);
  node()->setNegative(s == ExpressionNode::Sign::Negative);
  return *this;
}

template float RationalNode::templatedApproximate() const;
template double RationalNode::templatedApproximate() const;

}
