//
//  Zatoshi.swift
//  secant-testnet
//
//  Created by Lukáš Korba on 26.05.2022.
//  Modified and ported to PirateLightClientKit by Francisco Gindre

import Foundation

public struct Zatoshi {
    public enum Constants {
        public static let oneZecInZatoshi: Int64 = 100_000_000
        public static let maxZecSupply: Int64 = 21_000_000
        public static let maxZatoshi: Int64 = Constants.oneZecInZatoshi * Constants.maxZecSupply
    }
    
    public static var zero: Zatoshi { Zatoshi() }
    
    public static let decimalHandler = NSDecimalNumberHandler(
        roundingMode: NSDecimalNumber.RoundingMode.bankers,
        scale: 8,
        raiseOnExactness: true,
        raiseOnOverflow: true,
        raiseOnUnderflow: true,
        raiseOnDivideByZero: true
    )
    
    @Clamped(-Constants.maxZatoshi...Constants.maxZatoshi)
    public var amount: Int64 = 0

    /// Converts `Zatoshi` to `NSDecimalNumber`
    public var decimalValue: NSDecimalNumber {
        NSDecimalNumber(decimal: Decimal(amount) / Decimal(Constants.oneZecInZatoshi))
    }

    public init(_ amount: Int64 = 0) {
        self.amount = amount
    }
    /// Converts `Zatoshi` to human readable format, up to 8 fraction digits
    public func decimalString(formatter: NumberFormatter = NumberFormatter.zcashNumberFormatter) -> String {
        formatter.string(from: decimalValue.roundedZec) ?? ""
    }

    /// Converts `Decimal` to `Zatoshi`
    public static func from(decimal: Decimal) -> Zatoshi {
        let roundedZec = NSDecimalNumber(decimal: decimal).roundedZec
        let zec2zatoshi = Decimal(Constants.oneZecInZatoshi) * roundedZec.decimalValue
        return Zatoshi(NSDecimalNumber(decimal: zec2zatoshi).int64Value)
    }

    /// Converts `String` to `Zatoshi`
    public static func from(decimalString: String, formatter: NumberFormatter = NumberFormatter.zcashNumberFormatter) -> Zatoshi? {
        if let number = formatter.number(from: decimalString) {
            return Zatoshi.from(decimal: number.decimalValue)
        }
        
        return nil
    }
    
    public static func + (left: Zatoshi, right: Zatoshi) -> Zatoshi {
        Zatoshi(left.amount + right.amount)
    }

    public static func - (left: Zatoshi, right: Zatoshi) -> Zatoshi {
        Zatoshi(left.amount - right.amount)
    }
}

extension Zatoshi: Equatable {
    public static func == (lhs: Zatoshi, rhs: Zatoshi) -> Bool {
        lhs.amount == rhs.amount
    }
}

extension Zatoshi: Comparable {
    public static func < (lhs: Zatoshi, rhs: Zatoshi) -> Bool {
        lhs.amount < rhs.amount
    }
}

public extension NSDecimalNumber {
    /// Round the decimal to 8 fraction digits
    var roundedZec: NSDecimalNumber {
        self.rounding(accordingToBehavior: Zatoshi.decimalHandler)
    }

    /// Converts `NSDecimalNumber` to human readable format, up to 8 fraction digits
    var decimalString: String {
        self.roundedZec.stringValue
    }
}

extension Zatoshi: Codable {
    enum CodingKeys: String, CodingKey {
        case amount
    }

    public init(from decoder: Decoder) throws {
        do {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            self.amount = try values.decode(Int64.self, forKey: .amount)
        } catch {
            throw ZcashError.zatoshiDecode(error)
        }
    }
    
    public func encode(to encoder: Encoder) throws {
        do {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(self.amount, forKey: .amount)
        } catch {
            throw ZcashError.zatoshiEncode(error)
        }
    }
}
