module Main exposing (..)

import Panes
import Lazy
import Panes.Controller exposing (Pane(..))
import Panes.Login
import Model exposing (Model, openPane)
import Triple
import Html exposing (Html, text)
import Html.Attributes as HA
import Layout.MainWindow
import Browser
import Time
import TimeZone
import Task
import Network
import Network.Messages exposing (ReceiveMessage)
import Triple exposing (Triple)
import PaneMeta
import Panes.Navigation
import Panes.Modals
import Panes.Modal.Error
import History
import Triple exposing (triple)
import Http
import Browser.Navigation exposing (Key)
import Url exposing (Url)
import Tools
import Dict
import Notification
import Notification exposing (Notification)
import Model exposing (LoginPane(..))

main : Program () Model Msg
main =
    Browser.application
        { init = init
        , view = \model ->
            { title = List.head model.paneMeta.history
                |> Maybe.withDefault History.Home
                |> History.getTitle
                |> (\x -> x ++ " - Dacryptero")
            , body =
                [ Html.node "link"
                    [ HA.attribute "rel" "stylesheet"
                    , HA.attribute "property" "stylesheet"
                    , HA.attribute "href" "css/root.css"
                    ] []
                , view model
                ]
            }
        , update = update
        , subscriptions = subscriptions
        , onUrlRequest = always None << Debug.log "url-request"
        , onUrlChange = ChangeUrl
        }

type Msg
    = None
    | NetworkSocket (Result String ReceiveMessage)
    | SetZone (Maybe String) Time.Zone
    | SetTime Time.Posix
    | SetUploadProgress String Http.Progress
    | ChangeUrl Url
    | WrapPane Panes.Controller.Msg
    | WrapLogin Panes.Login.Msg
    | WrapNav Panes.Navigation.Msg
    | WrapModal Panes.Modals.Msg
    | WrapNotification Notification.Msg

applyEvent : Panes.Event -> Model -> (Model, Cmd Msg)
applyEvent event model =
    case event of
        Panes.Open history ->
            let
                newUrl : Url
                newUrl = Tools.setQueryToUrl
                    (History.encodeHistory history)
                    model.url
            in
                openPane history model
                |> Triple.mapSecond (Cmd.map WrapPane)
                |> Tools.insertCmd
                    ( if newUrl == model.url
                        then Cmd.none
                        else Browser.Navigation.pushUrl model.key
                            <| Url.toString newUrl
                    )
                |> handleEvents
        Panes.RemoveHistory history ->
            Tuple.pair
                { model
                | paneMeta = model.paneMeta |> \meta ->
                    { meta
                    | history = meta.history
                        |> List.filter
                            ((/=) history)
                    }
                }
                Cmd.none
        Panes.OpenFile id ->
            Panes.Modals.initFileInfo model.paneMeta id
            |> Panes.Modals.add model.modal
            |> Triple.mapAll
                (\x -> { model | modal = x })
                (Cmd.map WrapModal)
                identity
            |> handleEvents
        Panes.OpenAddPerson id db ->
            Panes.Modals.initAddPerson model.paneMeta id db
            |> Panes.Modals.add model.modal
            |> Triple.mapAll
                (\x -> { model | modal = x })
                (Cmd.map WrapModal)
                identity
            |> handleEvents
        Panes.OpenAddDatabase ->
            Panes.Modals.initAddDatabase model.paneMeta
            |> Panes.Modals.add model.modal
            |> Triple.mapAll
                (\x -> { model | modal = x })
                (Cmd.map WrapModal)
                identity
            |> handleEvents
        Panes.OpenDatabaseAccess name ->
            Panes.Modals.initManageDatabaseAccess model.paneMeta name
            |> Panes.Modals.add model.modal
            |> Triple.mapAll
                (\x -> { model | modal = x })
                (Cmd.map WrapModal)
                identity
            |> handleEvents
        Panes.Send msg ->
            ( model
            , Network.wsSend msg
            )
        Panes.SetUpdated ->
            Notification.addOrUpdate
                { id = Nothing
                , img = Just "/img/svgrepo/essential-set-2/save-135299.svg"
                , title = "Data saved"
                , description = "at " ++ Tools.viewDateTime model.paneMeta.zone model.paneMeta.now
                , progress = Notification.NoProgress
                , close = Just <| Tools.addMs 5000 model.paneMeta.now
                -- , close = Nothing
                }
                model.notification
            |> Tuple.mapBoth
                (\new -> { model | notification = new })
                (Cmd.map WrapNotification)
        Panes.CloseModal _ -> (model, Cmd.none)
        Panes.EditSearchHistoryQuery query ->
            let
                newModel : Model
                newModel =
                    { model
                    | paneMeta = model.paneMeta |> \meta ->
                        { meta
                        | history =
                            case meta.history of
                                History.Search _ _ :: hs ->
                                    History.Search Nothing query :: hs
                                _ -> meta.history
                        }
                    }
                
                newUrl : Url
                newUrl = Tools.setQueryToUrl
                    (Maybe.withDefault Dict.empty
                        <| Maybe.map History.encodeHistory
                        <| List.head
                        <| newModel.paneMeta.history
                    )
                    model.url
            in Tuple.pair newModel
                <| if newUrl == model.url
                    then Cmd.none
                    else Browser.Navigation.replaceUrl model.key
                        <| Url.toString newUrl
        Panes.AttributeHistory data ->
            Panes.Modals.initAttributeHistory model.paneMeta data
            |> Panes.Modals.add model.modal
            |> Triple.mapAll
                (\x -> { model | modal = x })
                (Cmd.map WrapModal)
                identity
            |> handleEvents
        Panes.Upload person parent ->
            Panes.Modals.initUpload model.paneMeta person parent
            |> Panes.Modals.add model.modal
            |> Triple.mapAll
                (\x -> { model | modal = x })
                (Cmd.map WrapModal)
                identity
            |> handleEvents
        Panes.AddUpload upload ->
            Tuple.pair
                { model 
                | paneMeta = model.paneMeta |> \meta -> 
                    { meta | upload = meta.upload ++ [ upload ]}
                }
                Cmd.none
        Panes.RemoveUpload tag ->
            Tuple.pair
                { model 
                | paneMeta = model.paneMeta |> \meta -> 
                    { meta 
                    | upload = List.filter ((/=) tag << .tag) meta.upload
                    }
                }
                Cmd.none
        Panes.AddHttpError error ->
            Panes.Modal.Error.initHttpError model.paneMeta error
            |> Triple.mapAll
                Panes.Modals.Error
                (Cmd.map Panes.Modals.ErrorMsg)
                identity
            |> Panes.Modals.add model.modal
            |> Triple.mapAll
                (\x -> { model | modal = x })
                (Cmd.map WrapModal)
                identity
            |> handleEvents
        Panes.SetNotification notification ->
            Notification.addOrUpdate notification
                model.notification
            |> Tuple.mapBoth
                (\new -> { model | notification = new })
                (Cmd.map WrapNotification)
        Panes.EditNotifcation edit -> Tuple.pair
            { model
            | notification = Notification.edit edit model.notification
            }
            Cmd.none
        Panes.EnterRegisterAccount dbName key -> Tuple.pair
            { model
            | login = LoginPaneRegister
                { database = dbName
                , key = key
                , username = ""
                , password1 = ""
                , password2 = ""
                }
            }
            Cmd.none

init : () -> Url -> Key -> (Model, Cmd Msg)
init () url key =
    let
        history : History.History
        history = Maybe.withDefault History.Home <| History.parseHistory url

        extraEvents : List Panes.Event
        extraEvents =
            if history == History.Home
            then []
            else [ Panes.Send <| Network.Messages.InfoRequest ]
    in
        (Model
            key url
            -- notification
            Notification.init
            -- login
            LoginPaneEmpty
            -- paneMeta
            (PaneMeta.init history)
            -- modal
            Panes.Modals.init
        , Cmd.batch
            [ Network.wsConnect "http://localhost:8015"
            , Task.perform identity
                <| Task.onError 
                    (always
                        <| Task.map (SetZone Nothing) 
                        <| Time.here
                    )
                <| Task.map
                    (\(name, zone) -> SetZone (Just name) zone)
                <| TimeZone.getZone
            , Task.perform SetTime Time.now
            ]
        , []
        )
        |> Model.initWithPane WrapPane
            (Triple.mapFirst Just
                <| Triple.mapThird
                    (List.append extraEvents)
                <| Model.initPane
                    history
                <| PaneMeta.init history
                -- <| Panes.Controller.initHome PaneMeta.init
            )
        |> Model.initWithPane WrapNav
            (Panes.Navigation.init (PaneMeta.init history) ())
        |> handleEvents

viewLogin : Model -> Html Msg
viewLogin model =
    Html.map WrapLogin
        <| Panes.Login.view
        <| Panes.PaneData model.paneMeta model.login

view : Model -> Html Msg
view model =
    if model.login /= LoginPaneEmpty
    then viewLogin model
    else case model.pane of
        Nothing -> viewLogin model
        Just pane ->
            Layout.MainWindow.view
                { title = "Dacryptero"
                , sideNav =
                    Html.map WrapNav
                    <| Panes.Navigation.view
                    <| Panes.PaneData model.paneMeta model.nav
                , pane = 
                    Html.map WrapPane
                    <| Panes.Controller.view
                    <| Panes.PaneData
                        model.paneMeta
                        pane
                , progress = []
                    -- List.map
                    --     (\ { name, progress } ->
                    --         Layout.MainWindow.Progress name progress
                    --     )
                    --     model.paneMeta.upload
                , zone = model.paneMeta.zone
                , now = model.paneMeta.now
                , modal = Html.map WrapModal
                    <| Panes.Modals.view 
                    <| Panes.PaneData model.paneMeta model.modal
                , taskbar = Html.map WrapModal
                    <| Panes.Modals.viewTaskBar
                    <| Panes.PaneData model.paneMeta model.modal
                , notification = model.notification
                , wrapNotification = WrapNotification
                }

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        None -> (model, Cmd.none)
        NetworkSocket (Err error) ->
            let
                d_ = Debug.log "Main:update:NetworkSocket:Err" error
            in handleEvents
                <| Triple.mapAll
                    (\x -> { model | modal = x})
                    (Cmd.map WrapModal)
                    identity
                <| Panes.Modals.add model.modal
                <| Triple.mapAll
                    Panes.Modals.Error
                    (Cmd.map Panes.Modals.ErrorMsg)
                    identity
                <| Panes.Modal.Error.initParsingError model.paneMeta error
        NetworkSocket (Ok event) ->
            let
                d_ = Debug.log "Main:update:NetworkSocket:Ok" event
                (model2, cmd1) =
                    case model.pane of
                        Nothing -> (model, Cmd.none)
                        Just pane -> 
                            doEventMsg 
                                Panes.Controller.apply
                                (\x m -> { m | pane = Just x })
                                WrapPane
                                event pane model
                (model3, cmd2) =
                    doEventMsg
                        Panes.Navigation.apply
                        (\x m -> { m | nav = x })
                        WrapNav
                        event model.nav model2
                (model4, cmd3) =
                    doEventMsg
                        Panes.Modals.apply
                        (\x m -> { m | modal = x })
                        WrapModal
                        event model.modal model3
                (model5, cmd4) =
                    doEventMsg
                        Panes.Login.apply
                        (\x m -> { m | login = x })
                        WrapLogin
                        event model.login model4
            in Tuple.pair
                { model5
                | paneMeta = PaneMeta.apply event model5.paneMeta
                }
                (Cmd.batch [ cmd1, cmd2, cmd3, cmd4 ])
        SetZone name zone -> Tuple.pair
            { model
            | paneMeta = model.paneMeta |> \meta ->
                { meta | zone = zone, zoneName = name }
            }
            Cmd.none
        SetTime time -> Tuple.pair
            { model
            | paneMeta = model.paneMeta |> \meta ->
                { meta | now = time }
            , notification = Notification.updateTime time
                model.notification
            }
            Cmd.none
        SetUploadProgress tag progress -> Tuple.pair
            { model
            | paneMeta = model.paneMeta |> \meta ->
                { meta 
                | upload = List.map
                    (\upload ->
                        if upload.tag == tag
                        then 
                            { upload
                            | progress = case progress of
                                Http.Sending p -> Just <| Http.fractionSent p
                                Http.Receiving p ->
                                    Maybe.map
                                        (\size -> Http.fractionSent
                                            { sent = p.received, size = size }
                                        )
                                        p.size
                            }
                        else upload
                    )
                    meta.upload
                }
            , notification = 
                (\(prog, img) -> Notification.edit
                    { id = "upload-" ++ tag
                    , img = Just <| Just img
                    , title = Nothing
                    , description = Nothing
                    , progress = Just
                        <| Maybe.withDefault Notification.UndefinedProgress
                        <| Maybe.map Notification.Progress
                        <| prog
                    , close = Nothing
                    }
                    model.notification
                )
                <| case progress of
                    Http.Sending p -> 
                        ( Just <| Http.fractionSent p
                        , "/img/svgrepo/essential-set-2/cloud-computing-36328.svg"
                        )
                    Http.Receiving p ->
                        ( Maybe.map
                            (\size -> Http.fractionSent
                                { sent = p.received, size = size }
                            )
                            p.size
                        , "/img/svgrepo/essential-set-2/cloud-computing-45584.svg"
                        )
            }
            Cmd.none
        ChangeUrl newUrl ->
            let
                newHistory : History.History
                newHistory = History.parseHistory newUrl |> Maybe.withDefault History.Home

                oldHistory : History.History
                oldHistory = List.head model.paneMeta.history |> Maybe.withDefault History.Home

            in
                if newHistory == oldHistory
                then ({ model | url = newUrl }, Cmd.none)
                else handleEvents 
                    <| Triple.triple 
                        { model | url = newUrl }
                        Cmd.none
                        [ Panes.Open newHistory ]
        WrapPane sub ->
            case model.pane of
                Nothing -> (model, Cmd.none)
                Just pane -> 
                    doEventMsg
                        Panes.Controller.update
                        (\x m -> { m | pane = Just x })
                        WrapPane
                        sub pane model
        WrapLogin sub ->
            doEventMsg
                Panes.Login.update
                (\x m -> { m | login = x })
                WrapLogin
                sub model.login model
        WrapNav sub ->
            doEventMsg
                Panes.Navigation.update
                (\x m -> { m | nav = x })
                WrapNav
                sub model.nav model
        WrapModal sub ->
            doEventMsg
                Panes.Modals.update
                (\x m -> { m | modal = x })
                WrapModal
                sub model.modal model
        WrapNotification sub ->
            Notification.update sub model.notification
            |> Tuple.mapBoth
                (\new -> { model | notification = new })
                (Cmd.map WrapNotification)

handleEvents : Triple Model (Cmd Msg) (List Panes.Event) -> (Model, Cmd Msg)
handleEvents data =
    Tuple.mapSecond
        (\x -> Cmd.batch <| Triple.second data :: x)
    <| List.foldl
        (\event (m, cs) ->
            Tuple.mapSecond
                (\c -> c :: cs)
            <| applyEvent event m
        )
        ( Triple.first data
        , []
        )
    <| Triple.third data

doEventMsg : (a -> Panes.PaneData b -> Triple b (Cmd msg) (List Panes.Event))
    -> (b -> Model -> Model)
    -> (msg -> Msg)
    -> a
    -> b
    -> Model
    -> (Model, Cmd Msg)
doEventMsg handler filler tagger msg data model =
    Panes.PaneData model.paneMeta data
    |> handler msg
    |> Triple.mapAll
        (\x -> filler x model)
        (Cmd.map tagger)
        identity
    |> handleEvents

subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ case model.pane of
            Nothing -> Sub.none
            Just pane ->
                Sub.map WrapPane
                <| Panes.Controller.subscription
                <| Panes.PaneData model.paneMeta pane
        , Sub.map WrapLogin
            <| Panes.Login.subscription
            <| Panes.PaneData model.paneMeta model.login
        , Sub.map WrapNav
            <| Panes.Navigation.subscription
            <| Panes.PaneData model.paneMeta model.nav
        , Sub.map WrapModal
            <| Panes.Modals.subscription
            <| Panes.PaneData model.paneMeta model.modal
        , Network.wsReceive NetworkSocket
        , Time.every 1000 SetTime
        , Sub.batch
            <| List.map (\{ tag } -> Http.track tag <| SetUploadProgress tag)
            <| model.paneMeta.upload
        ]
