-- This file is formatted with Ormolu
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoMonomorphismRestriction #-}

module Types
  ( Named (..),
    NamedIcon,
    IconInfo,
    Icon (..),
    SyntaxNode (..),
    NodeName (..),
    Port (..),
    NameAndPort (..),
    Connection,
    Edge (..),
    EdgeOption (..),
    Drawing (..),
    IDState (..),
    SpecialQDiagram,
    SpecialBackend,
    SpecialNum,
    SgNamedNode,
    IngSyntaxGraph,
    LikeApplyFlavor (..),
    CaseOrMultiIfTag (..),
    Labeled (..),
    EmbedDirection (..),
    EmbedInfo (..),
    AnnotatedGraph,
    NodeInfo (..),
    Embedder (..),
    mkEmbedder,
    EmbedderSyntaxNode,
  )
where

import qualified Data.Graph.Inductive as ING
import qualified Data.IntMap as IM
import Data.Set (Set, empty)
import Data.Typeable (Typeable)
import Diagrams.Prelude (Any, IsName, Path, QDiagram, Renderable, V2)
import Diagrams.TwoD.Text (Text)

newtype NodeName = NodeName Int deriving (Typeable, Eq, Ord, Show)

instance IsName NodeName

data Named a = Named {naName :: NodeName, naVal :: a}
  deriving (Show, Eq, Ord, Functor)

type NamedIcon = Named Icon

data Labeled a = Labeled {laValue :: a, laLabel :: String}
  deriving (Show, Eq, Ord)

instance Functor Labeled where
  fmap f (Labeled value str) = Labeled (f value) str

instance Applicative Labeled where
  pure x = Labeled x ""
  (Labeled f fStr) <*> (Labeled x xStr) = Labeled (f x) (fStr <> xStr)

type IconInfo = IM.IntMap Icon

-- | A datatype that represents an icon.
-- The TextBoxIcon's data is the text that appears in the text box.
data Icon
  = TextBoxIcon String
  | MultiIfIcon
      Int -- Number of alternatives
  | LambdaIcon
      [String] -- Parameter labels
      (Maybe NodeName) -- Function body expression
      (Set NodeName) -- Nodes inside the lambda
  | CaseIcon Int
  | CaseResultIcon
  | BindTextBoxIcon String
  | NestedApply
      LikeApplyFlavor -- apply or compose
      (Maybe NodeName) -- The function for apply, or the argument for compose
      [Maybe NodeName] -- list of arguments or functions
  | NestedPApp
      (Labeled (Maybe NamedIcon)) -- Data constructor
      [Labeled (Maybe NamedIcon)] -- Arguments
  | NestedCaseIcon [Maybe NodeName]
  | NestedMultiIfIcon [Maybe NodeName]
  deriving (Show, Eq, Ord)

data LikeApplyFlavor = ApplyNodeFlavor | ComposeNodeFlavor
  deriving (Show, Eq, Ord)

data CaseOrMultiIfTag = CaseTag | MultiIfTag deriving (Show, Eq, Ord)

-- TODO The full edge does not need to be included, just the port.
data Embedder a = Embedder
  { -- | Set of embedded nodes
    emEmbedded :: Set (NodeName, Edge),
    emNode :: a
  }
  deriving (Show, Eq, Ord, Functor)

mkEmbedder :: a -> Embedder a
mkEmbedder = Embedder empty

type EmbedderSyntaxNode = Embedder SyntaxNode

type SgNamedNode = Named EmbedderSyntaxNode

-- TODO remove Ints from SyntaxNode data constructors.
data SyntaxNode
  = -- Function application, composition, and applying to a composition
    -- The list of nodes is unordered (replace with a map?)
    ApplyNode LikeApplyFlavor Int
  | PatternApplyNode String [Labeled (Maybe SgNamedNode)]
  | NameNode String -- Identifiers or symbols
  | BindNameNode String
  | LiteralNode String -- Literal values like the string "Hello World"
  | FunctionDefNode -- Function definition (ie. lambda expression)
      [String] -- Parameter labels
      (Set NodeName) -- Nodes inside the lambda
  | CaseResultNode -- TODO remove caseResultNode
  | CaseOrMultiIfNode CaseOrMultiIfTag Int
  deriving (Show, Eq, Ord)

newtype Port = Port Int deriving (Typeable, Eq, Ord, Show)

instance IsName Port

data NameAndPort = NameAndPort NodeName (Maybe Port) deriving (Show, Eq, Ord)

type Connection = (NameAndPort, NameAndPort)

-- TODO Consider removing EdgeOption since it's unused.
data EdgeOption = EdgeInPattern deriving (Show, Eq, Ord)

-- | An Edge has an name of the source icon, and its optional port number,
-- and the name of the destination icon, and its optional port number.
data Edge = Edge
  { edgeOptions :: [EdgeOption],
    edgeConnection :: Connection
  }
  deriving (Show, Eq, Ord)

-- | A drawing is a map from names to Icons, a list of edges,
-- and a map of names to subDrawings
data Drawing = Drawing [NamedIcon] [Edge] deriving (Show, Eq)

-- | IDState is an Abstract Data Type that is used as a state whose value is a
-- unique id.
newtype IDState = IDState Int deriving (Eq, Show)

type SpecialNum n =
  (Floating n, RealFrac n, RealFloat n, Typeable n, Show n, Enum n)

-- Note that SpecialBackend is a constraint synonym, not a type synonym.
type SpecialBackend b n =
  (SpecialNum n, Renderable (Path V2 n) b, Renderable (Text n) b)

type SpecialQDiagram b n = QDiagram b V2 n Any

type IngSyntaxGraph gr = gr SgNamedNode Edge

data EmbedDirection
  = EdEmbedFrom -- The tail
  | EdEmbedTo -- The head
  deriving (Show, Eq)

-- A Nothing eiEmbedDir means the edge is not embedded.
data EmbedInfo a = EmbedInfo {eiEmbedDir :: Maybe EmbedDirection, eiVal :: a}
  deriving (Show, Eq, Functor)

type AnnotatedGraph gr = gr (NodeInfo SgNamedNode) (EmbedInfo Edge)

data NodeInfo a = NodeInfo
  { niParent :: Maybe ING.Node,
    niVal :: a
  }
  deriving (Show, Eq, Functor, Ord)
