content: {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE OverloadedLists #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE RankNTypes #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE TupleSections #-}
  
  module Typst.Constructors
    ( getConstructor
    )
  where
  
  import qualified Data.Vector as V
  import qualified Data.Map.Ordered as OM
  import qualified Data.Map as M
  import Data.Time (fromGregorian, secondsToDiffTime)
  import Data.Maybe (fromMaybe, mapMaybe)
  import qualified Data.ByteString as B
  import Typst.Types
  import Typst.Util (makeFunction, makeFunctionWithScope, namedArg, nthArg, allArgs)
  import qualified Data.Set as Set
  import Data.Text (Text)
  import qualified Data.Text as T
  import qualified Data.Text.Encoding as TE
  import Typst.Regex (makeRE)
  import Data.List (genericTake)
  import Control.Monad.Reader (asks)
  import Control.Monad (mplus)
  import Data.Char (ord, chr)
  
  getConstructor :: ValType -> Maybe Val
  getConstructor typ =
    case typ of
      TFloat -> Just $ makeFunction $ VFloat <$> nthArg 1
      TInteger -> Just $ makeFunction $ VInteger <$> nthArg 1
      TRegex -> Just $ makeFunction $ VRegex <$> (nthArg 1 >>= makeRE)
      TVersion -> Just $ makeFunction $ VVersion <$> (asks positional >>= mapM fromVal)
      TString -> Just $ makeFunctionWithScope
        (do
          val <- nthArg 1
          base <- namedArg "base" (10 :: Integer)
          let digitVector :: V.Vector Char
              digitVector = V.fromList $ ['0'..'9'] ++ ['A'..'Z']
          let renderDigit n = digitVector V.!? (fromIntegral n)
          VString <$>
            case val of
              VInteger n | base /= 10
                -> case mDigits base n of
                     Nothing -> fail "Could not convert number to base"
                     Just ds -> maybe
                       (fail "Could not convert number to base")
                       (pure . T.pack)
                       (mapM renderDigit ds)
              _ -> fromVal val `mplus` pure (repr val))
        [ ( "to-unicode",
             makeFunction $ do
               (val :: Text) <- nthArg 1
               case T.uncons val of
                 Just (c, t) | T.null t ->
                   pure $ VInteger $ fromIntegral $ ord c
                 _ -> fail "to-unicode expects a single character" )
        , ( "from-unicode",
             makeFunction $ do
               (val :: Int) <- nthArg 1
               pure $ VString $ T.pack [chr val] )
        ]
      TLabel -> Just $ makeFunction $ VLabel <$> nthArg 1
      TSymbol -> Just $ makeFunction $ do
          (t :: Text) <- nthArg 1
          vs <- drop 1 <$> allArgs
          variants <-
            mapM
              ( \case
                  VArray [VString k, VString v] ->
                    pure (Set.fromList (T.split (== '.') k), v)
                  _ -> fail "wrong type in symbol arguments"
              )
              vs
          pure $ VSymbol $ Symbol t False variants
      TDateTime -> Just $ makeFunction $ do
        mbyr <- namedArg "year" Nothing
        mbmo <- namedArg "month" Nothing
        mbda <- namedArg "day" Nothing
        mbhr <- namedArg "hour" Nothing
        mbmn <- namedArg "minute" Nothing
        mbsc <- namedArg "second" Nothing
        let mbday = case (mbyr, mbmo, mbda) of
                       (Nothing, _, _) -> Nothing
                       (Just yr, _, _) -> Just $
                         fromGregorian yr (fromMaybe 1 mbmo) (fromMaybe 1 mbda)
        let mbdifftime = case (mbhr, mbmn, mbsc) of
                                (Nothing, _, _) -> Nothing
                                (Just hr, _, _) -> Just $ secondsToDiffTime $
                                  (hr * 60 * 60) + maybe 0 (* 60) mbmn +
                                    fromMaybe 0 mbsc
        pure $ VDateTime mbday mbdifftime
      TDict -> Just $ makeFunction $ do
        a <- nthArg 1
        case a of
          VModule _ m -> pure $ VDict $ OM.fromList $ M.toList m
          _ -> fail "dictionary constructor requires a module as argument"
      TBytes -> Just $ makeFunction $ do
        x <- nthArg 1
        let extractWord8 (VInteger w) = Just $ fromIntegral w
            extractWord8 _ = Nothing
        case x of
          VString s -> pure $ VBytes $ TE.encodeUtf8 s
          VArray xs -> pure $ VBytes $ B.pack $ mapMaybe extractWord8 $ V.toList xs
          _ -> fail "bytes constructor requires a string or array as argument"
      TArguments -> Nothing
      -- TODO https://typst.app/docs/reference/foundations/arguments/
      TSelector -> Nothing
      -- TODO https://typst.app/docs/reference/foundations/selector/
      TCounter -> Nothing
      -- TODO https://typst.app/docs/reference/introspection/counter/
      _ -> Nothing
  
  
  -- mDigitsRev, mDigits from the unmaintained digits package
  -- https://hackage.haskell.org/package/digits-0.3.1
  -- (c) 2009-2016 Henry Bucklow, Charlie Harvey -- BSD-3-Clause license.
  mDigitsRev :: Integral n
      => n         -- ^ The base to use.
      -> n         -- ^ The number to convert to digit form.
      -> Maybe [n] -- ^ Nothing or Just the digits of the number in list form, in reverse.
  mDigitsRev base i = if base < 1
                      then Nothing -- We do not support zero or negative bases
                      else Just $ dr base i
      where
        dr _ 0 = []
        dr b x = case base of
                  1 -> genericTake x $ repeat 1
                  _ -> let (rest, lastDigit) = quotRem x b
                       in lastDigit : dr b rest
  
  -- | Returns the digits of a positive integer as a Maybe list.
  --   or Nothing if a zero or negative base is given
  mDigits :: Integral n
      => n -- ^ The base to use.
      -> n -- ^ The number to convert to digit form.
      -> Maybe [n] -- ^ Nothing or Just the digits of the number in list form
  mDigits base i = reverse <$> mDigitsRev base i
  