{-# LANGUAGE OverloadedStrings #-}

module Hydra.Sources.Tier4.Langs.Json.Decoding where

-- TODO: standardized Tier-4 imports
import           Prelude hiding ((++))
import qualified Data.List                 as L
import qualified Data.Map                  as M
import qualified Data.Set                  as S
import qualified Data.Maybe                as Y
import           Hydra.Dsl.Base            as Base
import qualified Hydra.Dsl.Core            as Core
import qualified Hydra.Dsl.Graph           as Graph
import qualified Hydra.Dsl.Lib.Equality    as Equality
import qualified Hydra.Dsl.Lib.Flows       as Flows
import qualified Hydra.Dsl.Lib.Io          as Io
import qualified Hydra.Dsl.Lib.Lists       as Lists
import qualified Hydra.Dsl.Lib.Literals    as Literals
import qualified Hydra.Dsl.Lib.Logic       as Logic
import qualified Hydra.Dsl.Lib.Maps        as Maps
import qualified Hydra.Dsl.Lib.Math        as Math
import qualified Hydra.Dsl.Lib.Optionals   as Optionals
import qualified Hydra.Dsl.Lib.Sets        as Sets
import           Hydra.Dsl.Lib.Strings     as Strings
import qualified Hydra.Dsl.Module          as Module
import qualified Hydra.Dsl.Terms           as Terms
import qualified Hydra.Dsl.Types           as Types
import           Hydra.Sources.Tier2.All

import qualified Hydra.Json as Json
import Hydra.Sources.Tier0.Json


jsonDecodingModule :: Module Kv
jsonDecodingModule = Module (Namespace "hydra/langs/json/decoding") elements [jsonModelModule, hydraCoreModule] [jsonModelModule] $
    Just "Decoding functions for JSON data"
  where
   elements = [
     Base.el decodeStringDef,
     Base.el decodeNumberDef,
     Base.el decodeBooleanDef,
     Base.el decodeArrayDef,
     Base.el decodeObjectDef,
     Base.el decodeFieldDef,
     Base.el decodeOptionalFieldDef]

jsonDecodingDefinition :: String -> Datum a -> Definition a
jsonDecodingDefinition label = definitionInModule jsonDecodingModule ("decode" <> label)

valueT = TypeVariable Json._Value

decodeStringDef :: Definition (Json.Value -> Flow s String)
decodeStringDef  = jsonDecodingDefinition "String" $
  function valueT (flowT sT stringT) $
  match Json._Value (Just $ Flows.fail @@ "expected a string") [
    Json._Value_string>>: Flows.pure]

decodeNumberDef :: Definition (Json.Value -> Flow s Double)
decodeNumberDef  = jsonDecodingDefinition "Number" $
  function valueT (flowT sT Types.bigfloat) $
  match Json._Value (Just $ Flows.fail @@ "expected a number") [
    Json._Value_number>>: Flows.pure]

decodeBooleanDef :: Definition (Json.Value -> Flow s Bool)
decodeBooleanDef  = jsonDecodingDefinition "Boolean" $
  function valueT (flowT sT booleanT) $
  match Json._Value (Just $ Flows.fail @@ "expected a boolean") [
    Json._Value_boolean>>: Flows.pure]

decodeArrayDef :: Definition ((Json.Value -> Flow s a) -> Json.Value -> Flow s [a])
decodeArrayDef  = jsonDecodingDefinition "Array" $
  function (functionT valueT (flowT sT aT)) (functionT valueT (flowT sT (listT aT))) $
  lambda "decodeElem" $ match Json._Value (Just $ Flows.fail @@ "expected an array") [
    Json._Value_array>>: Flows.mapList @@ (var "decodeElem")]

decodeObjectDef :: Definition (Json.Value -> Flow s (M.Map String Json.Value))
decodeObjectDef  = jsonDecodingDefinition "Object" $
  function valueT (flowT sT (mapT stringT valueT)) $
  match Json._Value (Just $ Flows.fail @@ "expected an object") [
    Json._Value_object>>: Flows.pure]

decodeFieldDef :: Definition ((Json.Value -> Flow s a) -> String -> (M.Map String Json.Value) -> Flow s a)
decodeFieldDef  = jsonDecodingDefinition "Field" $
  function (functionT valueT (flowT sT aT)) (functionT stringT (functionT (mapT stringT valueT) (flowT sT aT))) $
  lambda "decodeValue" $ lambda "name" $ lambda "m" $
    Flows.bind
      @@ (ref decodeOptionalFieldDef @@ var "decodeValue" @@ var "name" @@ var "m")
      @@ (matchOpt (Flows.fail @@ ("missing field: " ++ var "name")) Flows.pure)

decodeOptionalFieldDef :: Definition ((Json.Value -> Flow s a) -> String -> (M.Map String Json.Value) -> Flow s (Maybe a))
decodeOptionalFieldDef  = jsonDecodingDefinition "OptionalField" $
  function (functionT valueT (flowT sT aT)) (functionT stringT (functionT (mapT stringT valueT) (flowT sT (Types.optional aT)))) $
  lambda "decodeValue" $ lambda "name" $ lambda "m" $
    (matchOpt (Flows.pure @@ nothing) (lambda "v" (Flows.map @@ (lambda "x" (just $ var "x")) @@ (var "decodeValue" @@ var "v"))))
      @@ (Maps.lookup @@ var "name" @@ var "m")