{-# LANGUAGE DeriveFunctor
  , DeriveFoldable
  , DeriveTraversable
  , TypeSynonymInstances
  , FlexibleInstances
  , FlexibleContexts
  , UndecidableInstances #-}

module Abstract where

import Prelude hiding (foldr)

import Control.Monad.Reader

import Data.Set (Set)
import qualified Data.Set as Set

import qualified Data.IntMap as M

import Data.Foldable
import Data.Traversable

import qualified Concrete as C

import Util

-- * Abstract names and identifiers

type UID = Int

-- | Names in the abstract syntax are unique.
data Name = Name
  { uid        :: UID     -- ^ Positive numbers come from user,
                          --   negative from system (e.g. quoting).
  , suggestion :: C.Name  -- ^ Name suggestion for printing.
  }

instance Eq Name where
  x == y = uid x == uid y

instance Ord Name where
  compare x y = compare (uid x) (uid y)

instance Show Name where
  show x = suggestion x ++ ":" ++ show (uid x)

-- | Local and global names.
data Ident
  = Var { name :: Name }          -- ^ Locally bound identifier.
  | Con { name :: Name }          -- ^ Declared constant.
  | Def { name :: Name }          -- ^ Defined identifier.
  | Let { name :: Name }          -- ^ Shared expression identifier.
  deriving (Eq,Ord)

instance Show Ident where
  show (Var n) = "Var " ++ show n
  show (Con n) = "Con " ++ show n
  show (Def n) = "Def " ++ show n
  show (Let n) = "Let " ++ show n

isGlobalIdent :: Ident -> Bool
isGlobalIdent (Var{}) = False
isGlobalIdent (Let{}) = False
isGlobalIdent _       = True

-- * Generating local names

systemGeneratedName :: Name
systemGeneratedName = Name { uid = -1,  suggestion = C.noName }

noName = systemGeneratedName

type SysNameCounter = Int

initSysNameCounter :: SysNameCounter
initSysNameCounter = -1

-- | Get the next local name, as variant of an existing name.
nextSysName :: SysNameCounter -> Name -> (Name, SysNameCounter)
nextSysName i n = (n { uid = i }, i - 1)

-- | Get the next local name with no concrete representation.
nextSysName' :: SysNameCounter -> (Name, SysNameCounter)
nextSysName' i = nextSysName i noName

-- * Abstract syntax for declarations and expressions

newtype Declarations = Declarations { declarations :: [Declaration] }

data Declaration
  = TypeSig Name Type            -- ^ @c : A.@
  | Defn Name (Maybe Type) Expr  -- ^ @d : A = e.@ or @d = e.@
--  | GLet Name Expr               -- ^ @[x = e].@  global shared expr. NYI.
    deriving (Show)

type Type = Expr
type Expr = Expression Ident

type TypeExpr id = Expression id
data Expression id
  = Ident id                                        -- ^ @x@ or @c@ or @d@
  | Typ                                             -- ^ @type@
  | Pi   (Maybe Name) (TypeExpr id) (TypeExpr id)   -- ^ @A -> B@ or @{x:A} B@
  | Lam  Name (Maybe (TypeExpr id)) (Expression id) -- ^ @[x:A] E@ or @[x]E@
  | App  (Expression id) (Expression id)            -- ^ @E1 E2@
  | LLet Name (Expression id) (Expression id)       -- ^ @[x = E1] E2@
  deriving (Eq,Ord,Show,Functor,Foldable,Traversable)

-- * Spine view

instance IsApp Expr where
  isApp (App f e) = Just (f, e)
  isApp _         = Nothing

-- * alpha equality

newtype Alpha a = Alpha a
  deriving (Functor)

instance Show a => Show (Alpha a) where
  show (Alpha a) = show a

instance Ord (Alpha Expr) where
  compare (Alpha e) (Alpha e') = aCompare e e'

instance Ord (Alpha a) => Eq (Alpha a) where
  a == a' = compare a a' == EQ

type IMap = M.IntMap UID -- map directed from left to right
type Cmp = Reader IMap

class OrdAlpha a where
  aCompare :: a -> a -> Ordering
  aCompare e1 e2 = runReader (acmp e1 e2) M.empty

  acmp :: a -> a -> Cmp Ordering
  acmp e1 e2 = return $ aCompare e1 e2

instance OrdAlpha Name where
  acmp (Name x _) (Name y _)
    | x == y = return EQ
    | otherwise = do
        m <- ask
        case M.lookup x m of
          Nothing -> return $ compare x y
          Just y' -> return $ compare y' y

-- | Just look at UID
instance OrdAlpha Ident where
  acmp x y = acmp (name x) (name y)

instance OrdAlpha Expr where
  acmp e e' = case (e, e') of

    (Ident x, Ident x') -> acmp x x'
    (Ident _, _) -> return LT
    (_, Ident _) -> return GT

    (App f e, App f' e') -> lexM [ acmp f f', acmp e e' ]
    (App _ _, _) -> return LT
    (_, App _ _) -> return GT

    (Pi Nothing a b, Pi Nothing a' b') -> acmp (a,b) (a',b')
    (Pi Nothing _ _, _) -> return LT
    (_, Pi Nothing _ _) -> return GT

    (Pi (Just x) a b, Pi (Just x') a' b') -> lexM
      [ acmp a a'
      , local (M.insert (uid x) (uid x')) $ acmp b b' ]
    (Pi (Just _) _ _, _) -> return LT
    (_, Pi (Just _) _ _) -> return GT

    (Lam x a e, Lam x' a' e') -> lexM
      [ acmp a a'
      , local (M.insert (uid x) (uid x')) $ acmp e e' ]
    (Lam _ _ _, _) -> return LT
    (_, Lam _ _ _) -> return GT

    (LLet x a e, LLet x' a' e') -> lexM
      [ acmp a a'
      , local (M.insert (uid x) (uid x')) $ acmp e e' ]
    (LLet _ _ _, _) -> return LT
    (_, LLet _ _ _) -> return GT

    (Typ, Typ)   -> return EQ

    -- Redundant:
    -- (Typ, _)     -> return LT
    -- (_, Typ)     -> return GT

-- | Lexicographic comparison.
instance (OrdAlpha a, OrdAlpha b) => OrdAlpha (a,b) where
  acmp (a1,b1) (a2,b2) = lexM [acmp a1 a2, acmp b1 b2]

-- | Use only for lists of equal length!
instance (OrdAlpha a) => OrdAlpha [a] where
  acmp as bs = lexM $ zipWith acmp as bs

instance (OrdAlpha a) => OrdAlpha (Maybe a) where
  acmp Nothing Nothing   = return EQ
  acmp Nothing (Just _)  = return LT
  acmp (Just _) Nothing  = return GT
  acmp (Just a) (Just b) = acmp a b

-- | Lazy lexicographic combination.
lexM :: Monad m => [m Ordering] -> m Ordering
lexM []     = return EQ
lexM (m:ms) = do
  o <- m
  case o of
     LT -> return LT
     GT -> return GT
     EQ -> lexM ms

-- * Queries

globalIds :: Expr -> Set Ident
globalIds = foldr (\ n ns -> if isGlobalIdent n then Set.insert n ns else ns)
                  Set.empty

globalCNames :: Expr -> Set C.Name
globalCNames =
  foldr (\ n ns -> if isGlobalIdent n then Set.insert (suggestion $ name n) ns else ns)
        Set.empty
