module Util.YAML where

import Data.Char
import qualified Data.Map as M
import qualified Data.Set as S

data Node = Leaf String | List [Node] | Map [(String,Node)] | Null

class MapKey a where
    showMapKey :: a -> String

instance MapKey String where
    showMapKey s = s

class ToNode a where
    toNode :: a -> Node

instance ToNode Node where
    toNode x = x

instance ToNode String where
    toNode s = Leaf s

instance ToNode a => ToNode [a] where
    toNode ns = List (map toNode ns)

instance (MapKey k,ToNode a) => ToNode [(k,a)] where
    toNode ns = Map [ (showMapKey x,toNode y) | (x,y) <- ns ]

instance (MapKey k,ToNode b) => ToNode (M.Map k b) where
    toNode mp = Map [(showMapKey x, toNode y) | (x,y) <- M.toList mp]

instance ToNode a => ToNode (S.Set a) where
    toNode st = List $ map toNode (S.toList st)

instance ToNode a => ToNode (Maybe a) where
    toNode Nothing = Null
    toNode (Just x) = toNode x

instance (ToNode a,ToNode b) => ToNode (Either a b) where
    toNode (Left  x) = toNode x
    toNode (Right x) = toNode x

instance ToNode Bool where
    toNode True  = Leaf "true"
    toNode False = Leaf "false"

instance ToNode () where
    toNode () = Null

dumpNode :: Node -> String
dumpNode n = f False 0 n "\n" where
    f nn  i Null     = ns nn . showString "null"
    f nn  i (Leaf x) = ns nn . showString' x
    f nn i (List ns) = nl nn [ g i . showString "-" . f True (i + 1) n | n <- ns ]
    f nn i (Map  ns) = nl nn [ g i . showString x . showString ":" . f True (i + 1) y | (x,y) <- ns ]
    g i = showString $ replicate i ' '
    nl nn [] = id
    nl nn xs = (if nn then ('\n':) else id) . foldr1 (\x y -> x . showChar '\n' . y ) xs
    ns True  = showChar ' '
    ns False = id

showYAML :: ToNode a => a -> String
showYAML n = dumpNode (toNode n)

showString' x y = if all isGood x then x ++ y else '"':f x y where
    f [] y = '"':y
    f (x:xs) ys |  isQuoteGood x = x:f xs ys
                | otherwise  = '\\':x:f xs ys
    isGood x = isAlphaNum x || x `elem` "_-.@/"
    isQuoteGood x = isGood x || isSpace x || x `elem` "!@#$%^&*(){}/"