-----------------------------------------------------------------------------
-- 
-- Module      :  Prof.hs
-- Copyright   :  (c) Asumu Takikawa 2007
-- License     :  
--
-- Maintainer  :  
-- Stability   :  unstable
-- Portability :  not portable
--
-----------------------------------------------------------------------------

module Prof where

import Data.Char
import System.Directory (doesFileExist)
import System.FilePath
import System.IO
import System.Process
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Token
import Text.ParserCombinators.Parsec.Language (haskellDef)

import Graphics.UI.Gtk
import Graphics.UI.Gtk.ModelView.CellLayout
import qualified Graphics.UI.Gtk.ModelView as MView

import PropLang.Variable

import Data
import Evaluator
import Util

data Profile = Profile
               { title :: String
	       , flags :: String
	       , time :: String
	       , alloc :: String
	       }

data ProfileLine = ProfileLine 
		   { costCentre :: String
		   , moduleName :: String
		   , entries :: Integer
		   , indvTime :: Double
		   , indvAlloc :: Double
		   , inhTime :: Double
		   , inhAlloc :: Double 
		   }

--
-- Run the profiler
--
runProf :: Data -> IO ()
runProf dat = do
    cF <- getVar $ profCFlags dat
    rF <- getVar $ profRFlags dat
    o  <- getVar $ executable dat
    src <- getVar $ filename dat
    case src of
      Just x -> do
	exist <- doesFileExist x
	if not exist
	  then errorMessage dat "Selected file does not exist."
	  else do
	    (_,_,_,pid) <- runExternal "ghc" $ 
	        Just $ words cF ++ ["-o", o] ++ [x]
	    waitForProcess pid
	    let exe = if inCurrentDir o then "." </> o else o 
	    (_,_,_,pid2) <- runExternal exe $ Just $ words rF
	    waitForProcess pid2
	    res <- runProfileParser $ o ++ ".prof"
	    case res of
	      Left s  -> errorMessage dat s 
	      Right p -> runParseDialog p
      Nothing -> do
	errorMessage dat "No file selected for profiling."

    where
      inCurrentDir = null . fst . splitFileName

-- Parse a line
parseProfileLine :: Parser ProfileLine
parseProfileLine = do 
    let lexer = makeTokenParser haskellDef
    spaces
    cc <- notSpaces     ; spaces
    mn <- notSpaces     ; spaces
    no <- natural lexer ; spaces
    en <- natural lexer ; spaces
    it <- float lexer   ; spaces
    ia <- float lexer   ; spaces
    iht <- float lexer  ; spaces
    iha <- float lexer
    return $ ProfileLine cc mn en it ia iht iha

    where
	notSpaces = many $ satisfy $ not . isSpace

-- Parse the profiling output
parseProfile :: Parser [ProfileLine]
parseProfile = do
    ls <- many1 parseProfileLine
    return ls

-- Run the profile parsers
runProfileParser :: FilePath -> IO (Either String (Profile, [ProfileLine]))
runProfileParser file = do
  b <- doesFileExist file
  if not b 
    then return $ Left "No .prof file was found. Check that there are no compile errors or missing profiling flags."
    else do
      contents <- readFile file 
      let (title:_:flags:_:time:alloc:rest) = lines contents
	  prof = Profile (dropWhile isSpace title) (dropWhile isSpace flags)
	                 (dropWhile isSpace time) (dropWhile isSpace alloc)
      case parse parseProfile "" (unlines $ drop 11 $ rest) of
	Left x  -> return $ Left $ "Parse error: " ++ show x ++ "\n\n" ++
				    "This is likely a bug. Please file a report."
	Right x -> return $ Right (prof, x)

-- Set up and display the profiling dialog
runParseDialog :: (Profile, [ProfileLine]) -> IO ()
runParseDialog (p, ls) = do
    d <- dialogNew
    dialogAddButton d "gtk-close" ResponseClose
    up <- dialogGetUpper d
    titleLabel <- labelNew $ Just $ title p
    flagLabel  <- labelNew $ Just $ flags p
    timeLabel  <- labelNew $ Just $ time p
    allocLabel <- labelNew $ Just $ alloc p
    
    view <- MView.treeViewNew
    store <- MView.treeStoreNew []
    MView.treeViewSetModel view store

    -- Thanks to the Gtk2hs folks for this
    let createTextColumn name field = do
	  column <- MView.treeViewColumnNew
	  MView.treeViewAppendColumn view column
	  MView.treeViewColumnSetTitle column name
	  cell <- cellRendererTextNew
	  MView.treeViewColumnPackStart column cell True
	  cellLayoutSetAttributes column cell store
	    (\record -> [MView.cellText := field record])
	  
    createTextColumn "Cost Centre"       costCentre
    createTextColumn "Module"            moduleName
    createTextColumn "Entries"           (show . entries)
    createTextColumn "Individual %time"  (show . indvTime)
    createTextColumn "Individual %alloc" (show . indvAlloc)
    createTextColumn "Inherited %time"   (show . inhTime)
    createTextColumn "Inherited %alloc"  (show . inhAlloc)

    mapM_ (MView.treeStoreInsert store [] 0) ls

    boxPackStart up titleLabel PackNatural 0
    boxPackStart up flagLabel PackNatural 0
    boxPackStart up timeLabel PackNatural 0
    boxPackStart up allocLabel PackNatural 0
    boxPackStart up view PackRepel 0
    
    widgetShowAll d
    dialogRun d
    widgetHide d
    return ()
