import numbers.parser.*;
import numbers.floats.*;
import numbers.overflow.*;
import identifiers.(IdentifierChars);


//
// numeric type predicates
//

[T] SignedInteger?(static T) = false;
overload SignedInteger?(static Int8) = true;
overload SignedInteger?(static Int16) = true;
overload SignedInteger?(static Int32) = true;
overload SignedInteger?(static Int64) = true;
overload SignedInteger?(static Int128) = true;


[T] UnsignedInteger?(static T) = false;
overload UnsignedInteger?(static UInt8) = true;
overload UnsignedInteger?(static UInt16) = true;
overload UnsignedInteger?(static UInt32) = true;
overload UnsignedInteger?(static UInt64) = true;
overload UnsignedInteger?(static UInt128) = true;


[T] Integer?(static T) = SignedInteger?(T) or UnsignedInteger?(T);
[A, B] overload Integer?(static A, static B) = Integer?(A) and Integer?(B);


[T] ByteSizedInteger?(static T) = false;
overload ByteSizedInteger?(static Int8) = true;
overload ByteSizedInteger?(static UInt8) = true;

[T] Float?(static T) = false;
overload Float?(static Float32) = true;
overload Float?(static Float64) = true;
overload Float?(static Float80) = true;
[A, B] overload Float?(static A, static B) = Float?(A) and Float?(B);

[T] Imaginary?(static T) = false;
overload Imaginary?(static Imag32) = true;
overload Imaginary?(static Imag64) = true;
overload Imaginary?(static Imag80) = true;
[A, B] overload Imaginary?(static A, static B) = Imaginary?(A) and Imaginary?(B);

[T] Numeric?(static T) = Integer?(T) or Float?(T) or Imaginary?(T);
[A, B] overload Numeric?(static A, static B) = Numeric?(A) and Numeric?(B);



//
// BiggerInteger?, BiggerFloat?, BiggerNumeric?
//

[A,B | Integer?(A,B)]
private BiggerInteger?(static A, static B) =
    (TypeSize(A) > TypeSize(B)) or ((TypeSize(A) == TypeSize(B)) and
                                    UnsignedInteger?(A) and
                                    SignedInteger?(B));

private define BiggerFloat?;

[A,B | Float?(A,B) or Imaginary?(A,B)]
overload BiggerFloat?(static A, static B) = TypeSize(A) > TypeSize(B);

[A,B | (Float?(A) and Imaginary?(B)) or (Float?(B) and Imaginary?(A))]
overload BiggerFloat?(static A, static B) = TypeSize(A) > TypeSize(B);


private define BiggerNumeric?;

[A,B | (Float?(A) or Imaginary?(A)) and Integer?(B)]
overload BiggerNumeric?(static A, static B) = true;

[A,B | Integer?(A) and (Float?(B) or Imaginary?(B))]
overload BiggerNumeric?(static A, static B) = false;

[A,B | Integer?(A,B)]
overload BiggerNumeric?(static A, static B) = BiggerInteger?(A,B);

[A,B | Float?(A,B) or Imaginary?(A,B)]
overload BiggerNumeric?(static A, static B) = BiggerFloat?(A,B);

[A,B | (Float?(A) and Imaginary?(B)) or (Float?(B) and Imaginary?(A))]
overload BiggerNumeric?(static A, static B) = BiggerFloat?(A,B);




//
// toBiggerNumericType
//

define toBiggerNumericType;

[A | Numeric?(A)]
inline overload toBiggerNumericType(static A, static A, v) = v;

[A,B | Numeric?(A,B) and BiggerNumeric?(A,B)]
inline overload toBiggerNumericType(static A, static B, v) = numericConvert(A,v);

[A,B | Numeric?(A,B) and BiggerNumeric?(B,A)]
inline overload toBiggerNumericType(static A, static B, v) = numericConvert(B,v);

define wrapToBiggerNumericType;

[A | Numeric?(A)]
inline overload wrapToBiggerNumericType(static A, static A, v) = v;

[A,B | Numeric?(A,B) and BiggerNumeric?(A,B)]
inline overload wrapToBiggerNumericType(static A, static B, v) = wrapCast(A, v);

[A,B | Numeric?(A,B) and BiggerNumeric?(B,A)]
inline overload wrapToBiggerNumericType(static A, static B, v) = wrapCast(B, v);

ImagBaseType(static Imag32) = Float;
overload ImagBaseType(static Imag64) = Double;
overload ImagBaseType(static Imag80) = LongDouble;



//
// constructors
//

[T | Numeric?(T)]
inline overload T() = numericConvert(T, 0);

[T1,T2 | Numeric?(T1,T2)]
inline overload T1(src:T2) = numericConvert(T1, src);

[T,I | Float?(T) and Imaginary?(I)]
inline overload T(a:I) = T(0.);

[T,I | Float?(T) and Imaginary?(I)]
inline overload I(a:T) = I(0.j);



//
// assign
//

[A, B | Numeric?(A,B) and BiggerNumeric?(A,B)]
inline overload assign(ref dest:A, src:B) {
    primitiveCopy(dest, numericConvert(A,src));
}

[A, B | Integer?(A,B) and (TypeSize(A) == TypeSize(B))]
inline overload assign(ref dest:A, src:B) {
    primitiveCopy(dest, A(src));
}



//
// equals?, notEquals?, lesser?, lesserEquals?, greater?, greaterEquals?
//

[A,B | Numeric?(A,B)]
inline overload equals?(a:A, b:B) =
    equals?(toBiggerNumericType(A, B, a),
            toBiggerNumericType(A, B, b));

[A | Numeric?(A)]
inline overload equals?(a:A, b:A) = numericEquals?(a, b);

[A | Float?(A) or Imaginary?(A)]
inline overload equals?(a:A, b:A) = floatOrderedAndEquals?(a, b);


[A, B | Numeric?(A, B)]
inline overload notEquals?(a:A, b:B) =
    notEquals?(toBiggerNumericType(A, B, a),
               toBiggerNumericType(A, B, b));

[A | Numeric?(A)]
inline overload notEquals?(a:A, b:A) = not equals?(a, b);

[A | Float?(A) or Imaginary?(A)]
inline overload notEquals?(a:A, b:A) = floatUnorderedOrNotEquals?(a, b);


[A,B | Numeric?(A,B)]
inline overload lesser?(a:A, b:B) =
    lesser?(toBiggerNumericType(A, B, a),
            toBiggerNumericType(A, B, b));

[A | Numeric?(A)]
inline overload lesser?(a:A, b:A) = numericLesser?(a, b);

[A | Float?(A) or Imaginary?(A)]
inline overload lesser?(a:A, b:A) = floatOrderedAndLesser?(a, b);


[A, B | Numeric?(A, B)]
inline overload lesserEquals?(a:A, b:B) =
    lesserEquals?(toBiggerNumericType(A, B, a),
                  toBiggerNumericType(A, B, b));

[A | Numeric?(A)]
inline overload lesserEquals?(a:A, b:A) = not lesser?(b, a);

[A | Float?(A) or Imaginary?(A)]
inline overload lesserEquals?(a:A, b:A) = floatOrderedAndLesserEquals?(a, b);


[A,B | Numeric?(A,B)]
inline overload greater?(a:A, b:B) =
    greater?(toBiggerNumericType(A, B, a),
             toBiggerNumericType(A, B, b));

[A | Numeric?(A)]
inline overload greater?(a:A, b:A) = lesser?(b, a);

[A | Float?(A) or Imaginary?(A)]
inline overload greater?(a:A, b:A) = floatOrderedAndGreater?(a, b);


[A, B | Numeric?(A, B)]
inline overload greaterEquals?(a:A, b:B) =
    greaterEquals?(toBiggerNumericType(A, B, a),
                   toBiggerNumericType(A, B, b));

[A | Numeric?(A)]
inline overload greaterEquals?(a:A, b:A) = not lesser?(a, b);

[A | Float?(A) or Imaginary?(A)]
inline overload greaterEquals?(a:A, b:A) = floatOrderedAndGreaterEquals?(a, b);

inline inRange?(x,l,u) =  x<u and l<=x;

inline inRangeEquals?(x,l,u) =  x<=u and l<=x;

[T | Numeric?(T)]
inline zero?(x:T) = x == T(0);



//
// binary ops
//

[A,B | Numeric?(A,B)]
inline overload add(a:A, b:B) =
    add(toBiggerNumericType(A, B, a),
        toBiggerNumericType(A, B, b));

[A | Numeric?(A)]
inline overload add(a:A, b:A) = numericAdd(a, b);


[A,B | Numeric?(A,B)]
inline overload subtract(a:A, b:B) =
    subtract(toBiggerNumericType(A, B, a),
             toBiggerNumericType(A, B, b));

[A | Numeric?(A)]
inline overload subtract(a:A, b:A) = numericSubtract(a, b);


[A,B | Numeric?(A,B)]
inline overload multiply(a:A, b:B) =
    multiply(toBiggerNumericType(A, B, a),
             toBiggerNumericType(A, B, b));

[A | Numeric?(A)]
inline overload multiply(a:A, b:A) = numericMultiply(a, b);


[A,B | Numeric?(A,B)]
inline overload divide(a:A, b:B) =
    divide(toBiggerNumericType(A, B, a),
           toBiggerNumericType(A, B, b));

[A | Numeric?(A)]
inline overload divide(a:A, b:A) = numericDivide(a, b);


[A,B | Integer?(A,B)]
inline overload remainder(a:A, b:B) =
    remainder(toBiggerNumericType(A, B, a),
              toBiggerNumericType(A, B, b));

[A | Integer?(A)]
inline overload remainder(a:A, b:A) = integerRemainder(a, b);

//
// Imaginary binary ops
//


[A,B | Numeric?(A) and Imaginary?(B)]
inline overload multiply(a:A, b:B) = numericMultiply(numericConvert(B,a), b);

[A,B | Numeric?(B) and Imaginary?(A)]
inline overload multiply(a:A, b:B) = numericMultiply(a, numericConvert(A,b));

[A | Imaginary?(A)]
inline overload multiply(a:A, b:A) = numericConvert(ImagBaseType(A),-numericMultiply(a, b));

[A,B | Numeric?(A) and Imaginary?(B)]
inline overload divide(a:A, b:B) = numericDivide(numericConvert(B,-a), b);

[A,B | Numeric?(B) and Imaginary?(A)]
inline overload divide(a:A, b:B) = numericDivide(a, numericConvert(A,b));

[A | Imaginary?(A)]
inline overload divide(a:A, b:A) = numericConvert(ImagBaseType(A),numericDivide(a, b));


//
// unary ops
//

[A | Numeric?(A)]
inline overload plus(a:A) = a;

[A | Numeric?(A) and not UnsignedInteger?(A)]
inline overload minus(a:A) = numericNegate(a);



//
// inc, dec
//

[A | Numeric?(A)]
inline overload inc(a:A) {
    a += A(1);
}

[A | Numeric?(A)]
inline overload dec(a:A) {
    a -= A(1);
}



//
// bitwise ops
//

[A,B | Integer?(A,B)]
inline overload bitshl(a:A, b:B) = integerShiftLeft(a, A(b));

[A,B | Integer?(A,B)]
inline overload bitshr(a:A, b:B) = integerShiftRight(a, A(b));

[A,B | Integer?(A,B)]
inline overload bitand(a:A, b:B) =
    integerBitwiseAnd(wrapToBiggerNumericType(A, B, a),
                      wrapToBiggerNumericType(A, B, b));

[A | Integer?(A)]
inline overload bitand(a:A, b:A) = integerBitwiseAnd(a, b);

[A,B | Integer?(A,B)]
inline overload bitor(a:A, b:B) =
    integerBitwiseOr(wrapToBiggerNumericType(A, B, a),
                     wrapToBiggerNumericType(A, B, b));

[A | Integer?(A)]
inline overload bitor(a:A, b:A) = integerBitwiseOr(a, b);

[A,B | Integer?(A,B)]
inline overload bitxor(a:A, b:B) =
    integerBitwiseXor(wrapToBiggerNumericType(A, B, a),
                      wrapToBiggerNumericType(A, B, b));

[A | Integer?(A)]
inline overload bitxor(a:A, b:A) = integerBitwiseXor(a, b);

[A | Integer?(A)]
inline overload bitnot(a:A) = integerBitwiseNot(a);



//
// parse numbers from strings
//

[S | String?(S)]
inline overload Int8(s:S)  = parseInt8(s);

[S | String?(S)]
inline overload Int16(s:S) = parseInt16(s);

[S | String?(S)]
inline overload Int32(s:S) = parseInt32(s);

[S | String?(S)]
inline overload Int64(s:S) = parseInt64(s);

[S | String?(S)]
inline overload UInt8(s:S)  = parseUInt8(s);

[S | String?(S)]
inline overload UInt16(s:S) = parseUInt16(s);

[S | String?(S)]
inline overload UInt32(s:S) = parseUInt32(s);

[S | String?(S)]
inline overload UInt64(s:S) = parseUInt64(s);

[S | String?(S)]
inline overload Float32(s:S) = parseFloat32(s);

[S | String?(S)]
inline overload Float64(s:S) = parseFloat64(s);

[S | String?(S)]
inline overload Float80(s:S) = parseFloat80(s);

[S | String?(S)]
inline overload Imag32(s:S) = parseImag32(s);

[S | String?(S)]
inline overload Imag64(s:S) = parseImag64(s);

[S | String?(S)]
inline overload Imag80(s:S) = parseImag80(s);



//
// octal()

private define octalDigit;
overload octalDigit(static #"0") = 0;
overload octalDigit(static #"1") = 1;
overload octalDigit(static #"2") = 2;
overload octalDigit(static #"3") = 3;
overload octalDigit(static #"4") = 4;
overload octalDigit(static #"5") = 5;
overload octalDigit(static #"6") = 6;
overload octalDigit(static #"7") = 7;
//
[T,I |Integer?(T) and Identifier?(I)]
private _octal(static T, static I)
    = static foldlValues((n,d) -> T(8)*T(n) + T(octalDigit(d)), T(0), ..IdentifierChars(I));

define octal;
overload octal(T, I) = unwrapStatic(_octal(T, I));
overload octal(I) = octal(Int, I);


//
// overflow-checked math
//

IntegerOverflowChecks?() = AllAssertionsEnabled?()
    and AssertionEnabled?(#overflowChecks);

[A | Integer?(A) and IntegerOverflowChecks?()]
inline overload add(a:A, b:A) : A
    = integerAddChecked(a, b);
[A | Integer?(A) and IntegerOverflowChecks?()]
inline overload subtract(a:A, b:A) : A
    = integerSubtractChecked(a, b);
[A | Integer?(A) and IntegerOverflowChecks?()]
inline overload multiply(a:A, b:A) : A
    = integerMultiplyChecked(a, b);
[A | Integer?(A) and IntegerOverflowChecks?()]
inline overload divide(a:A, b:A) : A
    = integerDivideChecked(a, b);
[A | Integer?(A) and IntegerOverflowChecks?()]
inline overload remainder(a:A, b:A) : A
    = integerRemainderChecked(a, b);

[A, B | Integer?(A, B) and IntegerOverflowChecks?()]
inline overload bitshl(a:A, b:B) : A
    = integerShiftLeftChecked(a, A(b));

[A | SignedInteger?(A) and IntegerOverflowChecks?()]
inline overload minus(a:A) : A
    = integerNegateChecked(a);

[A, B | Integer?(A) and Numeric?(B) and IntegerOverflowChecks?()]
inline overload A(b:B) : A
    = integerConvertChecked(A, b);


//
// never-checked wrap-on-overflow math
//

[A | Integer?(A)]
inline wrapAdd(a:A, b:A) : A = numericAdd(a, b);
[A | Integer?(A)]
inline wrapSubtract(a:A, b:A) : A = numericSubtract(a, b);
[A | Integer?(A)]
inline wrapMultiply(a:A, b:A) : A = numericMultiply(a, b);
[A | Integer?(A)]
inline wrapDivide(a:A, b:A) : A = numericDivide(a, b);
[A | Integer?(A)]
inline wrapRemainder(a:A, b:A) : A = integerRemainder(a, b);

[A, ..AA | equalValues?(A, ..AA) and countValues(..AA) != 1]
inline overload wrapAdd(a:A, ..aa:AA) : A = foldValues(wrapAdd, a, ..aa);

[A, ..AA | equalValues?(..AA) and countValues(..AA) != 1]
inline overload wrapMultiply(a:A, ..aa:AA) : A = foldValues(wrapMultiply, a, ..aa);

[A, B | Integer?(A, B)]
inline wrapBitshl(a:A, b:B) : A = integerShiftLeft(a, A(b));

[A | SignedInteger?(A)]
inline wrapMinus(a:A) : A = numericNegate(a, b);

[A, B | Integer?(A) and Numeric?(B)]
inline wrapCast(static A, b:B) : A = numericConvert(A, b);
