{-# LANGUAGE CPP, OverloadedStrings, PatternGuards #-}
{-# OPTIONS_HADDOCK hide #-}

{- |
   Module      : Data.GraphViz.Internal.Util
   Description : Internal utility functions
   Copyright   : (c) Ivan Lazar Miljenovic
   License     : 3-Clause BSD-style
   Maintainer  : Ivan.Miljenovic@gmail.com

   This module defines internal utility functions.
-}
module Data.GraphViz.Internal.Util where

import Data.Char (isAsciiLower, isAsciiUpper, isDigit, ord)

import           Control.Monad       (liftM2)
import           Data.Function       (on)
import           Data.List           (groupBy, sortBy)
import           Data.Maybe          (isJust)
import           Data.Set            (Set)
import qualified Data.Set            as Set
import           Data.Text.Lazy      (Text)
import qualified Data.Text.Lazy      as T
import qualified Data.Text.Lazy.Read as T

#if MIN_VERSION_base(4,8,0)
import Data.Version (Version, makeVersion)
#else
import Data.Version (Version(..))
#endif

-- -----------------------------------------------------------------------------

isIDString :: Text -> Bool
isIDString :: Text -> Bool
isIDString = forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (\(Char
f,Text
os) -> Char -> Bool
frstIDString Char
f Bool -> Bool -> Bool
&& (Char -> Bool) -> Text -> Bool
T.all Char -> Bool
restIDString Text
os)
             forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Maybe (Char, Text)
T.uncons

-- | First character of a non-quoted 'String' must match this.
frstIDString   :: Char -> Bool
frstIDString :: Char -> Bool
frstIDString Char
c = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall a b. (a -> b) -> a -> b
$Char
c) [ Char -> Bool
isAsciiUpper
                          , Char -> Bool
isAsciiLower
                          , forall a. Eq a => a -> a -> Bool
(==) Char
'_'
                          , (\ Char
x -> Char -> Int
ord Char
x forall a. Ord a => a -> a -> Bool
>= Int
128)
                          ]

-- | The rest of a non-quoted 'String' must match this.
restIDString   :: Char -> Bool
restIDString :: Char -> Bool
restIDString Char
c = Char -> Bool
frstIDString Char
c Bool -> Bool -> Bool
|| Char -> Bool
isDigit Char
c

-- | Determine if this String represents a number.  Boolean parameter
--   determines if exponents are considered part of numbers for this.
isNumString     :: Bool -> Text -> Bool
isNumString :: Bool -> Text -> Bool
isNumString Bool
_      Text
""  = Bool
False
isNumString Bool
_      Text
"-" = Bool
False
isNumString Bool
allowE Text
str = case Text -> Maybe (Char, Text)
T.uncons forall a b. (a -> b) -> a -> b
$ Text -> Text
T.toLower Text
str of
                           Just (Char
'-',Text
str') -> Text -> Bool
go Text
str'
                           Maybe (Char, Text)
_               -> Text -> Bool
go Text
str
  where
    -- Can't use Data.Text.Lazy.Read.double as it doesn't cover all
    -- possible cases
    go :: Text -> Bool
go Text
s = forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry Text -> Text -> Bool
go' forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> Text -> (Text, Text)
T.span Char -> Bool
isDigit Text
s
    go' :: Text -> Text -> Bool
go' Text
ds Text
nds
      | Text -> Bool
T.null Text
nds = Bool
True
      | Text -> Bool
T.null Text
ds Bool -> Bool -> Bool
&& Text
nds forall a. Eq a => a -> a -> Bool
== Text
"." = Bool
False
      | Text -> Bool
T.null Text
ds
      , Just (Char
'.',Text
nds') <- Text -> Maybe (Char, Text)
T.uncons Text
nds
      , Just (Char
d,Text
nds'') <- Text -> Maybe (Char, Text)
T.uncons Text
nds' = Char -> Bool
isDigit Char
d Bool -> Bool -> Bool
&& Text -> Bool
checkEs' Text
nds''
      | Just (Char
'.',Text
nds') <- Text -> Maybe (Char, Text)
T.uncons Text
nds = Text -> Bool
checkEs forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> Text -> Text
T.dropWhile Char -> Bool
isDigit Text
nds'
      | Text -> Bool
T.null Text
ds = Bool
False
      | Bool
otherwise = Text -> Bool
checkEs Text
nds
    checkEs' :: Text -> Bool
checkEs' Text
s = case (Char -> Bool) -> Text -> (Text, Text)
T.break (Char
'e' forall a. Eq a => a -> a -> Bool
==) Text
s of
                   (Text
"", Text
_) -> Bool
False
                   (Text
ds,Text
es) -> (Char -> Bool) -> Text -> Bool
T.all Char -> Bool
isDigit Text
ds Bool -> Bool -> Bool
&& Text -> Bool
checkEs Text
es
    checkEs :: Text -> Bool
checkEs Text
str' = case Text -> Maybe (Char, Text)
T.uncons Text
str' of
                     Maybe (Char, Text)
Nothing       -> Bool
True
                     Just (Char
'e',Text
ds) -> Bool
allowE Bool -> Bool -> Bool
&& Text -> Bool
isIntString Text
ds
                     Maybe (Char, Text)
_             -> Bool
False

{-
-- | This assumes that 'isNumString' is 'True'.
toDouble     :: Text -> Double
toDouble str = case T.uncons $ T.toLower str of
                 Just ('-', str') -> toD $ '-' `T.cons` adj str'
                 _                -> toD $ adj str
  where
    adj s = T.cons '0'
            $ case T.span ('.' ==) s of
                (ds, ".") | not $ T.null ds -> s `T.snoc` '0'
                (ds, ds') | Just ('.',es) <- T.uncons ds'
                          , Just ('e',es') <- T.uncons es
                            -> ds `T.snoc` '.' `T.snoc` '0'
                                   `T.snoc` 'e' `T.snoc` '0' `T.append` es'
                _         -> s
    toD = either (const $ error "Not a Double") fst . T.signed T.double
-}
-- | This assumes that 'isNumString' is 'True'.
toDouble     :: Text -> Double
toDouble :: Text -> Double
toDouble Text
str = case Text -> Maybe (Char, Text)
T.uncons forall a b. (a -> b) -> a -> b
$ Text -> Text
T.toLower Text
str of
                 Just (Char
'-', Text
str') -> Text -> Double
toD forall a b. (a -> b) -> a -> b
$ Char
'-' Char -> Text -> Text
`T.cons` Text -> Text
adj Text
str'
                 Maybe (Char, Text)
_                -> Text -> Double
toD forall a b. (a -> b) -> a -> b
$ Text -> Text
adj Text
str
  where
    adj :: Text -> Text
adj Text
s = Char -> Text -> Text
T.cons Char
'0'
            forall a b. (a -> b) -> a -> b
$ case (Char -> Bool) -> Text -> (Text, Text)
T.span (Char
'.' forall a. Eq a => a -> a -> Bool
==) Text
s of
                (Text
ds, Text
".") | Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Text -> Bool
T.null Text
ds -> Text
s Text -> Char -> Text
`T.snoc` Char
'0'
                (Text
ds, Text
ds') | Just (Char
'.',Text
es) <- Text -> Maybe (Char, Text)
T.uncons Text
ds'
                          , Just (Char
'e',Text
_) <- Text -> Maybe (Char, Text)
T.uncons Text
es
                            -> Text
ds Text -> Char -> Text
`T.snoc` Char
'.' Text -> Char -> Text
`T.snoc` Char
'0' Text -> Text -> Text
`T.append` Text
es
                (Text, Text)
_              -> Text
s
    toD :: Text -> Double
toD = forall a. Read a => String -> a
read forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack

isIntString :: Text -> Bool
isIntString :: Text -> Bool
isIntString = forall a. Maybe a -> Bool
isJust forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Maybe Int
stringToInt

-- | Determine if this String represents an integer.
stringToInt     :: Text -> Maybe Int
stringToInt :: Text -> Maybe Int
stringToInt Text
str = case forall a. Num a => Reader a -> Reader a
T.signed forall a. Integral a => Reader a
T.decimal Text
str of
                       Right (Int
n, Text
"") -> forall a. a -> Maybe a
Just Int
n
                       Either String (Int, Text)
_             -> forall a. Maybe a
Nothing

-- | Graphviz requires double quotes to be explicitly escaped.
escapeQuotes           :: String -> String
escapeQuotes :: String -> String
escapeQuotes []        = []
escapeQuotes (Char
'"':String
str) = Char
'\\'forall a. a -> [a] -> [a]
:Char
'"'forall a. a -> [a] -> [a]
: String -> String
escapeQuotes String
str
escapeQuotes (Char
c:String
str)   = Char
c forall a. a -> [a] -> [a]
: String -> String
escapeQuotes String
str

-- | Remove explicit escaping of double quotes.
descapeQuotes                :: String -> String
descapeQuotes :: String -> String
descapeQuotes []             = []
descapeQuotes (Char
'\\':Char
'"':String
str) = Char
'"' forall a. a -> [a] -> [a]
: String -> String
descapeQuotes String
str
descapeQuotes (Char
c:String
str)        = Char
c forall a. a -> [a] -> [a]
: String -> String
descapeQuotes String
str

isKeyword :: Text -> Bool
isKeyword :: Text -> Bool
isKeyword = (forall a. Ord a => a -> Set a -> Bool
`Set.member` Set Text
keywords) forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
T.toLower

-- | The following are Dot keywords and are not valid as labels, etc. unquoted.
keywords :: Set Text
keywords :: Set Text
keywords = forall a. Ord a => [a] -> Set a
Set.fromList [ Text
"node"
                        , Text
"edge"
                        , Text
"graph"
                        , Text
"digraph"
                        , Text
"subgraph"
                        , Text
"strict"
                        ]

createVersion :: [Int] -> Version
#if MIN_VERSION_base(4,8,0)
createVersion :: [Int] -> Version
createVersion = [Int] -> Version
makeVersion
#else
createVersion bs = Version { versionBranch = bs, versionTags = []}
#endif

-- -----------------------------------------------------------------------------

uniq :: (Ord a) => [a] -> [a]
uniq :: forall a. Ord a => [a] -> [a]
uniq = forall b a. Ord b => (a -> b) -> [a] -> [a]
uniqBy forall a. a -> a
id

uniqBy   :: (Ord b) => (a -> b) -> [a] -> [a]
uniqBy :: forall b a. Ord b => (a -> b) -> [a] -> [a]
uniqBy a -> b
f = forall a b. (a -> b) -> [a] -> [b]
map forall a. [a] -> a
head forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall b a. Ord b => (a -> b) -> [a] -> [[a]]
groupSortBy a -> b
f

groupSortBy   :: (Ord b) => (a -> b) -> [a] -> [[a]]
groupSortBy :: forall b a. Ord b => (a -> b) -> [a] -> [[a]]
groupSortBy a -> b
f = forall a. (a -> a -> Bool) -> [a] -> [[a]]
groupBy (forall a. Eq a => a -> a -> Bool
(==) forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` a -> b
f) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy (forall a. Ord a => a -> a -> Ordering
compare forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` a -> b
f)

groupSortCollectBy     :: (Ord b) => (a -> b) -> (a -> c) -> [a] -> [(b,[c])]
groupSortCollectBy :: forall b a c. Ord b => (a -> b) -> (a -> c) -> [a] -> [(b, [c])]
groupSortCollectBy a -> b
f a -> c
g = forall a b. (a -> b) -> [a] -> [b]
map (forall (m :: * -> *) a1 a2 r.
Monad m =>
(a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 (,) (a -> b
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> a
head) (forall a b. (a -> b) -> [a] -> [b]
map a -> c
g)) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall b a. Ord b => (a -> b) -> [a] -> [[a]]
groupSortBy a -> b
f

-- | Fold over 'Bool's; first param is for 'False', second for 'True'.
bool       :: a -> a -> Bool -> a
bool :: forall a. a -> a -> Bool -> a
bool a
f a
t Bool
b = if Bool
b
             then a
t
             else a
f

isSingle     :: [a] -> Bool
isSingle :: forall a. [a] -> Bool
isSingle [a
_] = Bool
True
isSingle [a]
_   = Bool
False