module Camfort.Analysis.Syntax where
import Data.Char
import Data.List
import Data.Monoid
import Control.Monad.State.Lazy
import Debug.Trace
import Data.Data
import Data.Generics.Uniplate.Data
import Data.Generics.Uniplate.Operations
import Data.Generics.Zipper
import Data.Typeable
import Camfort.Analysis.Annotations
import Camfort.Analysis.IntermediateReps
import Camfort.Traverse
import Language.Fortran
data AnnotationFree t = AnnotationFree { annotationBound :: t } deriving Show
af = AnnotationFree
unaf = annotationBound
eraseSrcLocs :: (Typeable (t a), Data (t a)) => t a -> t a
eraseSrcLocs = transformBi erase' 
                    where erase' :: SrcLoc -> SrcLoc
                          erase' _ = SrcLoc { srcFilename = "", srcLine = 0, srcColumn = 0 }
setCompactSrcLocs :: (Typeable (t a), Data (t a)) => t a -> t a
setCompactSrcLocs = transformBi cmpact' 
                    where cmpact' :: SrcLoc -> SrcLoc
                          cmpact' (SrcLoc _ l c) = SrcLoc { srcFilename = "compact", srcLine = l, srcColumn = c }
lower = map toLower
instance Eq (AnnotationFree a) => Eq (AnnotationFree [a]) where
    (AnnotationFree xs) == (AnnotationFree xs') =
               if (length xs == length xs')
               then foldl (\b -> \(x, x') -> ((af x) == (af x')) && b) True (zip xs xs')
               else False 
instance Eq (AnnotationFree Int) where
    x == y = (unaf x) == (unaf y)
instance Eq (AnnotationFree Char) where
    x == y = (unaf x) == (unaf y)
instance Eq (AnnotationFree (AccessP ())) where
    x == y = (unaf x) == (unaf y)
instance (Eq (AnnotationFree a), Eq (AnnotationFree b)) => Eq (AnnotationFree (a, b)) where
    (AnnotationFree (x, y)) == (AnnotationFree (x', y')) = ((af x) == (af x')) && ((af y) == (af y'))
instance Eq (AnnotationFree (Expr a)) where
    
    (AnnotationFree (Var _ _ vs)) == (AnnotationFree (Var _ _ vs'))
          = cmp vs vs' where cmp [] [] = True
                             cmp ((VarName _ v,es):vs) ((VarName _ v',es'):vs') =
                                  
                                  
                                  
                                  if (lower v) == (lower v') then 
                                         (and (map (\(e, e') -> (af e) == (af e'))
                                                 (zip es es'))) && (cmp vs vs')
                                  else False
                             cmp _ _ = False
    
    (AnnotationFree e1) == (AnnotationFree e2) = (eraseSrcLocs $ fmap (const ()) e1) == 
                                                 (eraseSrcLocs $ fmap (const ()) e2)
instance Eq (AnnotationFree (Type a)) where
    (AnnotationFree (BaseType _ b attrs e1 e2)) == (AnnotationFree (BaseType _ b' attrs' e1' e2')) = 
       (af b == af b') && (af attrs == af attrs') && (af e1 == af e1') && (af e2 == af e2')
    (AnnotationFree (ArrayT _ eps b attrs e1 e2)) == (AnnotationFree (ArrayT _ eps' b' attrs' e1' e2')) =
       (af eps == af eps') && (af b == af b') && (af attrs == af attrs') && (af e1 == af e1') && (af e2 == af e2')
instance Eq (AnnotationFree (Attr p)) where
    (AnnotationFree (Dimension _ es)) == (AnnotationFree (Dimension _ es')) = af es == af es'
    (AnnotationFree x) == (AnnotationFree y) = (fmap (const ()) x) == (fmap (const ()) y)
instance Eq (AnnotationFree (BaseType p)) where
    (AnnotationFree (DerivedType _ s)) == (AnnotationFree (DerivedType _ s')) = (af s) == (af s')
    (AnnotationFree x) == (AnnotationFree y) = (fmap (const ()) x) == (fmap (const ()) y)
    
instance Eq (AnnotationFree (SubName p)) where
    (AnnotationFree (SubName _ s)) == (AnnotationFree (SubName _ s')) = (lower s) == (lower s')
    (AnnotationFree (NullSubName _)) == (AnnotationFree (NullSubName _)) = True
    _ == _ = False
instance Eq (AnnotationFree (IntentAttr p)) where
    (AnnotationFree x) == (AnnotationFree y) = (fmap (const ()) x) == (fmap (const ()) y)
instance Eq (AnnotationFree (MeasureUnitSpec p)) where
    (AnnotationFree (UnitProduct _ u)) == (AnnotationFree (UnitProduct _ u')) = (af u) == (af u')
    (AnnotationFree (UnitQuotient _ u1 u2)) == (AnnotationFree (UnitQuotient _ u1' u2')) =
       (af u1 == af u1') && (af u2 == af u2')
    (AnnotationFree (UnitNone _)) == (AnnotationFree (UnitNone _)) = True
    _ == _ = False
instance Eq (AnnotationFree (Fraction p)) where
    (AnnotationFree (IntegerConst _ n)) == (AnnotationFree (IntegerConst _ n')) = (af n) == (af n')
    (AnnotationFree (FractionConst _ p q)) == (AnnotationFree (FractionConst _ p' q')) =
       (af p == af p') && (af q == af q')
    (AnnotationFree (NullFraction _)) == (AnnotationFree (NullFraction _)) = True
    _ == _ = False
instance Ord (AccessP ()) where
    (VarA s1) <= (VarA s2)           = s1 <= s2
    (ArrayA s1 e1) <= (ArrayA s2 e2) = if (s1 == s2) then e1 <= e2 else s1 <= s2 
    (VarA s1) <= (ArrayA s2 e1)      = True
    _ <= _                           = False
instance Eq p => Ord (Expr p) where
    (Con _ _ c) <= (Con  _ _ c') = c <= c'
    e <= e'                      = error "Ordering on expressions only for constructors so far"
getSubName :: ProgUnit p -> Maybe String
getSubName (Main _ _ (SubName _ s) _ _ _)       = Just s
getSubName (Sub _ _ _ (SubName _ s) _ _)        = Just s
getSubName (Function _ _ _ (SubName _ s) _ _ _) = Just s
getSubName (Module _ _ (SubName _ s) _ _ _ _)   = Just s
getSubName (BlockData _ _ (SubName _ s) _ _ _)  = Just s
getSubName _                                    = Nothing
accesses f = nub $  [VarA (lower v) | (AssgExpr _ _ v _) <- (universeBi f)::[Expr Annotation]]
                     ++ concat [varExprToAccesses ve | ve@(Var _ _ _) <- (universeBi f)::[Expr Annotation]]
               
varExprToVariable :: Expr a -> Maybe Variable
varExprToVariable (Var _ _ ((VarName _ v, es):_)) = Just v
varExprToVariable _                               = Nothing
varExprToAccess :: Expr a -> Maybe Access
varExprToAccess v = varExprToVariable v >>= (Just . VarA)
varExprToAccesses :: Expr a -> [Access]
varExprToAccesses (Var _ _ ves) = [mkAccess v es | (VarName _ v, es) <- ves, all isConstant es] 
                                     where mkAccess v [] = VarA v
                                           mkAccess v es = ArrayA v (map (fmap (const ())) es)
varExprToAccesses _             = [] 
class Successors t where
    
    successorsRoot :: t a -> [t a]
    
    successors :: (Eq a, Typeable a) => Zipper (ProgUnit a) -> [t a]  
instance Successors Fortran where
    successorsRoot (FSeq _ _ f1 f2)          = [f1]
    successorsRoot (For _ _ _ _ _ _ f)       = [f]
    successorsRoot (If _ _ _ f efs f')       = [f]
    successorsRoot (Forall _ _ _ f)          = [f]
    successorsRoot (Where _ _ _ f Nothing)   = [f]
    successorsRoot (Where _ _ _ f (Just f')) = [f, f']
    successorsRoot (Label _ _ _ f)           = [f]
    successorsRoot _                         = []
    successors = 
        successorsF
         where
          successorsF :: forall a . (Eq a, Typeable a) => Zipper (ProgUnit a) -> [Fortran a]
          successorsF z = maybe [] id 
                           (do f <- (getHole z)::(Maybe (Fortran a))
                               ss <- return $ successorsRoot f
                               return $ ss ++ seekUp f (Just z))
          seekUp :: forall a . (Eq a, Typeable a) => Fortran a -> Maybe (Zipper (ProgUnit a)) -> [Fortran a]
          seekUp f z = case (z >>= up >>= getHole)::(Maybe (Fortran a)) of
                         Just uf -> 
                             case uf of
                               (FSeq _ _ f1 f2)     -> if (f == f1) then [f2]
                                                       else seekUp uf (z >>= up)
                               (For _ _ _ _ _ _ f') -> seekUp uf (z >>= up)
                               (If _ _ _ gf efs f') -> if (f == gf) then (maybe [] (:[]) f') ++ (map snd efs) 
                                                       else seekUp uf (z >>= up) 
                               (Forall _ _ _ f')    -> seekUp uf (z >>= up)
                               (Where _ _ _ f' _)   -> seekUp uf (z >>= up)
                               (Label _ _ _ f')     -> seekUp uf (z >>= up)
                               _                    -> []
                         Nothing -> [] 
 
rhsExpr :: Fortran Annotation -> [Expr Annotation]
rhsExpr (Assg _ _ _ e2)        = (universeBi e2)::[Expr Annotation]
rhsExpr (For _ _ v e1 e2 e3 _) = ((universeBi e1)::[Expr Annotation]) ++
                                  ((universeBi e2)::[Expr Annotation]) ++
                                  ((universeBi e3)::[Expr Annotation])
rhsExpr (If _ _ e f1 fes f3)    = ((universeBi e)::[Expr Annotation])
                            
rhsExpr (Allocate x sp e1 e2)   = ((universeBi e1)::[Expr Annotation]) ++
                                   ((universeBi e2)::[Expr Annotation])
rhsExpr (Call _ _ e as)         = ((universeBi e)::[Expr Annotation]) ++ 
                                   ((universeBi as)::[Expr Annotation])
rhsExpr (Deallocate _ _ es e)   = (concatMap (\e -> (universeBi e)::[Expr Annotation]) es) ++
                                    ((universeBi e)::[Expr Annotation])
rhsExpr (Forall _ _ (es, e) f)  = concatMap (\(_, e1, e2, e3) -> 
                                               ((universeBi e1)::[Expr Annotation]) ++
                                               ((universeBi e2)::[Expr Annotation]) ++
                                               ((universeBi e3)::[Expr Annotation])) es ++
                                    ((universeBi e)::[Expr Annotation])
rhsExpr (Nullify _ _ es)        = concatMap (\e -> (universeBi e)::[Expr Annotation]) es
rhsExpr (Inquire _ _ s es)      = concatMap (\e -> (universeBi e)::[Expr Annotation]) es
rhsExpr (Stop _ _ e)            = (universeBi e)::[Expr Annotation]
rhsExpr (Where _ _ e f _)       = (universeBi e)::[Expr Annotation]
rhsExpr (Write _ _ s es)        = concatMap (\e -> (universeBi e)::[Expr Annotation]) es
rhsExpr (PointerAssg _ _ _ e2)  = (universeBi e2)::[Expr Annotation]
rhsExpr (Return _ _ e)          = (universeBi e)::[Expr Annotation]
rhsExpr (Print _ _ e es)        = ((universeBi e)::[Expr Annotation]) ++ 
                                   (concatMap (\e -> (universeBi e)::[Expr Annotation]) es)
rhsExpr (ReadS _ _ s es)        = concatMap (\e -> (universeBi e)::[Expr Annotation]) es
rhsExpr _                     = []
lhsExpr :: Fortran Annotation -> [Expr Annotation]
lhsExpr (Assg _ _ e1 e2)        = ((universeBi e1)::[Expr Annotation])
lhsExpr (For x sp v e1 e2 e3 fs) = [Var x sp [(v, [])]]
lhsExpr (PointerAssg _ _ e1 e2) = ((universeBi e1)::[Expr Annotation])
lhsExpr t                        = concatMap lhsExpr ((children t)::[Fortran Annotation])
instance Monoid Int where
    mempty = 0
    mappend = (+)
countVariableDeclarations :: Program Annotation -> Int
countVariableDeclarations x = sum [length xs | (Decl _ _ xs _) <- (universeBi x)::[Decl Annotation]]
                               
numberStmts :: ProgUnit Annotation -> ProgUnit Annotation
numberStmts x = let 
                  numberF :: Fortran Annotation -> State Int (Fortran Annotation)
                  numberF = descendBiM number'
                  numberD :: Decl Annotation -> State Int (Decl Annotation)
                  numberD = descendBiM number'
                  number' :: Annotation -> State Int Annotation
                  
                  number' x = do n <- get 
                                 put (n + 1)
                                 return $ x { number = n }
                  (x', n)  = runState (descendBiM numberD x) 0
                  (x'', _) = runState (descendBiM numberF x') n
                in x''
variables f = nub $ map (map toLower) $ [v | (AssgExpr _ _ v _) <- (universeBi f)::[Expr Annotation]]
                 ++ [v | (VarName _ v) <- (universeBi f)::[VarName Annotation]] 
isConstant :: Expr p -> Bool
isConstant (Con _ _ _)  = True
isConstant (ConL _ _ _ _) = True
isConstant (ConS _ _ _) = True
isConstant _            = False
               
freeVariables :: (Data (t a), Data a) => t a -> [String]
freeVariables f = (variables f) \\ (binders f)
binders :: forall a t . (Data (t a), Typeable (t a), Data a, Typeable a) => t a -> [String]
binders f = nub $
               [v | (ArgName _ v) <- (universeBi f)::[ArgName a]] 
            ++ [v | (VarName _ v) <- (universeBi ((universeBi f)::[Decl a]))::[VarName a]]
            ++ [v | (For _ _ (VarName _ v) _ _ _ _) <- (universeBi f)::[Fortran a]]
affineMatch (Bin _ _ (Plus _) (Var _ _ [(VarName _ v, _)]) (Con _ _ n)) = Just (v, read n)
affineMatch (Bin _ _ (Plus _) (Con _ _ n) (Var _ _ [(VarName _ v, _)]))   = Just (v, read n)
affineMatch (Bin _ _ (Minus _) (Var _ _ [(VarName _ v, _)]) (Con _ _ n))    = Just (v,  read n)
affineMatch (Bin _ _ (Minus _) (Con _ _ n) (Var _  _ [(VarName _ v, _)])) = Just (v,  read n)
affineMatch (Var _ _  [(VarName _ v, _)])                               = Just (v, 0)
affineMatch _                                                           = Nothing
data QueryCmd t where
    Exprs  :: QueryCmd (Expr Annotation)
    Blocks :: QueryCmd (Block Annotation)
    Decls  :: QueryCmd (Decl Annotation)
    Locs   :: QueryCmd Access
    Vars   :: QueryCmd (Expr Annotation)
from :: forall t synTyp . (Data t, Data synTyp) => QueryCmd synTyp -> t -> [synTyp]
from Locs x = accesses x
from Vars x = [v | v@(Var _ _ _) <- (universeBi x)::[Expr Annotation]]
from _ x = (universeBi x)::[synTyp]
topFrom :: forall t synTyp . (Data t, Data synTyp) => QueryCmd synTyp -> t -> [synTyp]
topFrom Locs x = accesses x
topFrom _ x = (childrenBi x)::[synTyp]