module LLVM.VisualizeGraph (
  OutputType(..),
  visualizeGraph
  ) where

import GHC.Conc ( getNumCapabilities )

import Control.Arrow
import Control.Concurrent.ParallelIO.Local
import Control.Monad ( when )
import qualified Data.ByteString as BS
import Data.GraphViz
import Data.Maybe ( isNothing )

import System.Directory
import System.FilePath
import System.FilePath.Glob

import Paths_llvm_tools

import LLVM.Analysis
import LLVM.Analysis.Util.Testing ( buildModule )
import LLVM.Parse ( defaultParserOptions, parseLLVMFile )

import LLVM.HtmlWrapper
import LLVM.SvgInspection

data OutputType = CanvasOutput GraphvizCanvas
                | FileOutput GraphvizOutput
                | HtmlOutput
                deriving (Show)


-- | Visualize a graph-based analysis with graphviz.  It handles many
-- common options including both file and canvas output.
visualizeGraph :: (ToGraphviz a)
                  => FilePath -- ^ Input file name
                  -> Maybe FilePath -- ^ Output file name
                  -> OutputType -- ^ Type of output requested
                  -> [String] -- ^ Module optimization flags
                  -> (Module -> [(String, a)]) -- ^ A function to turn a Module into some graphs
                  -> IO ()
visualizeGraph inFile outFile fmt optOptions fromModule  = do
  let p = parseLLVMFile defaultParserOptions
  m <- buildModule [] optOptions p inFile
  let gs = fromModule m

  case fmt of
    HtmlOutput -> do
      when (isNothing outFile) $ ioError $ userError "Output directory not specified"
      -- Make a directory for all of the output and render each graph
      -- with graphviz to svg format.  For each svg, create an html
      -- wrapper page (with an index page).  The html page should be simple
      -- and just embed the SVG and load svgpan (and maybe jquery)
      let Just outFile' = outFile
          gdir = outFile' </> "graphs"

      createDirectoryIfMissing True gdir
      let jsAndCss = [ "OpenLayers.js"
                     , "jquery-1.7.1.js"
                     , "showGraph.js"
                     , "graph.css"
                     ]
      mapM_ (installStaticFile gdir) jsAndCss
      installStaticSubdir "img" gdir

      caps <- getNumCapabilities
      let actions = map (makeFunctionPage toGraphviz gdir) gs
      withPool caps $ \capPool -> parallel_ capPool actions
      writeHtmlIndex outFile' (map fst gs)

    -- If we are showing canvases, ignore function names
    CanvasOutput o -> mapM_ (\(_,g) -> runGraphvizCanvas' (toGraphviz g) o) gs

    FileOutput o -> do
      when (isNothing outFile) $ ioError $ userError "Output file not specified"
      let Just outFile' = outFile
      case gs of
        [(_, g)] -> runGraphviz (toGraphviz g) o outFile' >> return ()
        _ -> do
          -- If we have more than one function, put all of them in
          -- the given directory
          createDirectoryIfMissing True outFile'
          mapM_ (writeDotGraph toGraphviz outFile' o) gs

installStaticFile :: FilePath -> FilePath -> IO ()
installStaticFile dir name = do
  file <- getDataFileName ("share" </> name)
  copyFile file (dir </> name)

-- | Install all of the files in the given subdir of the datadir to a
-- destdir.  The subdir is created (this is basically cp -R).
installStaticSubdir :: FilePath -> FilePath -> IO ()
installStaticSubdir sdir destdir = do
  dd <- getDataDir
  let patt = dd </> "share" </> sdir </> "*"
  files <- namesMatching patt
  let toDest f = destdir </> sdir </> takeFileName f
  let namesAndDests = map (id &&& toDest) files
  createDirectoryIfMissing True (destdir </> sdir)
  mapM_ (uncurry copyFile) namesAndDests

makeFunctionPage :: (PrintDotRepr dg n)
                    => (a -> dg n)
                    -> FilePath
                    -> (FilePath, a)
                    -> IO ()
makeFunctionPage toGraph gdir (fname, g) = do
  let svgname = gdir </> gfilename
      dg = toGraph g
  -- Use the more general graphvizWithHandle so that we can read the
  -- generated image before writing it to disk.  This lets us extract
  -- its dimensions.  The other alternative is to just use the default
  -- graphviz to create a file and then read the file - this led to
  -- races when writing with multiple threads.  This approach is
  -- safer.
  dims <- graphvizWithHandle (commandFor dg) dg Svg $ \h -> do
    svgContent <- BS.hGetContents h
    BS.writeFile svgname svgContent
    return (getSvgSize svgContent)
  let Just (w, h) = dims
  writeHtmlWrapper gdir hfilename gfilename fname w h
  where
    gfilename = fname <.> "svg"
    hfilename = fname <.> "html"

writeDotGraph :: (PrintDotRepr dg n)
                 => (a -> dg n)
                 -> FilePath
                 -> GraphvizOutput
                 -> (FilePath, a)
                 -> IO ()
writeDotGraph toGraph dirname o (funcName, g) =
  runGraphviz (toGraph g) o filename >> return ()
  where
    filename = dirname </> funcName <.> toExt o

toExt :: GraphvizOutput -> String
toExt o =
  case o of
    Canon -> "dot"
    XDot {} -> "dot"
    Eps -> "eps"
    Fig -> "fig"
    Jpeg -> "jpg"
    Pdf -> "pdf"
    Png -> "png"
    Ps -> "ps"
    Ps2 -> "ps"
    Svg -> "svg"
    _ -> error $ "Unsupported format: " ++ show o
