{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StrictData #-}

module Octane.Type.Property (Property(..)) where

import Data.Aeson ((.=))
import Data.Function ((&))

import qualified Control.DeepSeq as DeepSeq
import qualified Data.Aeson as Aeson
import qualified Data.Binary as Binary
import qualified GHC.Generics as Generics
import qualified Octane.Type.Boolean as Boolean
import qualified Octane.Type.Dictionary as Dictionary
import qualified Octane.Type.Float32 as Float32
import qualified Octane.Type.Int32 as Int32
import qualified Octane.Type.List as List
import qualified Octane.Type.Text as Text
import qualified Octane.Type.Word64 as Word64


-- | A metadata property. All properties have a size, but only some actually
-- use it. The value stored in the property can be an array, a boolean, and
-- so on.
data Property
    = ArrayProperty
        Word64.Word64
        (List.List (Dictionary.Dictionary Property))
    | BoolProperty
        Word64.Word64
        Boolean.Boolean
    | ByteProperty
        Word64.Word64
        (Text.Text, Text.Text)
    | FloatProperty
        Word64.Word64
        Float32.Float32
    | IntProperty
        Word64.Word64
        Int32.Int32
    | NameProperty
        Word64.Word64
        Text.Text
    | QWordProperty
        Word64.Word64
        Word64.Word64
    | StrProperty
        Word64.Word64
        Text.Text
    deriving (Eq, Generics.Generic, Show)

-- | Stored with the size first, then the value.
instance Binary.Binary Property where
    get = do
        kind <- Binary.get
        case kind of
            _ | kind == arrayProperty -> do
                size <- Binary.get
                value <- Binary.get
                value & ArrayProperty size & pure

            _ | kind == boolProperty -> do
                size <- Binary.get
                value <- Binary.get
                value & BoolProperty size & pure

            _ | kind == byteProperty -> do
                size <- Binary.get
                key <- Binary.get
                if key == "OnlinePlatform_Steam"
                    then ("OnlinePlatform", key) & ByteProperty size & pure
                    else do
                        value <- Binary.get
                        (key, value) & ByteProperty size & pure

            _ | kind == floatProperty -> do
                size <- Binary.get
                value <- case Word64.unpack size of
                    4 -> Binary.get
                    x -> fail ("unknown FloatProperty size " ++ show x)
                value & FloatProperty size & pure

            _ | kind == intProperty -> do
                size <- Binary.get
                value <- case Word64.unpack size of
                    4 -> Binary.get
                    x -> fail ("unknown IntProperty size " ++ show x)
                value & IntProperty size & pure

            _ | kind == nameProperty -> do
                size <- Binary.get
                value <- Binary.get
                value & NameProperty size & pure

            _ | kind == qWordProperty -> do
                size <- Binary.get
                value <- case Word64.unpack size of
                    8 -> Binary.get
                    x -> fail ("unknown QWordProperty size " ++ show x)
                value & QWordProperty size & pure

            _ | kind == strProperty -> do
                size <- Binary.get
                value <- Binary.get
                value & StrProperty size & pure

            _ -> fail ("unknown property type " ++ show (Text.unpack kind))

    put property =
        case property of
            ArrayProperty size value -> do
                Binary.put arrayProperty
                Binary.put size
                Binary.put value

            BoolProperty size value -> do
                Binary.put boolProperty
                Binary.put size
                Binary.put value

            ByteProperty size (key, value) -> do
                Binary.put byteProperty
                Binary.put size
                Binary.put key
                Binary.put value

            FloatProperty size value -> do
                Binary.put floatProperty
                Binary.put size
                Binary.put value

            IntProperty size value -> do
                Binary.put intProperty
                Binary.put size
                Binary.put value

            NameProperty size value -> do
                Binary.put nameProperty
                Binary.put size
                Binary.put value

            QWordProperty size value -> do
                Binary.put qWordProperty
                Binary.put size
                Binary.put value

            StrProperty size value -> do
                Binary.put strProperty
                Binary.put size
                Binary.put value

instance DeepSeq.NFData Property where

instance Aeson.ToJSON Property where
    toJSON property = case property of
        ArrayProperty size x -> Aeson.object
            [ "Type" .= ("Array" :: Text.Text)
            , "Size" .= size
            , "Value" .= x
            ]
        BoolProperty size x -> Aeson.object
            [ "Type" .= ("Bool" :: Text.Text)
            , "Size" .= size
            , "Value" .= x
            ]
        ByteProperty size x -> Aeson.object
            [ "Type" .= ("Byte" :: Text.Text)
            , "Size" .= size
            , "Value" .= x
            ]
        FloatProperty size x -> Aeson.object
            [ "Type" .= ("Float" :: Text.Text)
            , "Size" .= size
            , "Value" .= x
            ]
        IntProperty size x -> Aeson.object
            [ "Type" .= ("Int" :: Text.Text)
            , "Size" .= size
            , "Value" .= x
            ]
        NameProperty size x -> Aeson.object
            [ "Type" .= ("Name" :: Text.Text)
            , "Size" .= size
            , "Value" .= x
            ]
        QWordProperty size x -> Aeson.object
            [ "Type" .= ("QWord" :: Text.Text)
            , "Size" .= size
            , "Value" .= x
            ]
        StrProperty size x -> Aeson.object
            [ "Type" .= ("Str" :: Text.Text)
            , "Size" .= size
            , "Value" .= x
            ]


arrayProperty :: Text.Text
arrayProperty = "ArrayProperty"


boolProperty :: Text.Text
boolProperty = "BoolProperty"


byteProperty :: Text.Text
byteProperty = "ByteProperty"


floatProperty :: Text.Text
floatProperty = "FloatProperty"


intProperty :: Text.Text
intProperty = "IntProperty"


nameProperty :: Text.Text
nameProperty = "NameProperty"


qWordProperty :: Text.Text
qWordProperty = "QWordProperty"


strProperty :: Text.Text
strProperty = "StrProperty"