// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

export class BigintMath {
  static INT64_MAX: bigint = 2n ** 63n - 1n;
  static INT64_MIN: bigint = -(2n ** 63n);

  // Returns the smallest integral power of 2 that is not smaller than n.
  // If n is less than or equal to 0, returns 1.
  static bitCeil(n: bigint): bigint {
    let result = 1n;
    while (result < n) {
      result <<= 1n;
    }
    return result;
  }

  // Returns the largest integral power of 2 which is not greater than n.
  // If n is less than or equal to 0, returns 1.
  static bitFloor(n: bigint): bigint {
    let result = 1n;
    while (result << 1n <= n) {
      result <<= 1n;
    }
    return result;
  }

  // Returns the largest integral value x where 2^x is not greater than n.
  static log2(n: bigint): number {
    let result = 1n;
    let log2 = 0;
    while (result << 1n <= n) {
      result <<= 1n;
      ++log2;
    }
    return log2;
  }

  // Returns the integral multiple of step which is closest to n.
  // If step is less than or equal to 0, returns n.
  static quant(n: bigint, step: bigint): bigint {
    step = BigintMath.max(1n, step);
    const halfStep = step / 2n;
    return step * ((n + halfStep) / step);
  }

  // Returns the largest integral multiple of step which is not larger than n.
  // If step is less than or equal to 0, returns n.
  static quantFloor(n: bigint, step: bigint): bigint {
    step = BigintMath.max(1n, step);
    if (n >= 0) {
      return n - (n % step);
    } else {
      // If we're negative, just subtract one more "step", unless we're already
      // aligned to a step then do nothing.
      return n - (n % step) - (n % step === 0n ? 0n : step);
    }
  }

  // Returns the smallest integral multiple of step which is not smaller than n.
  // If step is less than or equal to 0, returns n.
  static quantCeil(n: bigint, step: bigint): bigint {
    step = BigintMath.max(1n, step);
    if (n >= 0) {
      return n - (n % step) + (n % step === 0n ? 0n : step);
    } else {
      return n - (n % step);
    }
  }

  // Returns the greater of a and b.
  static max(a: bigint, b: bigint): bigint {
    return a > b ? a : b;
  }

  // Returns the smaller of a and b.
  static min(a: bigint, b: bigint): bigint {
    return a < b ? a : b;
  }

  // Returns the number of 1 bits in n.
  static popcount(n: bigint): number {
    if (n < 0n) {
      throw Error(`Can\'t get popcount of negative number ${n}`);
    }
    let count = 0;
    while (n) {
      if (n & 1n) {
        ++count;
      }
      n >>= 1n;
    }
    return count;
  }

  // Return the ratio between two bigints as a number.
  static ratio(dividend: bigint, divisor: bigint): number {
    return Number(dividend) / Number(divisor);
  }

  // Calculates the absolute value of a n.
  static abs(n: bigint) {
    return n < 0n ? -1n * n : n;
  }

  static clamp(n: bigint, min: bigint, max: bigint): bigint {
    if (n < min) {
      return min;
    } else if (n > max) {
      return max;
    } else {
      return n;
    }
  }
}
