module Main (main) where
import Data.List (nub)
import System.Info (os)
import System.Directory
import System.CPUTime (getCPUTime)
import System.Process (callCommand)
import CaseBi (getBFst')
import qualified Data.Vector as V
import EndOfExe (showE)
import Data.Maybe (isJust,fromJust)
isSep :: Char -> Bool
isSep c = c == ':'
isWindows :: Bool
isWindows = take 5 os == "mingw"
divideString :: (Char -> Bool) -> String -> [String]
divideString p xs | null xs = []
| otherwise = let (zs,ys) = break p xs in zs:divideString p (if null ys then ys else tail ys)
isEscapeChar :: Char -> Bool
isEscapeChar xs = xs `elem` "\n\r"
dropEmptyLines :: [String] -> [String]
dropEmptyLines [] = []
dropEmptyLines (ys:yss) | let ts = dropWhile isSep ys in all isEscapeChar ts || null ts = dropEmptyLines yss
| otherwise = ys:dropEmptyLines yss
cells :: String -> [[String]]
cells x = map (divideString isSep) (dropEmptyLines (lines x))
changeCell :: String -> String -> String -> String
changeCell xs ys zs | not . null $ ys = ys
| null ys && (not . null $ zs) = xs
| otherwise = []
isTruncated :: String -> String -> Bool
isTruncated ws vs = null ws && not (null vs)
toBoolList :: [String] -> [Bool]
toBoolList yss = zipWith isTruncated yss ([]:yss)
countChanged :: [Bool] -> Int
countChanged z = length (takeWhile not z)
createSecondLine :: [String] -> [String] -> [String]
createSecondLine xss yss = take (countChanged (toBoolList yss)) tss
where tss = zipWith3 changeCell xss yss uss
uss = tail yss ++ [""]
lineN :: Int -> [[String]] -> [String]
lineN n xss = last $! take n xss
lineN' :: Int -> [[String]] -> [String]
lineN' n xss = last $! take (n-1) xss
createNthLine :: [[String]] -> Int -> [String]
createNthLine xss n | n < 1 || n > length xss = error "Undefined line!"
| n == 1 = concat . take 1 $ xss
| otherwise = createSecondLine (lineN' n xss) (lineN n xss)
fillEmptyCells :: [[String]] -> [[String]]
fillEmptyCells xss = map (createNthLine xss) [1..length xss]
changeNthLine :: [String] -> String
changeNthLine xs = "\"" ++ concatMap (++"\"->\"") xs ++ endOfLineGv
dropLast :: String -> String
dropLast xs | isWindows = if drop (length xs - 5) xs == "->\"\r\n"
then dropLast (take (length xs - 5) xs)
else xs ++ ";\r\n"
| drop (length xs - 4) xs == "->\"\n" = dropLast (take (length xs - 4) xs)
| otherwise = xs ++ ";\n"
dropDouble :: String -> String
dropDouble (x:y:xs) | x == '\"' && y == '\"' = dropDouble (y:xs)
| otherwise = x:dropDouble (y:xs)
dropDouble xs = xs
dropNull :: [String] -> [String]
dropNull = filter (not . null)
processCellsA :: String -> [[String]]
processCellsA = fillEmptyCells . cells
processCellsZ :: [[String]] -> [String]
processCellsZ = map (dropDouble . dropLast . changeNthLine)
takeColumn :: Int -> [[String]] -> [String]
takeColumn n xss | null xss = error "Empty list in takeColumn!"
| if n < 1 then True else n > (length . head $ xss) = error "Undefined column!"
| otherwise = map (concat . take 1 . drop (n-1)) xss
findParentCell :: Int -> Int -> [[String]] -> String
findParentCell m n xss | if n < 2 then True else n > length xss = error "Undefined column!"
| otherwise = last . dropNull . take (n - 1) $ takeColumn m xss
createNthLine2 :: [[String]] -> Int -> [String]
createNthLine2 x n | if n < 1 then True else n > length x = error "Undefined line!"
| n == 1 = dropNull . head $ x
| null . head . lineN n $ x = findParentCell (length . takeWhile null . lineN n $ x) n x:dropNull (lineN n x)
| otherwise = dropNull (lineN n x)
fillEmptyCells2 :: [[String]] -> [[String]]
fillEmptyCells2 xss = map (createNthLine2 xss) [length xss,length xss - 1..1]
beginsWithAtSign :: String -> Bool
beginsWithAtSign xs | null xs = False
| length xs < 2 = False
| take 1 xs == "@" = True
| take 1 xs == "\"" && (drop 1 . take 2 $ xs) == "@" = True
| otherwise = False
findFilledWithColor :: [[String]] -> [String]
findFilledWithColor = concatMap (filter beginsWithAtSign)
endOfLineGv :: String
endOfLineGv | isWindows = "\r\n"
| otherwise = "\n"
makeFilledWithColor :: [String] -> String
makeFilledWithColor xss = concat (nub . zipWith (++) xss $ (repeat (" [style=filled, fillcolor=\"#ffffba\"];" ++ endOfLineGv)))
processCells :: String -> String
processCells = concat . nub . processCellsZ . fillEmptyCells2 . processCellsA
combineCells :: String -> String -> String
combineCells x0 x = let ch = getBFst' ("true", V.fromList [("0", "false"), ("1", "true"), ("2", "ortho"), ("3", "polyline")]) x0 in let (y,z) = (x,x) in concat ["strict digraph 1 {", endOfLineGv,
"overlap=false", endOfLineGv, "splines=", ch, endOfLineGv, processCells z, makeFilledWithColor . findFilledWithColor . fillEmptyCells2 . processCellsA $ y, endOfLineGv, "}", endOfLineGv]
main :: IO ()
main = do
xs <- readFile "1.csv"
if length xs > 0
then do
putStrLn "Please, input the basic name of the visualization file!"
zs <- getLine
ts <- getCPUTime
putStrLn "Please, specify the splines mode for GraphViz (see the documentation for GraphViz)"
putStrLn "0 -- for \"splines=false\""
putStrLn "1 -- for \"splines=true\""
putStrLn "2 -- for \"splines=ortho\""
putStrLn "3 -- for \"splines=polyline\""
putStrLn "The default one is \"splines=true\""
x2 <- getLine
let x0 = take 1 x2
putStrLn "Would you like to remove all \'@\' signs from the visualization file?"
remAt <- getLine
if take 1 remAt == "y"
then do
let ys = filter (/='@') . combineCells x0 $ xs in writeFile ("new." ++ show ts ++ "." ++ zs ++ ".gv") ys
putStrLn "The visualization will be created without the at-sign."
processFile 'n' ts zs
removeFile $ show ts ++ "." ++ zs ++ ".csv"
else do
let ys = combineCells x0 xs in writeFile (show ts ++ "." ++ zs ++ ".gv") ys
putStrLn "The visualization will be created with the at-sign preserved."
processFile 'a' ts zs
else error "Epmty file 1.csv!"
processFile :: Char -> Integer -> String -> IO ()
processFile w ts zs = do
renameFile "1.csv" (show ts ++ "." ++ zs ++ ".csv")
let x1 = showE "fdp"
x2 = showE "twopi"
x3 = showE "circo"
x4 = showE "neato"
x5 = showE "sfdp"
x6 = showE "dot"
if all isJust [x1,x2,x3,x4,x5,x6]
then do
putStrLn "Please, specify the GraphViz command: "
putStrLn "\'d\' -- for dot"
putStrLn "\'f\' -- for fdp"
putStrLn "\'t\' -- for twopi"
putStrLn "\'c\' -- for circo"
putStrLn "\'n\' -- for neato"
putStrLn "\'s\' -- for sfdp"
putStrLn "otherwise there will be used the default sfdp"
vs <- getLine
let u = take 1 vs in if null u || u == "\n" || u == "\x0000"
then error "Please, specify the needed character"
else do
let temp = getBFst' (fromJust (showE "sfdp"), V.fromList (map (\(x, y) -> (x, fromJust y)) [("c", showE "circo"), ("d", showE "dot"), ("f", showE "fdp"),
("n", showE "neato"), ("s", showE "sfdp"), ("t", showE "twopi")])) in callCommand $ temp u ++ (if w == 'n' then " -Tsvg new." else " -Tsvg ") ++
show ts ++ "." ++ zs ++ ".gv -O "
else error "Please, install the GraphViz so that its executables are in the directories mentioned in the variable PATH!"