{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeFamilies #-} 
module Agda.Syntax.Concrete.Name where
import Control.DeepSeq
import Data.ByteString.Char8 (ByteString)
import Data.Function
import qualified Data.Foldable as Fold
import qualified Data.List as List
import Data.Data (Data)
import GHC.Generics (Generic)
import System.FilePath
import Agda.Syntax.Common
import Agda.Syntax.Position
import Agda.Utils.FileName
import Agda.Utils.Lens
import Agda.Utils.Pretty
import Agda.Utils.Size
import Agda.Utils.Suffix
import Agda.Utils.Impossible
data Name
  = Name 
    { nameRange     :: Range
    , nameInScope   :: NameInScope
    , nameNameParts :: [NamePart]
    }
  | NoName 
    { nameRange     :: Range
    , nameId        :: NameId
    }
  deriving Data
isOpenMixfix :: Name -> Bool
isOpenMixfix n = case n of
  Name _ _ (x : xs@(_:_)) -> x == Hole || last xs == Hole
  _                       -> False
instance Underscore Name where
  underscore = NoName noRange __IMPOSSIBLE__
  isUnderscore NoName{} = True
  isUnderscore (Name {nameNameParts = [Id x]}) = isUnderscore x
  isUnderscore _ = False
data NamePart
  = Hole       
  | Id RawName  
  deriving (Data, Generic)
instance Eq Name where
    Name _ _ xs    == Name _ _ ys    = xs == ys
    NoName _ i     == NoName _ j     = i == j
    _              == _              = False
instance Ord Name where
    compare (Name _ _ xs)  (Name _ _ ys)      = compare xs ys
    compare (NoName _ i)   (NoName _ j)       = compare i j
    compare (NoName {})    (Name {})          = LT
    compare (Name {})      (NoName {})        = GT
instance Eq NamePart where
  Hole  == Hole  = True
  Id s1 == Id s2 = s1 == s2
  _     == _     = False
instance Ord NamePart where
  compare Hole    Hole    = EQ
  compare Hole    (Id {}) = LT
  compare (Id {}) Hole    = GT
  compare (Id s1) (Id s2) = compare s1 s2
data QName
  = Qual  Name QName 
  | QName Name       
  deriving (Data, Eq, Ord)
instance Underscore QName where
  underscore = QName underscore
  isUnderscore (QName x) = isUnderscore x
  isUnderscore Qual{}    = False
data TopLevelModuleName = TopLevelModuleName
  { moduleNameRange :: Range
  , moduleNameParts :: [String]
  }
  deriving (Show, Data)
instance Eq    TopLevelModuleName where (==)    = (==)    `on` moduleNameParts
instance Ord   TopLevelModuleName where compare = compare `on` moduleNameParts
instance Sized TopLevelModuleName where size    = size     .   moduleNameParts
nameToRawName :: Name -> RawName
nameToRawName = prettyShow
nameParts :: Name -> [NamePart]
nameParts (Name _ _ ps)    = ps
nameParts (NoName _ _)     = [Id "_"] 
nameStringParts :: Name -> [RawName]
nameStringParts n = [ s | Id s <- nameParts n ]
stringNameParts :: String -> [NamePart]
stringNameParts "_" = [Id "_"]   
stringNameParts s = loop s where
  loop ""                              = []
  loop ('_':s)                         = Hole : loop s
  loop s | (x, s') <- break (== '_') s = Id (stringToRawName x) : loop s'
class NumHoles a where
  numHoles :: a -> Int
instance NumHoles [NamePart] where
  numHoles = length . filter (== Hole)
instance NumHoles Name where
  numHoles NoName{}         = 0
  numHoles (Name { nameNameParts = parts }) = numHoles parts
instance NumHoles QName where
  numHoles (QName x)  = numHoles x
  numHoles (Qual _ x) = numHoles x
isOperator :: Name -> Bool
isOperator (NoName {})     = False
isOperator (Name _ _ ps)   = length ps > 1
isHole :: NamePart -> Bool
isHole Hole = True
isHole _    = False
isPrefix, isPostfix, isInfix, isNonfix :: Name -> Bool
isPrefix  x = not (isHole (head xs)) &&      isHole (last xs)  where xs = nameParts x
isPostfix x =      isHole (head xs)  && not (isHole (last xs)) where xs = nameParts x
isInfix   x =      isHole (head xs)  &&      isHole (last xs)  where xs = nameParts x
isNonfix  x = not (isHole (head xs)) && not (isHole (last xs)) where xs = nameParts x
data NameInScope = InScope | NotInScope
  deriving (Eq, Show, Data)
class LensInScope a where
  lensInScope :: Lens' NameInScope a
  isInScope :: a -> NameInScope
  isInScope x = x ^. lensInScope
  mapInScope :: (NameInScope -> NameInScope) -> a -> a
  mapInScope = over lensInScope
  setInScope :: a -> a
  setInScope = mapInScope $ const InScope
  setNotInScope :: a -> a
  setNotInScope = mapInScope $ const NotInScope
instance LensInScope NameInScope where
  lensInScope = id
instance LensInScope Name where
  lensInScope f = \case
    n@Name{ nameInScope = nis } -> (\nis' -> n { nameInScope = nis' }) <$> f nis
    n@NoName{} -> const n <$> f InScope
instance LensInScope QName where
  lensInScope f = \case
    Qual x xs -> (`Qual` xs) <$> lensInScope f x
    QName x   -> QName <$> lensInScope f x
nextStr :: String -> String
nextStr s = case suffixView s of
  (s0, suf) -> addSuffix s0 (nextSuffix suf)
nextName :: Name -> Name
nextName NoName{} = __IMPOSSIBLE__
nextName x@Name{ nameNameParts = ps } = x { nameInScope = NotInScope, nameNameParts = nextSuf ps }
  where
    nextSuf [Id s]       = [Id $ nextStr s]
    nextSuf [Id s, Hole] = [Id $ nextStr s, Hole]
    nextSuf (p : ps)     = p : nextSuf ps
    nextSuf []           = __IMPOSSIBLE__
firstNonTakenName :: (Name -> Bool) -> Name -> Name
firstNonTakenName taken x =
  if taken x
  then firstNonTakenName taken (nextName x)
  else x
nameRoot :: Name -> RawName
nameRoot NoName{} = __IMPOSSIBLE__
nameRoot x@Name{ nameNameParts = ps } =
    nameToRawName $ x{ nameNameParts = root ps }
  where
    root [Id s] = [Id $ strRoot s]
    root [Id s, Hole] = [Id $ strRoot s , Hole]
    root (p : ps) = p : root ps
    root [] = __IMPOSSIBLE__
    strRoot = fst . suffixView
sameRoot :: Name -> Name -> Bool
sameRoot = (==) `on` nameRoot
qualify :: QName -> Name -> QName
qualify (QName m) x     = Qual m (QName x)
qualify (Qual m m') x   = Qual m $ qualify m' x
unqualify :: QName -> Name
unqualify q = unqualify' q `withRangeOf` q
  where
  unqualify' (QName x)  = x
  unqualify' (Qual _ x) = unqualify' x
qnameParts :: QName -> [Name]
qnameParts (Qual x q) = x : qnameParts q
qnameParts (QName x)  = [x]
isQualified :: QName -> Bool
isQualified Qual{}  = True
isQualified QName{} = False
isUnqualified :: QName -> Maybe Name
isUnqualified Qual{}    = Nothing
isUnqualified (QName n) = Just n
toTopLevelModuleName :: QName -> TopLevelModuleName
toTopLevelModuleName q = TopLevelModuleName (getRange q) $ map prettyShow $ qnameParts q
moduleNameToFileName :: TopLevelModuleName -> String -> FilePath
moduleNameToFileName (TopLevelModuleName _ []) ext = __IMPOSSIBLE__
moduleNameToFileName (TopLevelModuleName _ ms) ext =
  joinPath (init ms) </> last ms <.> ext
projectRoot :: AbsolutePath -> TopLevelModuleName -> AbsolutePath
projectRoot file (TopLevelModuleName _ m) =
  mkAbsolute $
  foldr (.) id (replicate (length m - 1) takeDirectory) $
  takeDirectory $
  filePath file
noName_ :: Name
noName_ = noName noRange
noName :: Range -> Name
noName r = NoName r (NameId 0 0)
class IsNoName a where
  isNoName :: a -> Bool
  default isNoName :: (Foldable t, IsNoName b, t b ~ a) => a -> Bool
  isNoName = Fold.all isNoName
instance IsNoName String where
  isNoName = isUnderscore
instance IsNoName ByteString where
  isNoName = isUnderscore
instance IsNoName Name where
  isNoName (NoName _ _)      = True
  isNoName (Name _ _ [Hole]) = True   
  isNoName (Name _ _ [])     = True
  isNoName (Name _ _ [Id x]) = isNoName x
  isNoName _                 = False
instance IsNoName QName where
  isNoName (QName x) = isNoName x
  isNoName Qual{}    = False        
instance IsNoName a => IsNoName (Ranged a) where
instance IsNoName a => IsNoName (WithOrigin a) where
instance Show Name where
  show = prettyShow
instance Show NamePart where
  show = prettyShow
instance Show QName where
  show = prettyShow
instance Pretty Name where
  pretty (Name _ _ xs)    = hcat $ map pretty xs
  pretty (NoName _ _)     = "_"
instance Pretty NamePart where
  pretty Hole   = "_"
  pretty (Id s) = text $ rawNameToString s
instance Pretty QName where
  pretty (Qual m x)
    | isUnderscore m = pretty x 
    | otherwise      = pretty m <> "." <> pretty x
  pretty (QName x)  = pretty x
instance Pretty TopLevelModuleName where
  pretty (TopLevelModuleName _ ms) = text $ List.intercalate "." ms
instance HasRange Name where
    getRange (Name r _ ps)    = r
    getRange (NoName r _)     = r
instance HasRange QName where
    getRange (QName  x) = getRange x
    getRange (Qual n x) = fuseRange n x
instance HasRange TopLevelModuleName where
  getRange = moduleNameRange
instance SetRange Name where
  setRange r (Name _ nis ps) = Name r nis ps
  setRange r (NoName _ i)  = NoName r i
instance SetRange QName where
  setRange r (QName x)  = QName (setRange r x)
  setRange r (Qual n x) = Qual (setRange r n) (setRange r x)
instance SetRange TopLevelModuleName where
  setRange r (TopLevelModuleName _ x) = TopLevelModuleName r x
instance KillRange QName where
  killRange (QName x) = QName $ killRange x
  killRange (Qual n x) = killRange n `Qual` killRange x
instance KillRange Name where
  killRange (Name r nis ps)  = Name (killRange r) nis ps
  killRange (NoName r i)     = NoName (killRange r) i
instance KillRange TopLevelModuleName where
  killRange (TopLevelModuleName _ x) = TopLevelModuleName noRange x
instance NFData NameInScope where
  rnf InScope    = ()
  rnf NotInScope = ()
instance NFData Name where
  rnf (Name _ nis ns) = rnf nis `seq` rnf ns
  rnf (NoName _ n)  = rnf n
instance NFData NamePart where
  rnf Hole   = ()
  rnf (Id s) = rnf s
instance NFData QName where
  rnf (Qual a b) = rnf a `seq` rnf b
  rnf (QName a)  = rnf a