{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONS_HADDOCK hide #-}

-- |
-- Copyright: © 2018-2020 IOHK
-- License: Apache-2.0
--
module Internal.Coin
    (
      -- * Types
      Coin

      -- Construction and Deconstruction
    , coinFromIntegral
    , coinFromNatural
    , coinToIntegral
    , coinToNatural

      -- * Unary Operations
    , pred
    , succ

      -- * Binary Operations
    , add
    , sub
    , mul
    , div
    , mod

      -- * Calculating Distances
    , distance

      -- * Value Tests
    , isZero

      -- * Special Values
    , zero
    , one

    ) where

import Prelude hiding
    ( div, fromIntegral, mod, pred, succ )

import GHC.Generics
    ( Generic )
import Numeric.Natural
    ( Natural )
import Quiet
    ( Quiet (Quiet) )

import qualified Prelude

-- | Represents a non-negative integral amount of currency.
--
-- Use 'coinFromNatural' to create a coin from a natural number.
--
-- Use 'coinToNatural' to convert a coin into a natural number.
--
-- @since 1.0.0
newtype Coin = Coin { unCoin :: Natural }
    deriving stock (Eq, Generic, Ord)
    deriving Show via (Quiet Coin)

-- | Creates a coin from an integral number.
--
-- Returns a coin if (and only if) the given input is not negative.
--
coinFromIntegral :: Integral i => i -> Maybe Coin
coinFromIntegral i
    | i >= 0    = Just $ Coin $ Prelude.fromIntegral i
    | otherwise = Nothing

-- | Creates a coin from a natural number.
--
-- @since 1.0.0
coinFromNatural :: Natural -> Coin
coinFromNatural = Coin

-- | Converts the given coin into an integral number.
--
coinToIntegral :: Integral i => Coin -> i
coinToIntegral (Coin i) = Prelude.fromIntegral i

-- | Converts the given coin into a natural number.
--
-- @since 1.0.0
coinToNatural :: Coin -> Natural
coinToNatural = unCoin

add :: Coin -> Coin -> Coin
add (Coin x) (Coin y) = Coin $ x + y

sub :: Coin -> Coin -> Maybe Coin
sub (Coin x) (Coin y) = coinFromIntegral $ toInteger x - toInteger y

mul :: Integral i => Coin -> i -> Maybe Coin
mul (Coin x) y = coinFromIntegral $ toInteger x * toInteger y

div :: Integral i => Coin -> i -> Maybe Coin
div (Coin x) y
    | y <= 0    = Nothing
    | otherwise = coinFromIntegral $ toInteger x `Prelude.div` toInteger y

mod :: Integral i => Coin -> i -> Maybe Coin
mod (Coin x) y
    | y <= 0    = Nothing
    | otherwise = coinFromIntegral $ toInteger x `Prelude.mod` toInteger y

distance :: Coin -> Coin -> Coin
distance (Coin x) (Coin y)
    | x >= y    = Coin $ x - y
    | otherwise = Coin $ y - x

pred :: Coin -> Maybe Coin
pred x = x `sub` one

succ :: Coin -> Coin
succ x = x `add` one

isZero :: Coin -> Bool
isZero = (== zero)

zero :: Coin
zero = Coin 0

one :: Coin
one = Coin 1

newtype Sum a = Sum { getSum :: a }
    deriving stock (Eq, Generic, Ord)
    deriving Show via (Quiet (Sum a))

instance Monoid Coin where
    mempty = zero

instance Semigroup Coin where
    (<>) = add