/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
package org.apache.hadoop.examples.pi.math;

/** Modular arithmetics */
public class Modular {
  static final long MAX_SQRT_LONG = (long)Math.sqrt(Long.MAX_VALUE);

  /** Compute 2^e mod n */
  public static long mod(long e, long n) {
    final int HALF = (63 - Long.numberOfLeadingZeros(n)) >> 1;
    final int FULL = HALF << 1;
    final long ONES = (1 << HALF) - 1; 

    long r = 2;
    for (long mask = Long.highestOneBit(e) >> 1; mask > 0; mask >>= 1) {
      if (r <= MAX_SQRT_LONG) {
        r *= r;
        if (r >= n) r %= n;
      } else {
        // r^2 will overflow
        final long high = r >>> HALF;
        final long low  = r &= ONES;
        
        r *= r;
        if (r >= n) r %= n;

        if (high != 0) {
          long s = high * high;
          if (s >= n) s %= n;
          for(int i = 0; i < FULL; i++)
            if ((s <<= 1) >= n) s -= n;
          
          if (low == 0)
            r = s;
          else {
            long t = high * low;
            if (t >= n) t %= n;
            for(int i = -1; i < HALF; i++)
              if ((t <<= 1) >= n) t -= n;
            
            r += s;
            if (r >= n) r -= n;
            r += t;
            if (r >= n) r -= n;
          }
        }
      }

      if ((e & mask) != 0) {
        r <<= 1;
        if (r >= n) r -= n;
      }
    }
    return r;
  }

  /** Given x in [0,1) and a in (-1,1),
   * return (x, a) mod 1.0. 
   */
  public static double addMod(double x, final double a) {
    x += a;
    return x >= 1? x - 1: x < 0? x + 1: x;
  }

  /** Given 0 < x < y,
   * return x^(-1) mod y.
   */
  public static long modInverse(final long x, final long y) {
    if (x == 1) return 1;

    long a = 1;
    long b = 0;
    long c = x;

    long u = 0;
    long v = 1;
    long w = y;
    
    for(;;) {
      {
        final long q = w/c;
        w -= q*c;
        u -= q*a;
        if (w == 1) return u > 0? u: u + y;
        v -= q*b;
      }
      {
        final long q = c/w;
        c -= q*w;
        a -= q*u;
        if (c == 1) return a > 0? a: a + y;
        b -= q*v;
      }
    }
  }
}
