//
//  Decimal.swift
//  Decimal
//
//  Created by Brandon Sneed on 11/7/15.
//  Copyright © 2015 theholygrail.io. All rights reserved.
//

import Foundation

public struct Decimal {
    public let value: NSDecimalNumber
    
    /// Create an instance initialized to zero.
    public init() {
        value = NSDecimalNumber.zero()
    }
    /// Create an instance initialized to `value`.
    public init(_ value: NSDecimalNumber) {
        self.value = value
    }
    
    public init(_ value: NSNumber) {
        self.value = NSDecimalNumber(decimal: value.decimalValue)
    }
    
    public init(_ value: Float) {
        self.value = NSDecimalNumber(float: value)
    }

    public init(_ value: Double) {
        self.value = NSDecimalNumber(double: value)
    }
    
    public init(_ value: String) {
        self.value = NSDecimalNumber(string: value)
    }
}

extension Decimal: CustomStringConvertible {
    /// A textual representation of `self`.
    public var description: String {
        return String(reflecting: value)
    }
}

extension Decimal: CustomDebugStringConvertible {
    /// A textual representation of `self`.
    public var debugDescription: String {
        return String(reflecting: value)
    }
}

extension Decimal /*: FloatingPointType*/ { // It complains about _BitsType missing, no idea wtf that is.
    /// The positive infinity.
    public static var infinity: Decimal {
        return Decimal(Double.infinity)
    }
    /// A quiet NaN.
    public static var NaN: Decimal {
        return Decimal(NSDecimalNumber.notANumber())
    }
    /// A quiet NaN.
    public static var quietNaN: Decimal {
        return NaN
    }
    /// `true` iff `self` is negative.
    public var isSignMinus: Bool {
        return (value as Double).isSignMinus
    }
    /// `true` iff `self` is normal (not zero, subnormal, infinity, or
    /// NaN).
    public var isNormal: Bool {
        return (value as Double).isNormal
    }
    /// `true` iff `self` is zero, subnormal, or normal (not infinity
    /// or NaN).
    public var isFinite: Bool {
        return (value as Double).isFinite
    }
    /// `true` iff `self` is +0.0 or -0.0.
    public var isZero: Bool {
        return (value as Double).isZero
    }
    /// `true` iff `self` is subnormal.
    public var isSubnormal: Bool {
        return (value as Double).isSubnormal
    }
    /// `true` iff `self` is infinity.
    public var isInfinite: Bool {
        return (value as Double).isInfinite
    }
    /// `true` iff `self` is NaN.
    public var isNaN: Bool {
        return (value as Double).isNaN
    }
    /// `true` iff `self` is a signaling NaN.
    public var isSignaling: Bool {
        return (value as Double).isSignaling
    }
}

extension Decimal: Comparable, Equatable {
}

// MARK: Equatable
public func ==(lhs: Decimal, rhs: Decimal) -> Bool {
    return lhs.value.compare(rhs.value) == .OrderedSame
}

// MARK: Comparable
public func <(lhs: Decimal, rhs: Decimal) -> Bool {
    return lhs.value.compare(rhs.value) == .OrderedAscending
}

public func <=(lhs: Decimal, rhs: Decimal) -> Bool {
    let result = lhs.value.compare(rhs.value)
    if result == .OrderedAscending {
        return true
    } else if result == .OrderedSame {
        return true
    }
    return false
}

public func >=(lhs: Decimal, rhs: Decimal) -> Bool {
    let result = lhs.value.compare(rhs.value)
    if result == .OrderedDescending {
        return true
    } else if result == .OrderedSame {
        return true
    }
    return false
}

public func >(lhs: Decimal, rhs: Decimal) -> Bool {
    return lhs.value.compare(rhs.value) == .OrderedDescending
}

extension Decimal: Hashable {
    /// The hash value.
    ///
    /// **Axiom:** `x == y` implies `x.hashValue == y.hashValue`.
    ///
    /// - Note: The hash value is not guaranteed to be stable across
    ///   different invocations of the same program.  Do not persist the
    ///   hash value across program runs.
    public var hashValue: Int {
        return value.hash
    }
}

extension Decimal: IntegerLiteralConvertible {
    public init(integerLiteral value: IntegerLiteralType) {
        self.value = NSDecimalNumber(integer: value)
    }
}

extension Decimal: AbsoluteValuable {
    /// Returns the absolute value of `x`.
    @warn_unused_result
    public static func abs(x: Decimal) -> Decimal {
        if x.value.compare(NSDecimalNumber.zero()) == .OrderedAscending {
            // number is neg, multiply by -1
            let negOne = NSDecimalNumber(mantissa: 1, exponent: 0, isNegative: true)
            return Decimal(x.value.decimalNumberByMultiplyingBy(negOne, withBehavior: nil))
        } else {
            return x
        }
    }
}

extension Decimal {
    public init(_ v: UInt8) {
        value = NSDecimalNumber(unsignedChar: v)
    }
    public init(_ v: Int8) {
        value = NSDecimalNumber(char: v)
    }
    public init(_ v: UInt16) {
        value = NSDecimalNumber(unsignedShort: v)
    }
    public init(_ v: Int16) {
        value = NSDecimalNumber(short: v)
    }
    public init(_ v: UInt32) {
        value = NSDecimalNumber(unsignedInt: v)
    }
    public init(_ v: Int32) {
        value = NSDecimalNumber(int: v)
    }
    public init(_ v: UInt64) {
        value = NSDecimalNumber(unsignedLongLong: v)
    }
    public init(_ v: Int64) {
        value = NSDecimalNumber(longLong: v)
    }
    public init(_ v: UInt) {
        value = NSDecimalNumber(unsignedInteger: v)
    }
    public init(_ v: Int) {
        value = NSDecimalNumber(integer: v)
    }
}

// MARK: Addition operators

public func +(lhs: Decimal, rhs: Decimal) -> Decimal {
    return Decimal(lhs.value.decimalNumberByAdding(rhs.value))
}

public prefix func ++(lhs: Decimal) -> Decimal {
    return Decimal(lhs.value.decimalNumberByAdding(NSDecimalNumber.one()))
}

public postfix func ++(inout lhs: Decimal) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberByAdding(NSDecimalNumber.one()))
    return lhs
}

public func +=(inout lhs: Decimal, rhs: Decimal) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberBySubtracting(rhs.value))
    return lhs
}

public func +=(inout lhs: Decimal, rhs: Int) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberByAdding(NSDecimalNumber(integer: rhs)))
    return lhs
}

public func +=(inout lhs: Decimal, rhs: Double) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberByAdding(NSDecimalNumber(double: rhs)))
    return lhs
}

// MARK: Subtraction operators

public prefix func -(x: Decimal) -> Decimal {
    return Decimal(x.value.decimalNumberByMultiplyingBy(NSDecimalNumber(integer: -1)))
}

public func -(lhs: Decimal, rhs: Decimal) -> Decimal {
    return Decimal(lhs.value.decimalNumberBySubtracting(rhs.value))
}

public prefix func --(lhs: Decimal) -> Decimal {
    return Decimal(lhs.value.decimalNumberBySubtracting(NSDecimalNumber.one()))
}

public postfix func --(inout lhs: Decimal) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberBySubtracting(NSDecimalNumber.one()))
    return lhs
}

public func -=(inout lhs: Decimal, rhs: Decimal) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberBySubtracting(rhs.value))
    return lhs
}

public func -=(inout lhs: Decimal, rhs: Int) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberBySubtracting(NSDecimalNumber(integer: rhs)))
    return lhs
}

public func -=(inout lhs: Decimal, rhs: Double) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberBySubtracting(NSDecimalNumber(double: rhs)))
    return lhs
}


// MARK: Multiplication operators

public func *(inout lhs: Decimal, rhs: Decimal) -> Decimal {
    return Decimal(lhs.value.decimalNumberByMultiplyingBy(rhs.value))
}

public func *=(inout lhs: Decimal, rhs: Decimal) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberByMultiplyingBy(rhs.value))
    return lhs
}

public func *=(inout lhs: Decimal, rhs: Int) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberByMultiplyingBy(NSDecimalNumber(integer: rhs)))
    return lhs
}

public func *=(inout lhs: Decimal, rhs: Double) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberByMultiplyingBy(NSDecimalNumber(double: rhs)))
    return lhs
}

// MARK: Division operators

public func /(lhs: Decimal, rhs: Decimal) -> Decimal {
    return Decimal(lhs.value.decimalNumberByDividingBy(rhs.value))
}

public func /=(inout lhs: Decimal, rhs: Decimal) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberByDividingBy(rhs.value))
    return lhs
}

public func /=(inout lhs: Decimal, rhs: Int) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberByDividingBy(NSDecimalNumber(integer: rhs)))
    return lhs
}

public func /=(inout lhs: Decimal, rhs: Double) -> Decimal {
    lhs = Decimal(lhs.value.decimalNumberByDividingBy(NSDecimalNumber(double: rhs)))
    return lhs
}

// MARK: Power-of operators

public func ^(lhs: Decimal, rhs: Int) -> Decimal {
    return Decimal(lhs.value.decimalNumberByRaisingToPower(rhs))
}

extension Decimal: Strideable {
    /// Returns a stride `x` such that `self.advancedBy(x)` approximates
    /// `other`.
    ///
    /// - Complexity: O(1).
    public func distanceTo(other: Decimal) -> Decimal {
        return self - other
    }
    /// Returns a `Self` `x` such that `self.distanceTo(x)` approximates
    /// `n`.
    ///
    /// - Complexity: O(1).
    public func advancedBy(amount: Decimal) -> Decimal {
        return self + amount
    }

}