{-# LANGUAGE OverloadedStrings #-}

module Dot.Text
  ( encode
  , encodeLazy
  , builder
  , encodeToFile
  ) where

import Data.Monoid
import Data.Text (Text)
import Data.Text.Lazy.Builder (Builder)
import Dot.Types
import qualified Data.Text as Text
import qualified Data.Text.IO as TIO
import qualified Data.Text.Lazy as LText
import qualified Data.Text.Lazy.Builder as Builder

levelSpaces :: Int
levelSpaces = 2

indentationBuilder :: Builder
indentationBuilder = "  "

encode :: DotGraph -> Text
encode = LText.toStrict . encodeLazy

encodeLazy :: DotGraph -> LText.Text
encodeLazy = Builder.toLazyText . builder

encodeToFile :: FilePath -> DotGraph -> IO ()
encodeToFile fp dg = TIO.writeFile fp (encode dg)

builder :: DotGraph -> Builder
builder (DotGraph strictness directionality mid statements) = mempty
  <> encodeStrictness strictness
  <> encodeGraphDirectionality directionality
  <> encodeMaybeId mid
  <> "{\n"
  <> foldr (\statement builder -> encodeStatement directionality indentationBuilder statement <> builder) mempty statements
  <> "}"

encodeId :: Id -> Builder
encodeId (Id theId) = case Text.uncons theId of
  Just (c,_) -> if not (c >= '0' && c <= '9') && Text.all (\c -> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') theId
    then Builder.fromText theId
    else "\""
      <> Builder.fromText
         ( Text.replace "\"" "\\\""
         $ Text.replace "\n" "\\n"
         $ Text.replace "\\" "\\\\"
         $ theId
         )
      <> "\""
  Nothing -> "\"\""

encodeNodeId :: NodeId -> Builder
encodeNodeId (NodeId theId mport) =
  encodeId theId <> maybe mempty encodePort mport

encodePort :: Port -> Builder
encodePort (Port theId mcompass) =
  ":" <> encodeId theId <> maybe mempty encodeCompass mcompass

encodeCompass :: CardinalDirection -> Builder
encodeCompass x = case x of
  North -> "n"
  East -> "e"
  South -> "s"
  West -> "w"
  Northeast -> "ne"
  Northwest -> "nw"
  Southeast -> "se"
  Southwest -> "sw"

encodeMaybeId :: Maybe Id -> Builder
encodeMaybeId x = case x of
  Just theId -> encodeId theId <> " "
  Nothing -> mempty

encodeStrictness :: Strictness -> Builder
encodeStrictness x = case x of
  Strict -> "strict "
  NonStrict -> mempty

encodeElement :: Element -> Builder
encodeElement x = case x of
  Graph -> "graph "
  Node -> "node "
  Edge -> "edge "

encodeSubgraph :: Subgraph -> Builder
encodeSubgraph = error "encodeSubgraph: have not written this function yet"

encodeStatement :: Directionality -> Builder -> Statement -> Builder
encodeStatement directionality indentation x = case x of
  StatementAttribute (AttributeStatement element attrs) -> indentation
    <> encodeElement element
    <> encodeAttributes attrs
  StatementNode (NodeStatement theNodeId attrs) -> indentation
    <> encodeNodeId theNodeId
    <> encodeAttributes attrs
  StatementSubgraph subgraph -> encodeSubgraph subgraph
  StatementEdge (EdgeStatement elements attrs) -> indentation
    <> encodeEdgeElements directionality elements
    <> encodeAttributes attrs
  StatementEquality a b -> indentation
    <> encodeId a <> " = " <> encodeId b <> "\n"
  where nextIndentation = indentationBuilder <> indentation

encodeEdgeOp :: Directionality -> Builder
encodeEdgeOp x = case x of
  Undirected -> " -- "
  Directed -> " -> "

encodeEdgeElements :: Directionality -> ListTwo EdgeElement -> Builder
encodeEdgeElements edgeOp (ListTwo a b xs) =
  encodeEdgeElement a <> edgeOpBuilder <> encodeEdgeElement b
  <> foldr (\e builder -> edgeOpBuilder <> encodeEdgeElement e <> builder) mempty xs
  where edgeOpBuilder = encodeEdgeOp edgeOp

encodeEdgeElement :: EdgeElement -> Builder
encodeEdgeElement x = case x of
  EdgeSubgraph subgraph -> encodeSubgraph subgraph
  EdgeNode theNodeId -> encodeNodeId theNodeId

encodeAttributes :: [Attribute] -> Builder
encodeAttributes (x : xs) = " ["
  <> foldr (\attr builder -> encodeAttribute attr <> "," <> builder) (encodeAttribute x) xs
  <> "];\n"
encodeAttributes [] = " [];\n"

encodeAttribute :: Attribute -> Builder
encodeAttribute (Attribute attrId valId) = encodeId attrId <> "=" <> encodeId valId

encodeGraphDirectionality :: Directionality -> Builder
encodeGraphDirectionality x = case x of
  Directed -> "digraph "
  Undirected -> "graph "

example :: DotGraph
example = DotGraph Strict Directed (Just "foobar")
  [ StatementNode $ NodeStatement "a1"
    [ Attribute "color" "blue"
    , Attribute "shape" "box"
    ]
  , StatementNode $ NodeStatement "a2" []
  , StatementEdge $ EdgeStatement (ListTwo "a1" "a2" ["a3"])
    [ Attribute "color" "red"
    ]
  ]