{-# LANGUAGE DuplicateRecordFields      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NamedFieldPuns             #-}
{-# LANGUAGE OverloadedStrings          #-}
module IR.Name where

import Data.Word

import Data.Char   (toLower)
import Data.List   (intersperse)
import Data.String (IsString(fromString))

import qualified Data.Set  as S
import qualified Data.Text as T

class HasUnQ a where
    getUnQ :: a -> UnQ
class MkSub a where
    mkSub :: a -> UnQ -> a

instance HasUnQ UnQ where
    getUnQ = id
instance HasUnQ LocalQ where
    getUnQ = localUnQ
instance HasUnQ CapnpQ where
    getUnQ CapnpQ{local} = getUnQ local
instance HasUnQ GlobalQ where
    getUnQ GlobalQ{local} = getUnQ local

newtype UnQ = UnQ T.Text
    deriving(Show, Read, Eq, Ord, IsString)

newtype NS = NS [T.Text]
    deriving(Show, Read, Eq, Ord)

data LocalQ = LocalQ
    { localUnQ :: UnQ
    , localNS  :: NS
    }
    deriving(Show, Read, Eq, Ord)

instance IsString LocalQ where
    fromString s = LocalQ
        { localUnQ = fromString s
        , localNS = emptyNS
        }

-- | A fully qualified name for something defined in a capnproto schema.
-- this includes a local name within a file, and the file's capnp id.
data CapnpQ = CapnpQ
    { local  :: LocalQ
    , fileId :: !Word64
    }
    deriving(Show, Read, Eq, Ord)

data GlobalQ = GlobalQ
    { local    :: LocalQ
    , globalNS :: NS
    }
    deriving(Show, Read, Eq, Ord)

emptyNS :: NS
emptyNS = NS []

mkLocal :: NS -> UnQ -> LocalQ
mkLocal localNS localUnQ = LocalQ{localNS, localUnQ}

unQToLocal :: UnQ -> LocalQ
unQToLocal = mkLocal emptyNS

instance MkSub LocalQ where
    mkSub q = mkLocal (localQToNS q)

instance MkSub GlobalQ where
    mkSub q@GlobalQ{local} unQ = q { local = mkSub local unQ }

instance MkSub CapnpQ where
    mkSub q@CapnpQ{local} unQ = q { local = mkSub local unQ }

localQToNS :: LocalQ -> NS
localQToNS LocalQ{localUnQ=UnQ part, localNS=NS parts} = NS (part:parts)

localToUnQ :: LocalQ -> UnQ
localToUnQ LocalQ{localUnQ, localNS}
    | localNS == emptyNS = localUnQ
    | otherwise = UnQ (renderLocalNS localNS <> "'" <> renderUnQ localUnQ)

renderUnQ :: UnQ -> T.Text
renderUnQ (UnQ name)
    | name `S.member` keywords = name <> "_"
    | otherwise = name
  where
    keywords = S.fromList
        [ "as", "case", "of", "class", "data", "family", "instance", "default"
        , "deriving", "do", "forall", "foreign", "hiding", "if", "then", "else"
        , "import", "infix", "infixl", "infixr", "let", "in", "mdo", "module"
        , "newtype", "proc", "qualified", "rec", "type", "where"
        ]

renderLocalQ :: LocalQ -> T.Text
renderLocalQ = renderUnQ . localToUnQ

renderLocalNS :: NS -> T.Text
renderLocalNS (NS parts) = mconcat $ intersperse "'" $ reverse parts

getterName, setterName, hasFnName, newFnName :: LocalQ -> UnQ
getterName = accessorName "get_"
setterName = accessorName "set_"
hasFnName = accessorName "has_"
newFnName = accessorName "new_"

accessorName :: T.Text -> LocalQ -> UnQ
accessorName prefix = UnQ . (prefix <>) . renderLocalQ

-- | Lower-case the first letter of a name, making it legal as the name of a
-- variable (as opposed to a type or data constructor).
valueName :: LocalQ -> UnQ
valueName name =
    case T.unpack (renderLocalQ name) of
        []     -> ""
        (c:cs) -> UnQ $ T.pack $ toLower c : cs
