import { bind , Money , AnyCurrencyUnit } from "@easymoney/core" ; import { MoneyInput } from "./types" ; import { RoundingModes , RoundingModesType } from "@easymoney/core" ; import { assert } from "@easymoney/core" ; import { isNumeric } from "../assert" ; import { CalculatorBase } from "../calculator/" ; import { MoneyBase } from "./types" ; import { fromString , fromNumber } from "../number" ; import { PrivateInstance , Instance } from "./types" ; function construct < CT > ( amount , currency ) { let newAmount = null ; if ( ! Number . isInteger ( Number ( amount ) ) ) { let numberFromString = fromString ( ( amount as unknown ) as string ) ; if ( ! numberFromString . isInteger ( ) ) { throw new Error ( "Amount must be an integer(ish) value" ) ; } newAmount = numberFromString . getIntegerPart ( ) ; } return { amount : newAmount ? newAmount : fromString ( String ( amount ) ) . toString ( ) , currency } ; } const createMoneyFactory = ( calculator ) => < CT > ( { amount , currency } ) => { const money = construct ( amount , currency ) ; const privateInstance = { calculator , instanceMoney : money } as PrivateInstance < CT > ; privateInstance . round = bind ( round , privateInstance ) ; const publicInstance = { } as MoneyBase < CT > ; const instance = { privateInstance , publicInstance } ; publicInstance . add = bind ( add , instance ) ; publicInstance . isSameCurrency = bind ( isSameCurrency , publicInstance ) ; publicInstance . getAmount = bind ( getAmount , privateInstance ) ; publicInstance . getCurrency = bind ( getCurrency , privateInstance ) ; publicInstance . equals = bind ( equals , instance ) ; publicInstance . compare = bind ( compare , instance ) ; publicInstance . greaterThan = bind ( greaterThan , publicInstance ) ; publicInstance . greaterThanOrEqual = bind ( greaterThanOrEqual , publicInstance ) ; publicInstance . lessThan = bind ( lessThan , publicInstance ) ; publicInstance . lessThanOrEqual = bind ( lessThanOrEqual , publicInstance ) ; publicInstance . subtract = bind ( subtract , instance ) ; publicInstance . multiply = bind ( multiply , instance ) ; publicInstance . divide = bind ( divide , instance ) ; publicInstance . allocate = bind ( allocate , instance ) ; publicInstance . allocateTo = bind ( allocateTo , instance ) ; publicInstance . mod = bind ( mod , instance ) ; publicInstance . absolute = bind ( absolute , instance ) ; publicInstance . negative = bind ( negative , instance ) ; publicInstance . isZero = bind ( isZero , instance ) ; publicInstance . isPositive = bind ( isPositive , instance ) ; publicInstance . isNegative = bind ( isNegative , instance ) ; publicInstance . ratioOf = bind ( ratioOf , instance ) ; return publicInstance ; } ; export function createMoneyUnit ( calculator ) { return createMoneyFactory ( calculator ) ; } function round < CT > ( privateInstance , amount , roundingMode ) { assertRoundingMode ( roundingMode ) ; const { calculator } = privateInstance ; if ( roundingMode === RoundingModes . CEILING ) { return calculator . ceil ( amount ) ; } if ( roundingMode === RoundingModes . FLOOR ) { return calculator . floor ( amount ) ; } return calculator . round ( amount , roundingMode ) ; } function add < CT > ( instance , money ) { const { publicInstance , privateInstance } = instance ; const { calculator , instanceMoney } = privateInstance ; assertSameCurrency ( publicInstance , money ) ; const newAmount = calculator . add ( money . getAmount ( ) , instanceMoney . amount ) ; return createMoneyFactory ( privateInstance . calculator ) ( { amount : newAmount , currency : money . getCurrency ( ) } ) ; } function subtract < CT > ( instance , money ) { const { publicInstance , privateInstance } = instance ; const { calculator } = privateInstance ; assertSameCurrency ( publicInstance , money ) ; const newAmount = calculator . subtract ( publicInstance . getAmount ( ) , money . getAmount ( ) ) ; return createMoneyFactory ( privateInstance . calculator ) ( { amount : newAmount , currency : money . getCurrency ( ) } ) ; } function getAmount < CT > ( privateInstance ) { return privateInstance . instanceMoney . amount ; } function getCurrency < CT > ( privateInstance ) { return privateInstance . instanceMoney . currency ; } function isSameCurrency < CT > ( moneyInstance , money ) { return moneyInstance . getCurrency ( ) === money . getCurrency ( ) ; } function assertSameCurrency < CT > ( moneyInstance , money ) { assert ( isSameCurrency ( moneyInstance , money ) , new TypeError ( "Currencies must be identical" ) ) ; } function equals < CT > ( moneyInstance , money ) { return ( moneyInstance . publicInstance . isSameCurrency ( money ) && moneyInstance . privateInstance . instanceMoney . amount === money . getAmount ( ) ) ; } function compare < CT > ( instance , money ) { const { publicInstance , privateInstance } = instance ; assertSameCurrency ( publicInstance , money ) ; return privateInstance . calculator . compare ( privateInstance . instanceMoney . amount , money . getAmount ( ) ) ; } function greaterThan < CT > ( publicInstance , money ) { return publicInstance . compare ( money ) > 0 ; } function greaterThanOrEqual < CT > ( publicInstance , money ) { return publicInstance . compare ( money ) >= 0 ; } function lessThan < CT > ( publicInstance , money ) { return publicInstance . compare ( money ) < 0 ; } function lessThanOrEqual < CT > ( publicInstance , money ) { return publicInstance . compare ( money ) <= 0 ; } function multiply < CT > ( instance , multiplier , roundingMode = RoundingModes . HALF_EVEN ) { assertOperand ( multiplier ) ; assertRoundingMode ( roundingMode ) ; const { publicInstance , privateInstance } = instance ; const { calculator } = privateInstance ; const newAmount = calculator . multiply ( publicInstance . getAmount ( ) , multiplier ) ; const roundedAmount = privateInstance . round ( newAmount , roundingMode ) ; return createMoneyFactory ( privateInstance . calculator ) ( { amount : roundedAmount , currency : publicInstance . getCurrency ( ) } ) ; } function assertOperand ( operand ) { assert ( isNumeric ( operand ) , new TypeError ( "Operand should be a numeric value." ) ) ; } function assertRoundingMode ( roundingMode ) { assert ( [ RoundingModes . CEILING , RoundingModes . DOWN , RoundingModes . FLOOR , RoundingModes . HALF_DOWN , RoundingModes . HALF_EVEN , RoundingModes . HALF_UP , RoundingModes . UP ] . includes ( roundingMode ) , new TypeError ( "rounding mode should be one of RoundingModes.CEILING RoundingModes.DOWN RoundingModes.FLOOR RoundingModes.HALF_DOWN RoundingModes.HALF_EVEN RoundingModes.HALF_UP RoundingModes.UP" ) ) ; } function divide < CT > ( instance , divisor , roundingMode = RoundingModes . HALF_EVEN ) { assertOperand ( divisor ) ; assertRoundingMode ( roundingMode ) ; const { privateInstance , publicInstance } = instance ; const { round , calculator } = privateInstance ; const result = fromNumber ( divisor ) . toString ( ) ; if ( calculator . compare ( result , "0" ) === 0 ) { throw new TypeError ( "Division by zero" ) ; } const quotient = round ( calculator . divide ( publicInstance . getAmount ( ) , result ) , roundingMode ) ; return createMoneyFactory ( calculator ) ( { amount : quotient , currency : publicInstance . getCurrency ( ) } ) ; } function allocate < CT > ( instance , ratios ) { if ( ratios . length === 0 ) { throw new TypeError ( "Cannot allocate to none, ratios cannot be an empty array" ) ; } const { privateInstance , publicInstance } = instance ; const { calculator } = privateInstance ; let reminder = publicInstance . getAmount ( ) ; const results = [ ] ; const total = ratios . reduce ( ( acc , val ) => acc + val , 0 ) ; if ( total <= 0 ) { throw new TypeError ( "Cannot allocate to none, sum of ratios must be greater than zero" ) ; } for ( let i = 0 ; i < ratios . length ; i ++ ) { const ratio = ratios [ i ] ; if ( ratio < 0 ) { throw new TypeError ( "Cannot allocate to none, ratio must be zero or positive" ) ; } const share = calculator . share ( publicInstance . getAmount ( ) , ratio , total ) ; results [ i ] = share ; reminder = calculator . subtract ( reminder , share ) ; } if ( calculator . compare ( reminder , "0" ) === 0 ) { return results . map ( result => createMoneyFactory ( calculator ) ( { amount : result , currency : publicInstance . getCurrency ( ) } ) ) ; } const fractions = ratios . map ( ratio => { const share = ( ratio / total ) * Number ( publicInstance . getAmount ( ) ) ; return share - Math . floor ( share ) ; } ) ; while ( calculator . compare ( reminder , "0" ) > 0 ) { let index ; if ( fractions . length !== 0 ) { let array = [ ] ; fractions . forEach ( ( fraction , fractIndex ) => { if ( fraction === Math . max ( ... fractions ) ) { array . push ( fractIndex ) ; } } ) ; index = array [ 0 ] ; } else { index = 0 ; } results [ index ] = calculator . add ( results [ index ] , "1" ) ; reminder = calculator . subtract ( reminder , "1" ) ; fractions [ index ] = 0 ; } return results . map ( result => createMoneyFactory ( calculator ) ( { amount : result , currency : publicInstance . getCurrency ( ) } ) ) ; } function allocateTo < CT > ( instance , n ) { if ( ! Number . isInteger ( n ) ) { throw new TypeError ( "Number of targets must be an integer" ) ; } if ( n <= 0 ) { throw new TypeError ( "Cannot allocate to none, target must be greater than zero" ) ; } return instance . publicInstance . allocate ( Array ( n ) . fill ( 1 ) ) ; } function mod < CT > ( instance , divisor ) { assertSameCurrency ( instance . publicInstance , divisor ) ; const { publicInstance , privateInstance } = instance ; const { calculator } = privateInstance ; const newAmount = calculator . mod ( publicInstance . getAmount ( ) , divisor . getAmount ( ) ) ; return createMoneyFactory ( privateInstance . calculator ) ( { amount : newAmount , currency : publicInstance . getCurrency ( ) } ) ; } function absolute < CT > ( instance ) { const { privateInstance , publicInstance } = instance ; return createMoneyFactory ( privateInstance . calculator ) ( { amount : privateInstance . calculator . absolute ( publicInstance . getAmount ( ) ) , currency : publicInstance . getCurrency ( ) } ) ; } function negative < CT > ( instance ) { const { privateInstance , publicInstance } = instance ; const { calculator } = privateInstance ; const newAmount = calculator . subtract ( "0" , publicInstance . getAmount ( ) ) ; return createMoneyFactory ( calculator ) ( { amount : newAmount , currency : publicInstance . getCurrency ( ) } ) ; } function isZero < CT > ( instance ) { const { privateInstance , publicInstance } = instance ; const { calculator } = privateInstance ; return calculator . compare ( publicInstance . getAmount ( ) , "0" ) === 0 ; } function isPositive < CT > ( instance ) { const { privateInstance , publicInstance } = instance ; const { calculator } = privateInstance ; return calculator . compare ( publicInstance . getAmount ( ) , "0" ) > 0 ; } function isNegative < CT > ( instance ) { const { privateInstance , publicInstance } = instance ; const { calculator } = privateInstance ; return calculator . compare ( publicInstance . getAmount ( ) , "0" ) < 0 ; } function ratioOf < CT > ( instance , money ) { if ( money . isZero ( ) ) { throw new TypeError ( "Cannot calculate a ratio of zero" ) ; } const { privateInstance , publicInstance } = instance ; const { calculator } = privateInstance ; return calculator . divide ( publicInstance . getAmount ( ) , money . getAmount ( ) ) ; }