{-# LANGUAGE DeriveDataTypeable #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Language.ObjC.Syntax.Attributes
-- Copyright   :  (c) [1995..1999] Manuel M. T. Chakravarty
--                (c) 2008 Benedikt Huber (stripped radically)
--                (c) 2012 John W. Lato
-- License     :  BSD-style
-- Maintainer  :  jwlato@gmail.com
-- Stability   :  experimental
-- Portability :  ghc
--
-- source position and unqiue name
-----------------------------------------------------------------------------
module Language.ObjC.Data.Node (
   NodeInfo(..), undefNode, isUndefNode,
   mkNodeInfoOnlyPos,mkNodeInfoPosLen, mkNodeInfo,mkNodeInfo',
   internalNode, -- deprecated, use undefNode
   CNode(nodeInfo), fileOfNode,
   posOfNode, nameOfNode, getLastTokenPos, lengthOfNode,
   eqByName,
) where
import Language.ObjC.Data.Position
import Language.ObjC.Data.Name     (Name)
import Data.Generics

-- | Parsed entity attribute
data NodeInfo = OnlyPos  !Position {-# UNPACK #-} !PosLength        -- only pos and last token (for internal stuff only)
              | NodeInfo !Position {-# UNPACK #-} !PosLength {-# UNPACK #-} !Name  -- pos, last token and unique name
           deriving (Data,Typeable)

instance Show NodeInfo where
    showsPrec d (OnlyPos p l) =
      (showString "(OnlyPos ") . (showsPrec d p) . (showString " ") . (showsPrec d l) . (showString ")")
    showsPrec d (NodeInfo p l n) =
      (showString "(NodeInfo ") . (showsPrec d p) . (showString " ") . (showsPrec d l) . (showString " ") . (showsPrec d n) . (showString ")")

-- name equality of attributes, used to define (name) equality of objects
instance Eq NodeInfo where
  (NodeInfo   _ _ id1) == (NodeInfo   _ _ id2) = id1 == id2
  _               == _               =
    error "Attributes: Attempt to compare `OnlyPos' attributes!"

-- attribute ordering
instance Ord NodeInfo where
  (NodeInfo   _ _ id1) <= (NodeInfo   _ _ id2) = id1 <= id2
  _               <= _               =
    error "Attributes: Attempt to compare `OnlyPos' attributes!"

instance Pos NodeInfo where
  posOf (OnlyPos pos _) = pos
  posOf (NodeInfo pos _ _) = pos

-- | get the number of characters an AST node spans
lengthOfNode :: NodeInfo -> Maybe Int
lengthOfNode ni = len
    where
    len = case ni of NodeInfo firstPos lastTok _ -> computeLength firstPos lastTok
                     OnlyPos firstPos lastTok -> computeLength firstPos lastTok
    computeLength pos (PL lastPos len')
      | len' < 0   = Nothing
      | otherwise = Just (posOffset lastPos + len' - posOffset pos)

-- | get the position and length of the last token
getLastTokenPos :: NodeInfo -> PosLength
getLastTokenPos (NodeInfo _ lastTok _) = lastTok
getLastTokenPos (OnlyPos _ lastTok) = lastTok

-- | a class for convenient access to the attributes of an attributed object
class CNode a where
  nodeInfo :: a -> NodeInfo
instance CNode NodeInfo where
  nodeInfo = id
instance (CNode a, CNode b) => CNode (Either a b) where
  nodeInfo = either nodeInfo nodeInfo

nameOfNode :: NodeInfo -> Maybe Name
nameOfNode (OnlyPos _ _) = Nothing
nameOfNode (NodeInfo _ _ name) = Just name
posOfNode :: NodeInfo -> Position
posOfNode ni = case ni of (OnlyPos pos _) -> pos; (NodeInfo pos _ _) -> pos
fileOfNode :: (CNode a) => a -> Maybe FilePath
fileOfNode = fmap posFile . justIf isSourcePos . posOfNode . nodeInfo where
    justIf predicate x | predicate x = Just x
                       | otherwise   = Nothing

-- | equality by name
eqByName           :: CNode a => a -> a -> Bool
eqByName obj1 obj2  = (nodeInfo obj1) == (nodeInfo obj2)


-- attribute identifier creation
-- -----------------------------
{-# DEPRECATED internalNode "use undefNode instead" #-}
internalNode :: NodeInfo
internalNode = undefNode

-- | create a node with neither name nor positional information
undefNode :: NodeInfo
undefNode = OnlyPos nopos (PL nopos nO)

-- | return True if the node carries neither name nor positional information
isUndefNode :: NodeInfo -> Bool
isUndefNode (OnlyPos p _) | isNoPos p = True
                          | otherwise = False
isUndefNode _ = False

nO :: Int
nO = (-1)

-- |
-- | Given only a source position, create a new node attribute
mkNodeInfoOnlyPos :: Position -> NodeInfo
mkNodeInfoOnlyPos pos  = OnlyPos pos (PL nopos nO)

-- | Given a source position and the position and length of the last token, create a new node attribute
mkNodeInfoPosLen :: Position -> (Position,Int) -> NodeInfo
mkNodeInfoPosLen p pl = OnlyPos p (uncurry PL pl)

-- | Given a source position and a unique name, create a new attribute
-- identifier
mkNodeInfo :: Position -> Name -> NodeInfo
mkNodeInfo pos name  = NodeInfo pos (PL nopos nO) name

-- | Given a source position, the position and length of the last token and a unique name, create a new attribute
-- identifier. Strict in
mkNodeInfo' :: Position -> (Position,Int) -> Name -> NodeInfo
mkNodeInfo' pos lasttok name = NodeInfo pos (uncurry PL lasttok) name