// Copyright 2019 the V8 project authors. All rights reserved. Use of this
// source code is governed by a BSD-style license that can be found in the
// LICENSE file.

namespace math {

transitioning macro ReduceToSmiOrFloat64(implicit context: Context)(x: JSAny):
    never
    labels SmiResult(Smi), Float64Result(float64) {
  let x1: JSAny = x;
  while (true) {
    typeswitch (x1) {
      case (s: Smi): {
        goto SmiResult(s);
      }
      case (h: HeapNumber): {
        goto Float64Result(Convert<float64>(h));
      }
      case (a: JSAnyNotNumber): {
        x1 = conversion::NonNumberToNumber(a);
      }
    }
  }
  VerifiedUnreachable();
}

// ES6 #sec-math.abs
extern macro IsIntPtrAbsWithOverflowSupported(): constexpr bool;
extern macro TrySmiAdd(Smi, Smi): Smi labels Overflow;
extern macro TrySmiSub(Smi, Smi): Smi labels Overflow;
extern macro TrySmiAbs(Smi): Smi labels Overflow;
extern macro Float64Abs(float64): float64;
const kSmiMaxValuePlusOne:
    constexpr float64 generates '0.0 - kSmiMinValue';

transitioning javascript builtin
MathAbs(js-implicit context: NativeContext)(x: JSAny): Number {
  try {
    ReduceToSmiOrFloat64(x) otherwise SmiResult, Float64Result;
  } label SmiResult(s: Smi) {
    try {
      if constexpr (IsIntPtrAbsWithOverflowSupported()) {
        const result: Smi = TrySmiAbs(s)
            otherwise SmiOverflow;
        return result;
      } else {
        if (0 <= s) {
          return s;
        } else {
          const result: Smi = TrySmiSub(0, s) otherwise SmiOverflow;
          return result;
        }
      }
    } label SmiOverflow {
      return NumberConstant(kSmiMaxValuePlusOne);
    }
  } label Float64Result(f: float64) {
    return Convert<Number>(Float64Abs(f));
  }
}

// ES6 #sec-math.ceil
extern macro Float64Ceil(float64): float64;
transitioning javascript builtin
MathCeil(js-implicit context: NativeContext)(x: JSAny): Number {
  try {
    ReduceToSmiOrFloat64(x) otherwise SmiResult, Float64Result;
  } label SmiResult(s: Smi) {
    return s;
  } label Float64Result(f: float64) {
    return Convert<Number>(Float64Ceil(f));
  }
}

// ES6 #sec-math.floor
extern macro Float64Floor(float64): float64;
transitioning javascript builtin
MathFloor(js-implicit context: NativeContext)(x: JSAny): Number {
  try {
    ReduceToSmiOrFloat64(x) otherwise SmiResult, Float64Result;
  } label SmiResult(s: Smi) {
    return s;
  } label Float64Result(f: float64) {
    return Convert<Number>(Float64Floor(f));
  }
}

// ES6 #sec-math.round
extern macro Float64Round(float64): float64;
transitioning javascript builtin
MathRound(js-implicit context: NativeContext)(x: JSAny): Number {
  try {
    ReduceToSmiOrFloat64(x) otherwise SmiResult, Float64Result;
  } label SmiResult(s: Smi) {
    return s;
  } label Float64Result(f: float64) {
    return Convert<Number>(Float64Round(f));
  }
}

// ES6 #sec-math.trunc
extern macro Float64Trunc(float64): float64;
transitioning javascript builtin
MathTrunc(js-implicit context: NativeContext)(x: JSAny): Number {
  try {
    ReduceToSmiOrFloat64(x) otherwise SmiResult, Float64Result;
  } label SmiResult(s: Smi) {
    return s;
  } label Float64Result(f: float64) {
    return Convert<Number>(Float64Trunc(f));
  }
}

// ES6 #sec-math.pow
extern macro Float64Pow(float64, float64): float64;
extern macro TruncateTaggedToFloat64(implicit context: Context)(JSAny): float64;

@export
macro MathPowImpl(implicit context: Context)(base: JSAny, exponent: JSAny):
    Number {
  const baseValue: float64 = TruncateTaggedToFloat64(base);
  const exponentValue: float64 = TruncateTaggedToFloat64(exponent);
  const result: float64 = Float64Pow(baseValue, exponentValue);
  return Convert<Number>(result);
}

transitioning javascript builtin
MathPow(js-implicit context: NativeContext)(
    base: JSAny, exponent: JSAny): Number {
  return MathPowImpl(base, exponent);
}

// ES6 #sec-math.max
extern macro Float64Max(float64, float64): float64;
transitioning javascript builtin
MathMax(js-implicit context: NativeContext)(...arguments): Number {
  let result: float64 = MINUS_V8_INFINITY;
  const argCount = arguments.length;
  for (let i: intptr = 0; i < argCount; i++) {
    const doubleValue = TruncateTaggedToFloat64(arguments[i]);
    result = Float64Max(result, doubleValue);
  }
  return Convert<Number>(result);
}

// ES6 #sec-math.min
extern macro Float64Min(float64, float64): float64;
transitioning javascript builtin
MathMin(js-implicit context: NativeContext)(...arguments): Number {
  let result: float64 = V8_INFINITY;
  const argCount = arguments.length;
  for (let i: intptr = 0; i < argCount; i++) {
    const doubleValue = TruncateTaggedToFloat64(arguments[i]);
    result = Float64Min(result, doubleValue);
  }
  return Convert<Number>(result);
}

// ES6 #sec-math.acos
extern macro Float64Acos(float64): float64;

transitioning javascript builtin
MathAcos(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Acos(value));
}

// ES6 #sec-math.acosh
extern macro Float64Acosh(float64): float64;

transitioning javascript builtin
MathAcosh(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Acosh(value));
}

// ES6 #sec-math.asin
extern macro Float64Asin(float64): float64;

transitioning javascript builtin
MathAsin(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Asin(value));
}

// ES6 #sec-math.asinh
extern macro Float64Asinh(float64): float64;

transitioning javascript builtin
MathAsinh(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Asinh(value));
}

// ES6 #sec-math.atan
extern macro Float64Atan(float64): float64;

transitioning javascript builtin
MathAtan(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Atan(value));
}

// ES6 #sec-math.atan2
extern macro Float64Atan2(float64, float64): float64;

transitioning javascript builtin
MathAtan2(js-implicit context: NativeContext)(y: JSAny, x: JSAny): Number {
  const yValue = Convert<float64>(ToNumber_Inline(y));
  const xValue = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Atan2(yValue, xValue));
}

// ES6 #sec-math.atanh
extern macro Float64Atanh(float64): float64;

transitioning javascript builtin
MathAtanh(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Atanh(value));
}

// ES6 #sec-math.cbrt
extern macro Float64Cbrt(float64): float64;

transitioning javascript builtin
MathCbrt(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Cbrt(value));
}

// ES6 #sec-math.clz32
extern macro Word32Clz(int32): int32;

transitioning javascript builtin
MathClz32(js-implicit context: NativeContext)(x: JSAny): Number {
  const value: int32 = Convert<int32>(ToNumber_Inline(x));
  return Convert<Number>(Word32Clz(value));
}

// ES6 #sec-math.cos
extern macro Float64Cos(float64): float64;

transitioning javascript builtin
MathCos(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Cos(value));
}

// ES6 #sec-math.cosh
extern macro Float64Cosh(float64): float64;

transitioning javascript builtin
MathCosh(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Cosh(value));
}

// ES6 #sec-math.exp
extern macro Float64Exp(float64): float64;

transitioning javascript builtin
MathExp(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Exp(value));
}

// ES6 #sec-math.expm1
extern macro Float64Expm1(float64): float64;

transitioning javascript builtin
MathExpm1(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Expm1(value));
}

// ES6 #sec-math.fround
transitioning javascript builtin
MathFround(js-implicit context: NativeContext)(x: JSAny): Number {
  const x32 = Convert<float32>(ToNumber_Inline(x));
  const x64 = Convert<float64>(x32);
  return Convert<Number>(x64);
}

// ES6 #sec-math.imul
transitioning javascript builtin
MathImul(js-implicit context: NativeContext)(x: JSAny, y: JSAny): Number {
  const x = Convert<int32>(ToNumber_Inline(x));
  const y = Convert<int32>(ToNumber_Inline(y));
  return Convert<Number>(x * y);
}

// ES6 #sec-math.log
extern macro Float64Log(float64): float64;

transitioning javascript builtin
MathLog(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Log(value));
}

// ES6 #sec-math.log1p
extern macro Float64Log1p(float64): float64;

transitioning javascript builtin
MathLog1p(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Log1p(value));
}

// ES6 #sec-math.log10
extern macro Float64Log10(float64): float64;

transitioning javascript builtin
MathLog10(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Log10(value));
}

// ES6 #sec-math.log2
extern macro Float64Log2(float64): float64;

transitioning javascript builtin
MathLog2(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Log2(value));
}

// ES6 #sec-math.sin
extern macro Float64Sin(float64): float64;

transitioning javascript builtin
MathSin(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Sin(value));
}

// ES6 #sec-math.sign
transitioning javascript builtin
MathSign(js-implicit context: NativeContext)(x: JSAny): Number {
  const num = ToNumber_Inline(x);
  const value = Convert<float64>(num);

  if (value < 0) {
    return -1;
  } else if (value > 0) {
    return 1;
  } else {
    return num;
  }
}

// ES6 #sec-math.sinh
extern macro Float64Sinh(float64): float64;

transitioning javascript builtin
MathSinh(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Sinh(value));
}

// ES6 #sec-math.sqrt
extern macro Float64Sqrt(float64): float64;

transitioning javascript builtin
MathSqrt(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Sqrt(value));
}

// ES6 #sec-math.tan
extern macro Float64Tan(float64): float64;

transitioning javascript builtin
MathTan(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Tan(value));
}

// ES6 #sec-math.tanh
extern macro Float64Tanh(float64): float64;

transitioning javascript builtin
MathTanh(js-implicit context: NativeContext)(x: JSAny): Number {
  const value = Convert<float64>(ToNumber_Inline(x));
  return Convert<Number>(Float64Tanh(value));
}

// ES6 #sec-math.hypot
transitioning javascript builtin
MathHypot(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): Number {
  const length = arguments.length;
  if (length == 0) {
    return 0;
  }
  const absValues = AllocateZeroedFixedDoubleArray(length);
  let oneArgIsNaN: bool = false;
  let max: float64 = 0;
  for (let i: intptr = 0; i < length; ++i) {
    const value = Convert<float64>(ToNumber_Inline(arguments[i]));
    if (Float64IsNaN(value)) {
      oneArgIsNaN = true;
    } else {
      const absValue = Float64Abs(value);
      absValues.floats[i] = Convert<float64_or_hole>(absValue);
      if (absValue > max) {
        max = absValue;
      }
    }
  }
  if (max == V8_INFINITY) {
    return V8_INFINITY;
  } else if (oneArgIsNaN) {
    return kNaN;
  } else if (max == 0) {
    return 0;
  }
  dcheck(max > 0);

  // Kahan summation to avoid rounding errors.
  // Normalize the numbers to the largest one to avoid overflow.
  let sum: float64 = 0;
  let compensation: float64 = 0;
  for (let i: intptr = 0; i < length; ++i) {
    const n = absValues.floats[i].ValueUnsafeAssumeNotHole() / max;
    const summand = n * n - compensation;
    const preliminary = sum + summand;
    compensation = (preliminary - sum) - summand;
    sum = preliminary;
  }
  return Convert<Number>(Float64Sqrt(sum) * max);
}

// ES6 #sec-math.random
extern macro RefillMathRandom(NativeContext): Smi;

transitioning javascript builtin
MathRandom(js-implicit context: NativeContext, receiver: JSAny)(): Number {
  let smiIndex: Smi = *NativeContextSlot(ContextSlot::MATH_RANDOM_INDEX_INDEX);
  if (smiIndex == 0) {
    // refill math random.
    smiIndex = RefillMathRandom(context);
  }
  const newSmiIndex: Smi = smiIndex - 1;
  *NativeContextSlot(ContextSlot::MATH_RANDOM_INDEX_INDEX) = newSmiIndex;

  const array: FixedDoubleArray =
      *NativeContextSlot(ContextSlot::MATH_RANDOM_CACHE_INDEX);
  const random: float64 =
      array.floats[Convert<intptr>(newSmiIndex)].ValueUnsafeAssumeNotHole();
  return AllocateHeapNumberWithValue(random);
}
}