// ----------------------------------------------------------------------------
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ----------------------------------------------------------------------------

using System;
using System.Diagnostics;

namespace Microsoft.Singularity.Crypto.PublicKey
{
    struct Digit
    {
        readonly UInt32 _digit;
        Digit(UInt32 digit) { _digit = digit; }
        public static implicit operator UInt32(Digit d) { return d._digit; }
        public static explicit operator long(Digit d) {
            return (long)d._digit;
        }
        public static implicit operator Digit(UInt32 digit) {
            return new Digit(digit);
        }
        public override string ToString() { return _digit.ToString(); }
        public static Digit operator <<(Digit d, int n) {
            Debug.Assert(0 <= n && n < BitN, "internal error");
            return new Digit(d._digit << n);
        }
        public static Digit operator >>(Digit d, int n) {
            Debug.Assert(0 <= n && n < BitN, "internal error");
            return new Digit(d._digit >> n);
        }
        internal const int BitN = 32;
        internal const UInt32 MaxValue = UInt32.MaxValue;
        static byte[]
            leadingZeroBitN
          = new byte[] { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
        internal static int SigBitN(Digit d) {
            d |= 1;
            int bitN = BitN;
            while (d < 1U << BitN - 5) {
                bitN -= 5; d <<= 5;
            }
            return bitN - leadingZeroBitN[d >> BitN - 4];
        }
        internal static Digit TwoAdicInverse(Digit d) {
            if ((d & 1) == 0) {
                throw new ArgumentException();
            }
            UInt32 dInverse = unchecked(3 * d ^ 2)
                 , error = unchecked(1 - d * dInverse);
            Debug.Assert((error & 31) == 0, "internal error");
            for (int bitN = 5; bitN < BitN / 2; bitN *= 2) {
                unchecked {
                    dInverse += dInverse * error;
                    error = error * error;
                    Debug.Assert(error == 1 - d * dInverse, "internal error");
                }
            }
            return unchecked(dInverse * error + dInverse);
        }
        static byte[]
            trailingZeroN
          = new byte[] { 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 };
        internal static Digit OddGcd(Digit d1, Digit d2) {
            if (((d1 | d2) & 1) == 0) {
                throw new ArgumentException();
            }
            if (d1 == 0 || d2 == 0) {
                Debug.Assert(false, "untested code");
                return d1 + d2;
            }
            do { d1 >>= trailingZeroN[d1 & 15]; } while ((d1 & 1) == 0);
            do { d2 >>= trailingZeroN[d2 & 15]; } while ((d2 & 1) == 0);
            while (d1 != d2) {
                int sh1 = 1 + trailingZeroN[(d1 ^ d2) >> 1 & 15];
                UInt32 n12Max = d1 > d2 ? d1 : d2;
                d1 = d1 ^ d2 ^ n12Max;
                d2 = n12Max - d1 >> sh1;
                do { d2 >>= trailingZeroN[d2 & 15]; } while ((d2 & 1) == 0);
                Debug.Assert((d1 & d2 & 1) != 0, "internal error");
            }
            return d1;
        }
        internal static Digit Random(Random generator) {
            return (UInt32)generator.Next(65536) << 16
                 | (UInt32)generator.Next(65536);
        }
        internal static Digit
        Random(UInt32 dlow, UInt32 dhigh, Random generator) {
            if (dhigh < dlow) {
                throw new ArgumentException();
            }
            UInt32 spread = dhigh - dlow;
            int shiftBitN = BitN - SigBitN(spread | 1);
            UInt32 result = 0;
            do {
                result = Random(generator)._digit >> shiftBitN;
            } while (result > spread);
            return dlow + result;
        }
    }
}
