{-# LANGUAGE DerivingStrategies, DeriveAnyClass #-}
{-# LANGUAGE NoImplicitPrelude, ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}

module DSV.NumberViews
  ( InvalidNat (..), byteStringNatView, textNatView
                   , byteStringNatView_, textNatView_
  , InvalidRational (..), byteStringRationalView, textRationalView
                        , byteStringRationalView_, textRationalView_
  , InvalidDollars (..), byteStringDollarsView, textDollarsView
                       , byteStringDollarsView_, textDollarsView_
  ) where

import DSV.AttoView
import DSV.ByteString
import DSV.IO
import DSV.Numbers
import DSV.Prelude
import DSV.Text
import DSV.TextReaderView
import DSV.UTF8
import DSV.Validation
import DSV.ViewType

-- attoparsec
import qualified Data.Attoparsec.ByteString.Char8

data InvalidNat = InvalidNat
  deriving stock (Eq, Show)
  deriving anyclass Exception

byteStringNatView :: View InvalidNat ByteString Natural
byteStringNatView = attoByteStringView InvalidNat p
  where
    p = Data.Attoparsec.ByteString.Char8.decimal

byteStringNatView_ :: View () ByteString Natural
byteStringNatView_ = discardViewError byteStringNatView

textNatView :: View InvalidNat Text Natural
textNatView = textReaderView InvalidNat textReadDecimal

textNatView_ :: View () Text Natural
textNatView_ = discardViewError textNatView

data InvalidRational = InvalidRational
  deriving stock (Eq, Show)
  deriving anyclass Exception

{- |

Read a rational number written in decimal notation.

=== Examples

>>> :set -XOverloadedStrings

>>> applyView byteStringRationalView "1234"
Success (1234 % 1)

>>> applyView byteStringRationalView "1234.567"
Success (1234567 % 1000)

>>> applyView byteStringRationalView "12.3.4"
Failure InvalidRational

-}

byteStringRationalView :: View InvalidRational ByteString Rational
byteStringRationalView =
    textRationalView .
    overViewError (\InvalidUtf8 -> InvalidRational) utf8TextView

byteStringRationalView_ :: View () ByteString Rational
byteStringRationalView_ = discardViewError byteStringRationalView

{- |

Read a rational number written in decimal notation.

=== Examples

>>> :set -XOverloadedStrings

>>> applyView textRationalView "1234"
Success (1234 % 1)

>>> applyView textRationalView "1234.567"
Success (1234567 % 1000)

>>> applyView textRationalView "12.3.4"
Failure InvalidRational

-}

textRationalView :: View InvalidRational Text Rational
textRationalView = textReaderView InvalidRational textReadRational

textRationalView_ :: View () Text Rational
textRationalView_ = discardViewError textRationalView

data InvalidDollars = InvalidDollars
  deriving stock (Eq, Show)
  deriving anyclass Exception

{- | Read a dollar amount.

=== Examples

>>> applyView byteStringDollarsView "$1234.567"
Success (1234567 % 1000)

>>> applyView byteStringDollarsView "1234.567"
Failure InvalidDollars

-}

byteStringDollarsView :: View InvalidDollars ByteString Rational
byteStringDollarsView =
    textDollarsView .
    overViewError (\InvalidUtf8 -> InvalidDollars) utf8TextView

byteStringDollarsView_ :: View () ByteString Rational
byteStringDollarsView_ = discardViewError byteStringDollarsView

{- | Read a dollar amount.

=== Examples

>>> applyView textDollarsView "$1234.567"
Success (1234567 % 1000)

>>> applyView textDollarsView "1234.567"
Failure InvalidDollars

-}

textDollarsView :: View InvalidDollars Text Rational
textDollarsView =
    overViewError
        (\InvalidRational -> InvalidDollars)
        textRationalView
    .
    View (
        maybe (Failure InvalidDollars) Success .
        textStripPrefix "$"
    )

textDollarsView_ :: View () Text Rational
textDollarsView_ = discardViewError textDollarsView