module Data.Schema exposing
    ( Schema
    , Role
    , Attribute
    , decode
    )

import Json.Decode as JD exposing (Decoder)
import Json.Decode.Pipeline exposing (required)
import Dict exposing (Dict)

type alias Schema = Dict String Role

merge : Schema -> Schema -> (Schema, Schema)
merge finish =
    Dict.foldl
        (\key role (fin, todo) ->
            case role.parent of
                Nothing -> (Dict.insert key role fin, todo)
                Just parent ->
                    case Dict.get parent fin of
                        Nothing -> (fin, Dict.insert key role todo)
                        Just par ->
                            (Dict.insert
                                key
                                { role
                                | attributes = Dict.union role.attributes par.attributes
                                }
                                fin
                            , todo
                            )
        )
        (finish, Dict.empty)

mergeAll : Schema -> Schema -> Decoder Schema
mergeAll finish todo =
    if Dict.isEmpty todo
    then JD.succeed finish
    else case merge finish todo of
        -- it is a single case of expression used to make use of the automatic tail recursion
        -- elimination build in the elm compiler
        (newFinish, newTodo) ->
            if newTodo == todo
            then JD.fail "cannot merge attributes of a child with its parent"
            else mergeAll newFinish newTodo

decodeDict : Decoder { a | name : String } -> Decoder (Dict String { a | name : String })
decodeDict decoder =
    JD.map
        (List.map
            (\({name} as entry) -> (name, entry))
            >> Dict.fromList
        )
    <| JD.list decoder

decode : Decoder Schema
decode =
    JD.andThen
        (mergeAll Dict.empty)
    <| decodeDict decodeRole

type alias Role =
    { name: String
    , abstract: Bool
    , parent: Maybe String
    , attributes: Dict String Attribute
    }

decodeRole : Decoder Role
decodeRole =
    JD.succeed Role
        |> required "name" JD.string
        |> required "abstract" JD.bool
        |> required "parent" (JD.nullable JD.string)
        |> required "attributes" (JD.dict decodeAttribute)

type alias Attribute =
    { type_: String
    , protected: Bool
    , history: Bool
    }

decodeAttribute : Decoder Attribute
decodeAttribute =
    JD.succeed Attribute
        |> required "type" JD.string
        |> required "protected" JD.bool
        |> required "history" JD.bool
