module Editor exposing (..)

import Triple exposing (Triple)
import Panes exposing (Event)
import Editor.DateTime
import Editor.SingleLine
import Editor.MultiLine
import Editor.Number
import Network.Messages exposing (EditDeniedData, EditDeniedReason)
import Html exposing (Html, div)
import Html.Attributes exposing (class)
import Time exposing (Posix, Zone)
import Data exposing (Id)
import Triple exposing (triple)
import Task
import Process
import Html.Attributes exposing (id)
import Json.Decode exposing (Value)
import Json.Encode as JE
import Iso8601

doUpdate : (change -> model1 -> Triple model2 (Cmd msg) (List Event))
    -> (model1 -> model2)
    -> Triple model1 (Cmd msg) (Maybe change)
    -> Triple model2 (Cmd msg) (List Event)
doUpdate updater nothing data =
    case Triple.third data of
        Nothing -> Triple.mapThird (always []) data
            |> Triple.mapFirst nothing
        Just change ->
            Triple.first data
            |> updater change
            |> Triple.mapSecond
                (\x -> Cmd.batch [ x, Triple.second data ])

type EditorState
    = DateTime Editor.DateTime.Model
    | SingleLine Editor.SingleLine.Model
    | MultiLine Editor.MultiLine.Model
    | Number Editor.Number.Model

type EditorMsg
    = DateTimeMsg Editor.DateTime.Msg
    | SingleLineMsg Editor.SingleLine.Msg
    | MultiLineMsg Editor.MultiLine.Msg
    | NumberMsg Editor.Number.Msg
    | Send Snapshot

type EditorValue
    = ValuePosix Posix
    | ValueText String
    | ValueFloat Float

type EditorTarget
    = TargetInterviewTime Id
    | TargetInterviewLocation Id
    | TargetInterviewInterviewer Id
    | TargetPersonContact Id
    | TargetPersonName Id
    | TargetAttribute Id String

type alias Component =
    { target: EditorTarget
    , incr: Int
    , state: EditorState
    , unsafed: Maybe (Snapshot, EditorValue)
    , denied: Maybe (EditDeniedReason, String)
    }

{-| This will later identify the component and changement details.
    Only if the snapshot matches the data will be considered as settled and therefore can be saved.

    If the snapshot is missing a field it is highly possible that the same snapshot can be created
    at a later time but with a different data behind.
-}
type alias Snapshot =
    { target: EditorTarget
    , time: Posix
    , incr: Int
    }

initDateTime : EditorTarget -> Maybe String -> Zone -> Posix -> Posix -> Component
initDateTime target zoneName zone now time =
    { target = target
    , incr = 0
    , state = DateTime <| Editor.DateTime.init zoneName zone now time
    , unsafed = Nothing
    , denied = Nothing
    }

initSingleLine : EditorTarget -> String -> Component
initSingleLine target value =
    { target = target
    , incr = 0
    , state = SingleLine <| Editor.SingleLine.init value
    , unsafed = Nothing
    , denied = Nothing
    }

initMultiLine : EditorTarget -> String -> Component
initMultiLine target value =
    { target = target
    , incr = 0
    , state = MultiLine <| Editor.MultiLine.init value
    , unsafed = Nothing
    , denied = Nothing
    }

initNumber : EditorTarget -> Float -> Component
initNumber target value =
    { target = target
    , incr = 0
    , state = Number <| Editor.Number.init value
    , unsafed = Nothing
    , denied = Nothing
    }

view : Bool -> Posix -> Component -> Html EditorMsg
view readonly now component =
    div [ class "editor-component" ]
        [ case component.denied of
            Nothing -> Html.text ""
            Just (code, reason) ->
                div [ class "editor-component-denied" ]
                <| List.singleton
                <| Html.text
                <| case code of
                    Network.Messages.EditDeniedIdNotFound ->
                        "Id not found"
                    Network.Messages.EditDeniedSchemaNotFound ->
                        "specified schema not found"
                    Network.Messages.EditDeniedAttributeNotAvailable ->
                        "this attribute is not available for this element"
                    Network.Messages.EditDeniedAttributeForbidden ->
                        "this attribute is not allowed to be changed"
                    Network.Messages.EditDeniedInvalidValueType ->
                        "this specified value is not compatible if the schema"
                    Network.Messages.EditDeniedGeneric -> reason
        , case component.state of
            DateTime data -> Editor.DateTime.view readonly now data
                |> Html.map DateTimeMsg
            SingleLine data -> Editor.SingleLine.view readonly data
                |> Html.map SingleLineMsg
            MultiLine data -> Editor.MultiLine.view readonly data
                |> Html.map MultiLineMsg
            Number data -> Editor.Number.view readonly data
                |> Html.map NumberMsg
        ]

setDenied : EditDeniedData -> Component -> Component
setDenied denied component =
    { component
    | denied = Just (denied.reasonCode, denied.reason)
    }

toNetworkMsg : EditorTarget -> EditorValue -> Maybe Network.Messages.SendMessage
toNetworkMsg target value =
    case (target, value) of
        (TargetInterviewTime id, ValuePosix posix) ->
            Just <| Network.Messages.EditInterviewSend
                { id = id
                , delete = False
                , time = Just posix
                , location = Nothing
                , interviewer = Nothing
                }
        (TargetInterviewTime _, _) -> Nothing
        (TargetInterviewLocation id, ValueText v) ->
            Just <| Network.Messages.EditInterviewSend
                { id = id
                , delete = False
                , time = Nothing
                , location = Just v
                , interviewer = Nothing
                }
        (TargetInterviewLocation _, _) -> Nothing
        (TargetInterviewInterviewer id, ValueText v) ->
            Just <| Network.Messages.EditInterviewSend
                { id = id
                , delete = False
                , time = Nothing
                , location = Nothing
                , interviewer = Just v
                }
        (TargetInterviewInterviewer _, _) -> Nothing
        (TargetPersonContact id, ValueText v) ->
            Just <| Network.Messages.EditPersonSend
                { id = id
                , delete = False
                , contact = Just v
                , name = Nothing
                }
        (TargetPersonContact _, _) -> Nothing
        (TargetPersonName id, ValueText v) ->
            Just <| Network.Messages.EditPersonSend
                { id = id
                , delete = False
                , contact = Nothing
                , name = Just v
                }
        (TargetPersonName _, _) -> Nothing
        (TargetAttribute id key, ValuePosix v) ->
            Just <| Network.Messages.EditAttributeSend
            <| Network.Messages.EditAttributeData id key
            <| Iso8601.encode v
        (TargetAttribute id key, ValueText v) ->
            Just <| Network.Messages.EditAttributeSend
            <| Network.Messages.EditAttributeData id key
            <| JE.string v
        (TargetAttribute id key, ValueFloat v) ->
            Just <| Network.Messages.EditAttributeSend
            <| Network.Messages.EditAttributeData id key
            <| JE.float v

updateComp : (msg -> model -> Triple model (Cmd msg) (Maybe data))
    -> (model -> EditorState)
    -> (msg -> EditorMsg)
    -> (data -> EditorValue)
    -> Component -> Posix -> msg -> model
    -> Triple Component (Cmd EditorMsg) (List Event)
updateComp updater wrapState mapMsg mapData comp now sub model =
    updater sub model
    |> Triple.mapAll
        (\x -> 
            { comp 
            | state = wrapState x 
            , denied = Nothing
            }
        )
        (Cmd.map mapMsg)
        identity
    |> doUpdate
        (\data m -> m.incr + 1
            |> Snapshot m.target now
            |> \snapshot -> triple
                { m
                | unsafed = Just
                    ( snapshot
                    , mapData data
                    )
                , incr = snapshot.incr
                }
                ( Process.sleep (10 * 1000)
                    |> Task.perform
                        (\() -> Send snapshot)
                )
                []
        )
        identity

update : Posix -> EditorMsg -> Component -> Triple Component (Cmd EditorMsg) (List Event)
update now msg ({ state } as comp) =
    case (msg, state) of
        (DateTimeMsg sub, DateTime model) ->
            updateComp 
                Editor.DateTime.update
                DateTime DateTimeMsg ValuePosix
                comp now sub model
        (DateTimeMsg _, _) -> triple comp Cmd.none []
        (SingleLineMsg sub, SingleLine model) ->
            updateComp
                Editor.SingleLine.update
                SingleLine SingleLineMsg ValueText
                comp now sub model
        (SingleLineMsg _, _) -> triple comp Cmd.none []
        (MultiLineMsg sub, MultiLine model) ->
            updateComp
                Editor.MultiLine.update
                MultiLine MultiLineMsg ValueText
                comp now sub model
        (MultiLineMsg _, _) -> triple comp Cmd.none []
        (NumberMsg sub, Number model) ->
            updateComp
                Editor.Number.update
                Number NumberMsg ValueFloat
                comp now sub model
        (NumberMsg _, _) -> triple comp Cmd.none []
        (Send snapshot, _) ->
            case comp.unsafed of
               Nothing -> triple comp Cmd.none []
               Just (snap, _) ->
                    if snapshot == snap
                    then triple
                        { comp
                        | unsafed = Nothing
                        }
                        Cmd.none
                        <| save comp
                    else triple comp Cmd.none []
        -- _ -> triple comp Cmd.none []

save : Component -> List Event
save comp =
    case comp.unsafed of
        Nothing -> []
        Just (_, value) ->
            Maybe.withDefault []
            <| Maybe.map
                (\x -> [ Panes.Send x, Panes.SetUpdated ])
            <| toNetworkMsg comp.target value
