{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module Data.VRML.Types where

import GHC.Generics
import Data.Int
import Data.String

data VRML = VRML
  { version :: String
  , statements :: [Statement]
  }
  deriving (Generic,Show,Eq)

data Statement
  = StNode NodeStatement
  | StProto ProtoStatement
  | StRoute Route
  deriving (Generic,Eq)

class NodeLike a where
  node :: NodeTypeId -> [NodeBodyElement] -> a

instance NodeLike Statement where
  node i b = StNode (NodeStatement (Node i b))

instance NodeLike NodeStatement where
  node i b = NodeStatement (Node i b)

instance NodeLike Node where
  node i b = Node i b

instance NodeLike FieldValue where
  node i b = Snode (Just (NodeStatement (Node i b)))

instance Show Statement where
  show (StNode (NodeStatement (Node i b))) = "node " ++ show i ++ " " ++ show b
  show (StNode s) = "StNode (" ++ show s ++ ")"
  show (StProto s) = "StProto (" ++ show s ++ ")"
  show (StRoute s) = "StRoute (" ++ show s ++ ")"

data NodeStatement
  = NodeStatement Node
  | DEF NodeNameId Node
  | USE NodeNameId
  deriving (Generic,Show,Eq)

data ProtoStatement
  = Proto NodeTypeId [Interface] [ProtoStatement] Node [Statement]
  | ExternProto NodeTypeId [ExternInterface] URLList
  deriving (Generic,Show,Eq)

data RestrictedInterface
  = RestrictedInterfaceEventIn FieldType EventInId
  | RestrictedInterfaceEventOut FieldType EventOutId
  | RestrictedInterfaceField FieldType FieldId FieldValue
  deriving (Generic,Show,Eq)

data Interface
  = InterfaceEventIn FieldType EventInId
  | InterfaceEventOut FieldType EventOutId
  | InterfaceField FieldType FieldId FieldValue
  | InterfaceExposedField FieldType FieldId FieldValue
  deriving (Generic,Show,Eq)

data ExternInterface
  = ExternInterfaceEventIn FieldType EventInId
  | ExternInterfaceEventOut FieldType EventOutId
  | ExternInterfaceField FieldType FieldId
  | ExternInterfaceExposedField FieldType FieldId
  deriving (Generic,Show,Eq)

data Route
  = Route NodeNameId EventOutId NodeNameId EventInId
  deriving (Generic,Show,Eq)

newtype URLList = URLList [String]
  deriving (Generic,Show,Eq)

data Node
  = Node NodeTypeId [NodeBodyElement]
  | Script [ScriptBodyElement]
  deriving (Generic,Show,Eq)

data ScriptBodyElement
  = SBNode NodeBodyElement
  | SBRestrictedInterface RestrictedInterface
  | SBEventIn FieldType EventInId EventInId
  | SBEventOut FieldType EventOutId EventOutId
  | SBFieldId FieldType FieldId FieldId
  deriving (Generic,Show,Eq)

data NodeBodyElement
  = FV FieldId FieldValue
  | NBFieldId FieldId FieldId
  | NBEventIn EventInId EventInId
  | NBEventOut EventOutId EventOutId
  | NBRoute Route
  | NBProto ProtoStatement
  deriving (Generic,Show,Eq)

newtype NodeNameId = NodeNameId String
  deriving (Generic,Show,Eq)

newtype NodeTypeId = NodeTypeId String
  deriving (Generic,Eq)

newtype FieldId = FieldId String
  deriving (Generic,Eq)

newtype EventInId = EventInId String
  deriving (Generic,Show,Eq)

newtype EventOutId = EventOutId String
  deriving (Generic,Show,Eq)

data FieldType
  = MFBool
  | MFColor
  | MFFloat
  | MFString
  | MFTime
  | MFVec2f
  | MFVec3f
  | MFNode
  | MFRotation
  | MFInt32
  | SFBool
  | SFColor
  | SFFloat
  | SFImage
  | SFInt32
  | SFNode
  | SFRotation
  | SFString
  | SFTime
  | SFVec2f
  | SFVec3f
  deriving (Generic,Show,Eq)

newtype Color = Color (Float,Float,Float) deriving (Generic,Show,Eq)
newtype Time = Time Double deriving (Generic,Show,Eq)

data FieldValue
  = Sbool Bool
  | Scolor Color
  | Sfloat Float
  | Simage [Int32]
  | Sint32 Int32
  | Snode (Maybe NodeStatement)
  | Srotation (Float,Float,Float,Float)
  | Sstring String
  | Stime Time
  | Svec2f (Float,Float)
  | Svec3f (Float,Float,Float)
  | Mbool [Bool]
  | Mcolor [Color]
  | Mfloat [Float]
  | Mint32 [Int32]
  | Mnode [NodeStatement]
  | Mrotation [(Float,Float,Float,Float)]
  | Mstring [String]
  | Mtime [Time]
  | Mvec2f [(Float,Float)]
  | Mvec3f [(Float,Float,Float)]
  deriving (Generic,Show,Eq)

instance IsString FieldValue where
  fromString s = Sstring s

instance IsString NodeNameId where
  fromString s = NodeNameId s

instance IsString NodeTypeId where
  fromString s = NodeTypeId s

instance IsString FieldId where
  fromString s = FieldId s

instance IsString EventInId where
  fromString s = EventInId s

instance IsString EventOutId where
  fromString s = EventOutId s

instance Show NodeTypeId where
  show (NodeTypeId s) = show s

instance Show FieldId where
  show (FieldId s) = show s


instance Semigroup Node where
  (<>) a b =
    let (Node (NodeTypeId xname) xbody) = a
        (Node (NodeTypeId yname) ybody) = b
    in Node (NodeTypeId (xname ++ yname)) (xbody++ybody)

instance Monoid Node where
  mempty = Node "" []

class ToNode a where
  toNode :: NodeLike b => a -> b
  default toNode :: (Generic a, ToNode' (Rep a), NodeLike b) => a -> b
  toNode a =
    let (Node name body) = toNode' (from a)
    in node name body

instance ToNode Bool where
  toNode a = node "" [(FV "" (Sbool a))]

instance ToNode Color where
  toNode a = node "" [(FV "" (Scolor a))]

instance ToNode Float where
  toNode a = node "" [(FV "" (Sfloat a))]

instance ToNode Int32 where
  toNode a = node "" [(FV "" (Sint32 a))]

instance ToNode Node where
  toNode a = node "" [(FV "" (Snode (Just (NodeStatement a))))]

instance ToNode (Float,Float,Float,Float) where
  toNode a = node "" [(FV "" (Srotation a))]

instance ToNode String where
  toNode a = node "" [(FV "" (Sstring a))]

instance ToNode Time where
  toNode a = node "" [(FV "" (Stime a))]

instance ToNode (Float,Float) where
  toNode a = node "" [(FV "" (Svec2f a))]

instance ToNode (Float,Float,Float) where
  toNode a = node "" [(FV "" (Svec3f a))]

instance ToNode [Bool] where
  toNode a = node "" [(FV "" (Mbool a))]

instance ToNode [Color] where
  toNode a = node "" [(FV "" (Mcolor a))]

instance ToNode [Float] where
  toNode a = node "" [(FV "" (Mfloat a))]

instance ToNode [Int32] where
  toNode a = node "" [(FV "" (Mint32 a))]

instance ToNode [Node] where
  toNode a = node "" [(FV "" (Mnode (map NodeStatement a)))]

instance ToNode [(Float,Float,Float,Float)] where
  toNode a = node "" [(FV "" (Mrotation a))]

instance ToNode [Time] where
  toNode a = node "" [(FV "" (Mtime a))]

instance ToNode [String] where
  toNode a = node "" [(FV "" (Mstring a))]

instance ToNode [(Float,Float)] where
  toNode a = node "" [(FV "" (Mvec2f a))]

instance ToNode [(Float,Float,Float)] where
  toNode a = node "" [(FV "" (Mvec3f a))]

instance ToNode a => ToNode (Maybe a) where
  toNode (Just a) = toNode a
  toNode Nothing = node "" []

class ToNode' f where
  toNode' :: f a -> Node

instance ToNode' U1 where
  toNode' U1 = mempty

instance (ToNode' f, ToNode' g) => ToNode' (f :+: g) where
  toNode' (L1 x) = toNode' x
  toNode' (R1 x) = toNode' x

instance (ToNode' f, ToNode' g) => ToNode' (f :*: g) where
  toNode' (x :*: y) = toNode' x <> toNode' y

instance (ToNode c) => ToNode' (K1 i c) where
  toNode' (K1 x) = toNode x

instance (Selector c, ToNode' f) => ToNode' (M1 S c f) where
  toNode' a@(M1 x) =
    case toNode' x of
      (Node "" [(FV _ v)]) -> Node "" [(FV (FieldId (selName a)) v)]
      v@(Node x _) -> Node "" [(FV (FieldId (selName a)) (Snode (Just (NodeStatement v))))]

instance (Constructor c, ToNode' f) => ToNode' (M1 C c f) where
  toNode' a@(M1 x) = Node (NodeTypeId (conName a)) [] <> toNode' x

instance (ToNode' f) => ToNode' (M1 D c f) where
  toNode' (M1 x) = toNode' x