{-# LANGUAGE CPP, MagicHash, DeriveDataTypeable, NoMonomorphismRestriction, RankNTypes, RecordWildCards #-}

{- |
   Module      : GHC.Vis.Internal
   Copyright   : (c) Dennis Felsing
   License     : 3-Clause BSD-style
   Maintainer  : dennis@felsin9.de

 -}
module GHC.Vis.Internal (
  parseClosure,
  showClosureFields
  )
  where

#if __GLASGOW_HASKELL__ < 706
import Prelude hiding (catch)
#endif

import GHC.Vis.Types

import GHC.HeapView hiding (pkg, modl, fun, arrWords)

import Control.Monad
import Control.Monad.State hiding (State, fix)

import Data.Word
import Data.Char
import Data.List hiding (insert)
import qualified Data.IntMap as M

import Text.Printf
import Unsafe.Coerce

import System.IO.Unsafe

-- TODO: Remove
instance Eq Box where
  a == b = unsafePerformIO $ areBoxesEqual a b

-- | Parse a closure to a list of VisObjects
parseClosure :: HeapGraphIndex -> PrintState [VisObject]
parseClosure i = do
  o <- correctObject i
  case o of
    Link n -> return [Link n] -- Don't build infinite heaps
    _      -> do HeapGraph m <- gets heapGraph
                 l <- parseInternal i $ hgeClosure $ m M.! i
                 return $ insertObjects o l

correctObject :: HeapGraphIndex -> PrintState VisObject
correctObject i = do
  name <- getName i
  bindings <- gets bindings

  if null name
  then if i `elem` bindings then
                 (do name' <- setName i
                     return $ Named name' [])
                 else return $ Unnamed ""
  else return $ Link name

setName :: HeapGraphIndex -> PrintState String
setName i = do
  n <- getName i
  if null n
  then do
    s@(PState ti fi xi _ (HeapGraph m)) <- get
    let Just hge@(HeapGraphEntry{hgeClosure = c}) = M.lookup i m
    let (name,newState) = case bindingLetter c of
          't' -> ('t' : show ti, s{tCounter' = ti + 1})
          'f' -> ('f' : show fi, s{fCounter' = fi + 1})
          'x' -> ('x' : show xi, s{xCounter' = xi + 1})
          _   -> error "Invalid letter"

    let m' = M.insert i (hge{hgeData = name}) m
    put $ newState{heapGraph = HeapGraph m'}
    return name
  else return n

  where
    bindingLetter c = case c of
        ThunkClosure {..} -> 't'
        SelectorClosure {..} -> 't'
        APClosure {..} -> 't'
        PAPClosure {..} -> 'f'
        BCOClosure {..} -> 't'
        FunClosure {..} -> 'f'
        _ -> 'x'

getName :: HeapGraphIndex -> PrintState String
getName i = do
  HeapGraph m <- gets heapGraph
  return $ hgeData $ m M.! i

insertObjects :: VisObject -> [VisObject] -> [VisObject]
insertObjects _ xs@(Function _ : _) = xs
insertObjects _ xs@(Thunk _ : _) = xs
insertObjects (Link name) _ = [Link name]
insertObjects (Named name _) xs = [Named name xs]
insertObjects (Unnamed _) xs = xs
insertObjects _ _ = error "unexpected arguments"

parseInternal :: HeapGraphIndex -> GenClosure (Maybe HeapGraphIndex) -> PrintState [VisObject]
parseInternal _ (ConsClosure _ [] [dataArg] _pkg modl name) =
 return [Unnamed $ case (modl, name) of
    k | k `elem` [ ("GHC.Word", "W#")
                 , ("GHC.Word", "W8#")
                 , ("GHC.Word", "W16#")
                 , ("GHC.Word", "W32#")
                 , ("GHC.Word", "W64#")
                 ] -> name ++ " " ++ show dataArg

    k | k `elem` [ ("GHC.Integer.Type", "S#")
                 , ("GHC.Types", "I#")
                 , ("GHC.Int", "I8#")
                 , ("GHC.Int", "I16#")
                 , ("GHC.Int", "I32#")
                 , ("GHC.Int", "I64#")
                 ] -> name ++ " " ++ show ((fromIntegral :: Word -> Int) dataArg)

    ("GHC.Types", "C#") -> show . chr $ fromIntegral dataArg
    --("GHC.Types", "C#") -> '\'' : (chr $ fromIntegral dataArg) : "'"

    ("GHC.Types", "D#") -> printf "D# %0.5f" (unsafeCoerce dataArg :: Double)
    ("GHC.Types", "F#") -> printf "F# %0.5f" (unsafeCoerce dataArg :: Double)

    _ -> printf "%s %d" (infixFix name) dataArg
  ]

parseInternal _ (ConsClosure (StgInfoTable 1 3 _ _) _ [_,0,0] _ "Data.ByteString.Internal" "PS")
  = return [Unnamed "ByteString 0 0"]

parseInternal _ (ConsClosure (StgInfoTable 1 3 _ _) [bPtr] [_,start,end] _ "Data.ByteString.Internal" "PS")
  = do cPtr  <- liftM mbParens $ contParse bPtr
       return $ Unnamed (printf "ByteString %d %d " start end) : cPtr

parseInternal _ (ConsClosure (StgInfoTable 2 3 _ _) [bPtr1,bPtr2] [_,start,end] _ "Data.ByteString.Lazy.Internal" "Chunk")
  = do cPtr1 <- liftM mbParens $ contParse bPtr1
       cPtr2 <- liftM mbParens $ contParse bPtr2
       return $ Unnamed (printf "Chunk %d %d " start end) : cPtr1 ++ [Unnamed " "] ++ cPtr2

parseInternal _ (ConsClosure (StgInfoTable 2 0 _ _) [bHead,bTail] [] _ "GHC.Types" ":")
  = do cHead <- liftM mbParens $ contParse bHead
       cTail <- liftM mbParens $ contParse bTail
       return $ cHead ++ [Unnamed ":"] ++ cTail

parseInternal _ (ConsClosure _ bPtrs dArgs _ _ name)
  = do cPtrs <- mapM (liftM mbParens . contParse) bPtrs
       let tPtrs = intercalate [Unnamed " "] cPtrs
       let sPtrs = if null tPtrs then [Unnamed ""] else Unnamed " " : tPtrs
       return $ Unnamed (unwords $ infixFix name : map show dArgs) : sPtrs

parseInternal _ (ArrWordsClosure _ _ arrWords)
  = return $ intercalate [Unnamed ","] (map (\x -> [Unnamed (printf "0x%x" x)]) arrWords)

parseInternal _ (IndClosure _ b)
  = contParse b

parseInternal _ (SelectorClosure _ b)
  = contParse b

parseInternal _ (BlackholeClosure _ b)
  = contParse b

parseInternal _ BlockingQueueClosure{}
  = return [Unnamed "BlockingQueue"]

parseInternal _ (OtherClosure (StgInfoTable _ _ cTipe _) _ _)
  = return [Unnamed $ show cTipe]

parseInternal _ (UnsupportedClosure (StgInfoTable _ _ cTipe _))
  = return [Unnamed $ show cTipe]

-- Reversed order of ptrs
parseInternal b (ThunkClosure _ bPtrs args) = do
  name <- setName b
  cPtrs <- mapM contParse $ reverse bPtrs
  let tPtrs = intercalate [Unnamed ","] cPtrs
      sPtrs = if null tPtrs then [Unnamed ""] else Unnamed "(" : tPtrs ++ [Unnamed ")"]
      sArgs = Unnamed $ if null args then "" else show args
  return $ Thunk (infixFix name) : sPtrs ++ [sArgs]

parseInternal i (FunClosure _ bPtrs args) = do
  name <- setName i
  cPtrs <- mapM contParse $ reverse bPtrs
  let tPtrs = intercalate [Unnamed ","] cPtrs
      sPtrs = if null tPtrs then [Unnamed ""] else Unnamed "(" : tPtrs ++ [Unnamed ")"]
      sArgs = Unnamed $ if null args then "" else show args
  return $ Function (infixFix name) : sPtrs ++ [sArgs]

-- bPtrs here can currently point to Nothing, because else we might get infinite heaps
parseInternal _ (MutArrClosure _ _ _ bPtrs)
  -- = do cPtrs <- mutArrContParse2 bPtrs
  = do cPtrs <- mapM contParse bPtrs
       let tPtrs = intercalate [Unnamed ","] cPtrs
       return $ if null tPtrs then [Unnamed ""] else Unnamed "(" : tPtrs ++ [Unnamed ")"]

parseInternal _ (MutVarClosure _ b)
  = do c <- contParse b
       return $ Unnamed "MutVar " : c

parseInternal _ (BCOClosure _ _ _ bPtr _ _ _)
  -- = do cPtrs <- bcoContParse2 [bPtr]
  = do cPtrs <- mapM contParse [bPtr]
       let tPtrs = intercalate [Unnamed ","] cPtrs
       return $ Unnamed "BCO" : tPtrs

parseInternal i (APClosure _ _ _ fun pl) = do
  name <- setName i
  fPtr <- contParse fun
  pPtrs <- mapM contParse $ reverse pl
  let tPtrs = intercalate [Unnamed ","] pPtrs
      sPtrs = if null tPtrs then [Unnamed ""] else Unnamed "[" : tPtrs ++ [Unnamed "]"]
  return $ Thunk (infixFix name) : fPtr ++ sPtrs

parseInternal i (PAPClosure _ _ _ fun pl) = do
  name <- setName i
  fPtr <- contParse fun
  pPtrs <- mapM contParse $ reverse pl
  let tPtrs = intercalate [Unnamed ","] pPtrs
      sPtrs = if null tPtrs then [Unnamed ""] else Unnamed "[" : tPtrs ++ [Unnamed "]"]
  return $ Function (infixFix name) : fPtr ++ sPtrs

parseInternal i (APStackClosure _ fun pl) = do
  name <- setName i
  fPtr <- contParse fun
  pPtrs <- mapM contParse $ reverse pl
  let tPtrs = intercalate [Unnamed ","] pPtrs
      sPtrs = if null tPtrs then [Unnamed ""] else Unnamed "[" : tPtrs ++ [Unnamed "]"]
  return $ Thunk (infixFix name) : fPtr ++ sPtrs

parseInternal _ (MVarClosure _ qHead qTail qValue)
   = do cHead <- liftM mbParens $ contParse qHead
        cTail <- liftM mbParens $ contParse qTail
        cValue <- liftM mbParens $ contParse qValue
        return $ Unnamed "MVar#(" : cHead ++ [Unnamed ","] ++ cTail ++ [Unnamed ","] ++ cValue ++ [Unnamed ")"]

contParse :: Maybe HeapGraphIndex -> PrintState [VisObject]
contParse Nothing = return []
contParse (Just i) = parseClosure i

-- It turned out that bcoContParse actually does go into an infinite loop, for
-- example for this:
-- foldr' op i [] = i
-- foldr' op i (x:xs) = op x (foldr' op i xs)
-- :view foldr'
-- We fix this by giving visited closures a dummy name, so we recognize when we
-- get into a loop.
--bcoContParse :: [Box] -> MaybeT PrintState [[VisObject]]
--bcoContParse [] = return []
--bcoContParse (b:bs) = gets heapMap >>= \h -> case lookup b h of
--  Nothing    -> do let ptf = unsafePerformIO $ getBoxedClosureData b >>= pointersToFollow2
--                   r <- lift $ countReferences b
--                   n <- getName b
--                   case n of
--                     Just _ -> bcoContParse bs
--                     Nothing -> do
--                       when (r > 1) $ setVisited b
--                       bcoContParse $ ptf ++ bs
--  Just (_,c) -> do p  <- parseClosure b c
--                   ps <- bcoContParse bs
--                   return $ p : ps

--mutArrContParse :: [Box] -> MaybeT PrintState [[VisObject]]
--mutArrContParse [] = return []
--mutArrContParse (b:bs) = gets heapMap >>= \h -> case lookup b h of
--  Nothing -> mutArrContParse bs
--  Just (_,c) -> do p  <- parseClosure b c
--                   ps <- mutArrContParse bs
--                   return $ p : ps

-- TODO: Doesn't work quite right, for example with (1,"fo")
mbParens :: [VisObject] -> [VisObject]
mbParens t = if needsParens
    then Unnamed "(" : t ++ [Unnamed ")"]
    else t
  where needsParens = go (0 :: Int) $ show t
        go 0 (' ':_) = True
        go _ [] = False
        go c (')':ts) = go (c-1) ts
        go c ('(':ts) = go (c+1) ts
        go c ('\'':'(':ts) = go c ts
        go c ('\'':')':ts) = go c ts
        go c (_:ts) = go c ts
--mbParens t | ' ' `objElem` t = Unnamed "(" : t ++ [Unnamed ")"]
--           | otherwise     = t
--  where objElem c = any go
--          where go (Unnamed xs) = c `elem` xs
--                go (Named _ os) = any go os
--                go _ = False

--showClosure :: Closure -> String
--showClosure = unwords . showClosureFields

-- | Textual representation of Heap objects, used in the graph visualization.
showClosureFields :: GenClosure t -> [String]
showClosureFields (ConsClosure _ _ [dataArg] _ modl name) =
 case (modl, name) of
    k | k `elem` [ ("GHC.Word", "W#")
                 , ("GHC.Word", "W8#")
                 , ("GHC.Word", "W16#")
                 , ("GHC.Word", "W32#")
                 , ("GHC.Word", "W64#")
                 ] -> [name, show dataArg]

    k | k `elem` [ ("GHC.Integer.Type", "S#")
                 , ("GHC.Types", "I#")
                 , ("GHC.Int", "I8#")
                 , ("GHC.Int", "I16#")
                 , ("GHC.Int", "I32#")
                 , ("GHC.Int", "I64#")
                 ] -> [name, show ((fromIntegral :: Word -> Int) dataArg)]

    ("GHC.Types", "C#") -> ["C#", [chr $ fromIntegral dataArg]]

    ("GHC.Types", "D#") -> ["D#", printf "%0.5f" (unsafeCoerce dataArg :: Double)]
    ("GHC.Types", "F#") -> ["F#", printf "%0.5f" (unsafeCoerce dataArg :: Double)]

    -- :m +GHC.Arr
    -- let b = array ((1,1),(3,2)) [((1,1),42),((1,2),23),((2,1),999),((2,2),1000),((3,1),1001),((3,2),1002)]
    -- b
    -- :view b
    _ -> [name, show dataArg]

showClosureFields (ConsClosure (StgInfoTable 1 3 _ 0) _ [_,0,0] _ "Data.ByteString.Internal" "PS")
  = ["ByteString","0","0"]

showClosureFields (ConsClosure (StgInfoTable 1 3 _ 0) [_] [_,start,end] _ "Data.ByteString.Internal" "PS")
  = ["ByteString",printf "%d" start,printf "%d" end]

showClosureFields (ConsClosure (StgInfoTable 2 3 _ 1) [_,_] [_,start,end] _ "Data.ByteString.Lazy.Internal" "Chunk")
  = ["Chunk",printf "%d" start,printf "%d" end]

showClosureFields (ConsClosure _ _ dArgs _ _ name)
  = name : map show dArgs

-- Reversed order of ptrs
showClosureFields ThunkClosure{}
  = ["Thunk"]

showClosureFields SelectorClosure{}
  = ["Selector"]

-- Probably should delete these from Graph
showClosureFields IndClosure{}
  = ["Ind"]

showClosureFields BlackholeClosure{}
  = ["Blackhole"]

showClosureFields APClosure{}
  = ["AP"]

showClosureFields PAPClosure{}
  = ["PAP"]

showClosureFields APStackClosure{}
  = ["APStack"]

showClosureFields BCOClosure{}
  = ["BCO"]

showClosureFields (ArrWordsClosure _ _ arrWords)
  = map (printf "0x%x") arrWords

showClosureFields MutArrClosure{}
  = ["MutArr"]

showClosureFields MutVarClosure{}
  = ["MutVar"]

showClosureFields MVarClosure{}
  = ["MVar#"]

showClosureFields FunClosure{}
  = ["Fun"]

showClosureFields BlockingQueueClosure{}
  = ["BlockingQueue"]

showClosureFields (OtherClosure (StgInfoTable _ _ cTipe _) _ _)
  = [show cTipe]

showClosureFields (UnsupportedClosure (StgInfoTable _ _ cTipe _))
  = [show cTipe]

--showClosure c = "Missing pattern for " ++ show c

-- | Make infix names prefix
infixFix :: String -> String
infixFix xs
  | isInfix xs = '(' : xs ++ ")"
  | otherwise  = xs

-- | Determine whether a name is an infix name, based on
-- http://www.haskell.org/onlinereport/haskell2010/haskellch2.html
isInfix :: String -> Bool
isInfix []              = False
isInfix ('[':_)         = False
isInfix ('(':_)         = False
isInfix (x:_)
  | x `elem` ascSymbols = True
  | isSymbol x          = True
  | isPunctuation x     = True
  | otherwise           = False
  where ascSymbols = "!#$%&*+./<=>?@  \\^|-~:"
