{-# language DeriveFunctor, DeriveFoldable, DeriveTraversable, DeriveGeneric #-}
{-# language InstanceSigs, ScopedTypeVariables, TypeApplications #-}
{-# language LambdaCase #-}
{-# language TemplateHaskell #-}

{-|
Module      : Language.Python.Syntax.Numbers
Copyright   : (C) CSIRO 2017-2019
License     : BSD3
Maintainer  : Isaac Elliott <isaace71295@gmail.com>
Stability   : experimental
Portability : non-portable

Numerical literal values in Python
-}

module Language.Python.Syntax.Numbers
  ( -- * Datatypes
    IntLiteral(..)
  , Sign(..)
  , E(..)
  , FloatExponent(..)
  , FloatLiteral(..)
  , ImagLiteral(..)
    -- * Rendering
    -- | The output of these functions is guaranteed to be valid Python code
  , showIntLiteral
  , showFloatLiteral
  , showFloatExponent
  , showImagLiteral
  )
where

import Control.Lens.Lens (Lens')
import Control.Lens.Review ((#))
import Data.Deriving (deriveEq1, deriveOrd1)
import Data.Digit.Binary (BinDigit)
import Data.Digit.Char (charHeXaDeCiMaL, charOctal, charBinary, charDecimal)
import Data.Digit.Octal (OctDigit)
import Data.Digit.Decimal (DecDigit)
import Data.Digit.Hexadecimal.MixedCase (HeXDigit)
import Data.Generics.Product.Typed (typed)
import Data.List.NonEmpty (NonEmpty)
import Data.Semigroup ((<>))
import Data.Text (Text)
import Data.These (These(..))
import GHC.Generics (Generic)

import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Text as Text

import Language.Python.Syntax.Ann

-- | An integer literal value.
--
-- @5@ is an integer literal.
--
-- @6.2@ is a literal but is not an integer
--
-- @x@ might be an integer, but is not a literal
--
-- See <https://docs.python.org/3.5/reference/lexical_analysis.html#integer-literals>
data IntLiteral a
  -- | Decimal
  --
  -- @1234@
  = IntLiteralDec
  { _intLiteralAnn :: Ann a
  , _unsafeIntLiteralDecValue :: NonEmpty DecDigit
  }
  -- | Binary
  --
  -- @0b10110@
  | IntLiteralBin
  { _intLiteralAnn :: Ann a
  , _unsafeIntLiteralBinUppercase :: Bool
  , _unsafeIntLiteralBinValue :: NonEmpty BinDigit
  }
  -- | Octal
  --
  -- @0o1367@
  | IntLiteralOct
  { _intLiteralAnn :: Ann a
  , _unsafeIntLiteralOctUppercase :: Bool
  , _unsafeIntLiteralOctValue :: NonEmpty OctDigit
  }
  -- | Mixed-case hexadecimal
  --
  -- @0x18B4f@
  | IntLiteralHex
  { _intLiteralAnn :: Ann a
  , _unsafeIntLiteralHexUppercase :: Bool
  , _unsafeIntLiteralHexValue :: NonEmpty HeXDigit
  }
  deriving (Eq, Show, Functor, Foldable, Traversable, Generic)
deriveEq1 ''IntLiteral
deriveOrd1 ''IntLiteral

instance HasAnn IntLiteral where
  annot :: forall a. Lens' (IntLiteral a) (Ann a)
  annot = typed @(Ann a)

-- | Positive or negative, as in @-7@
data Sign = Pos | Neg deriving (Eq, Ord, Show, Generic)

-- | When a floating point literal is in scientific notation, it includes the character
-- @e@, which can be lower or upper case.
data E = Ee | EE deriving (Eq, Ord, Show, Generic)

-- | The exponent of a floating point literal.
--
-- An @e@, followed by an optional 'Sign', followed by at least one digit.
data FloatExponent = FloatExponent E (Maybe Sign) (NonEmpty DecDigit)
  deriving (Eq, Ord, Show, Generic)

-- | A literal floating point value.
--
-- Eg. @7.63@
--
-- See <https://docs.python.org/3.5/reference/lexical_analysis.html#floating-point-literals>
data FloatLiteral a
  -- | \'Complete\' floats
  --
  -- @12.@
  --
  -- @12.34@
  --
  -- @12.e34@
  --
  -- @12.34e56@
  = FloatLiteralFull
  { _floatLiteralAnn :: Ann a
  , _floatLiteralFullLeft :: NonEmpty DecDigit
  , _floatLiteralFullRight
      :: Maybe (These (NonEmpty DecDigit) FloatExponent)
  }
  -- | Floats that begin with a decimal point
  --
  -- @.12@
  --
  -- @.12e34@
  | FloatLiteralPoint
  { _floatLiteralAnn :: Ann a
  -- . [0-9]+
  , _floatLiteralPointRight :: NonEmpty DecDigit
  -- [ 'e' ['-' | '+'] [0-9]+ ]
  , _floatLiteralPointExponent :: Maybe FloatExponent
  }
  -- | Floats with no decimal points
  --
  -- @12e34@
  | FloatLiteralWhole
  { _floatLiteralAnn :: Ann a
  -- [0-9]+
  , _floatLiteralWholeRight :: NonEmpty DecDigit
  -- [ 'e' ['-' | '+'] [0-9]+ ]
  , _floatLiteralWholeExponent :: FloatExponent
  }
  deriving (Eq, Show, Functor, Foldable, Traversable, Generic)
deriveEq1 ''FloatLiteral
deriveOrd1 ''FloatLiteral

instance HasAnn FloatLiteral where
  annot :: forall a. Lens' (FloatLiteral a) (Ann a)
  annot = typed @(Ann a)

-- | Imaginary number literals
--
-- See <https://docs.python.org/3.5/reference/lexical_analysis.html#imaginary-literals>
data ImagLiteral a
  -- | A decimal integer followed by a \'j\'
  --
  -- @12j@
  = ImagLiteralInt
  { _imagLiteralAnn :: Ann a
  , _unsafeImagLiteralIntValue :: NonEmpty DecDigit
  , _imagLiteralUppercase :: Bool
  }
  -- | A float followed by a \'j\'
  --
  -- @12.j@
  --
  -- @12.3j@
  --
  -- @.3j@
  | ImagLiteralFloat
  { _imagLiteralAnn :: Ann a
  , _unsafeImagLiteralFloatValue :: FloatLiteral a
  , _imagLiteralUppercase :: Bool
  }
  deriving (Eq, Show, Functor, Foldable, Traversable, Generic)
deriveEq1 ''ImagLiteral
deriveOrd1 ''ImagLiteral

instance HasAnn ImagLiteral where
  annot :: forall a. Lens' (ImagLiteral a) (Ann a)
  annot = typed @(Ann a)

showIntLiteral :: IntLiteral a -> Text
showIntLiteral (IntLiteralDec _ n) =
  Text.pack $
  (charDecimal #) <$> NonEmpty.toList n
showIntLiteral (IntLiteralBin _ b n) =
  Text.pack $
  '0' : (if b then 'B' else 'b') : fmap (charBinary #) (NonEmpty.toList n)
showIntLiteral (IntLiteralOct _ b n) =
  Text.pack $
  '0' : (if b then 'O' else 'o') : fmap (charOctal #) (NonEmpty.toList n)
showIntLiteral (IntLiteralHex _ b n) =
  Text.pack $
  '0' : (if b then 'X' else 'x') : fmap (charHeXaDeCiMaL #) (NonEmpty.toList n)

showFloatExponent :: FloatExponent -> Text
showFloatExponent (FloatExponent e s ds) =
  Text.pack $
  (case e of; EE -> 'E'; Ee -> 'e') :
  foldMap (\case; Pos -> "+"; Neg -> "-") s <>
  fmap (charDecimal #) (NonEmpty.toList ds)

showFloatLiteral :: FloatLiteral a -> Text
showFloatLiteral (FloatLiteralFull _ a b) =
  Text.pack (fmap (charDecimal #) (NonEmpty.toList a) <> ".") <>
  foldMap
    (\case
       This x -> Text.pack $ fmap (charDecimal #) (NonEmpty.toList x)
       That x -> showFloatExponent x
       These x y ->
         Text.pack (fmap (charDecimal #) (NonEmpty.toList x)) <>
         showFloatExponent y)
    b
showFloatLiteral (FloatLiteralPoint _ a b) =
  Text.pack ('.' : fmap (charDecimal #) (NonEmpty.toList a)) <>
  foldMap showFloatExponent b
showFloatLiteral (FloatLiteralWhole _ a b) =
  Text.pack (fmap (charDecimal #) (NonEmpty.toList a)) <>
  showFloatExponent b

showImagLiteral :: ImagLiteral a -> Text
showImagLiteral (ImagLiteralInt _ ds b) =
  Text.pack $ fmap (charDecimal #) (NonEmpty.toList ds) ++ [if b then 'J' else 'j']
showImagLiteral (ImagLiteralFloat _ f b) =
  showFloatLiteral f <> Text.singleton (if b then 'J' else 'j')
