module Panes.Search.Result exposing (..)

import Dict exposing (Dict)
import Data.Search exposing (SearchResponse, SearchResult, SortConfig, SortInfo)
import Time exposing (Posix)
import Html exposing (Html, div)
import Html.Attributes as HA exposing (class)
import Html.Lazy as HL
import Html.Keyed as HK
import Html.Events as HE
import Data.Search exposing (SortInfoEntry(..))
import Data.Search exposing (SortDirection(..))

flip : Order -> Order
flip order =
    case order of
       LT -> GT
       EQ -> EQ
       GT -> LT
    
condFlip : SortDirection -> Order -> Order
condFlip dir =
    case dir of
        Ascending -> identity
        Descending -> flip

compareEntry : SortDirection -> SortInfoEntry -> SortInfoEntry -> Order
compareEntry d a b =
    case (a, b) of
        (SortInfoNull, SortInfoNull) -> EQ
        (SortInfoNull, _) -> GT
        (_ , SortInfoNull) -> LT
        (SortInfoDouble l, SortInfoDouble r) -> condFlip d <| compare l r
        (SortInfoDouble _, _) -> condFlip d LT
        (_, SortInfoDouble _) -> condFlip d GT
        (SortInfoString l, SortInfoString r) -> condFlip d <| compare l r

compareResult : SortConfig -> SortInfo -> SortInfo -> Order
compareResult conf infoA infoB =
    case (conf, infoA, infoB) of
        (c::cs, a::as_, b::bs) ->
            let
                res : Order
                res = compareEntry c.direction a b
            in
                if res == EQ
                then compareResult cs as_ bs
                else res
        _ -> EQ

type alias Result =
    { query: String
    , incr: Int
    , id: String
    , isLast: Bool
    , isTrimmed: Bool
    , totalResultCount: Int
    , elapsed: Float
    , sortConfig: SortConfig
    , results: List SearchResult
    , page: Int
    }

newId : Int -> Posix -> String
newId incr time = 
    String.fromInt (Time.posixToMillis time) ++ "-"
        ++ String.fromInt incr

nextOrInit : String -> Posix -> Maybe Result -> Result
nextOrInit query time result =
    case result of
        Nothing -> init query time
        Just res -> next query time res

init : String -> Posix -> Result
init query time =
    { query = query
    , incr = 0
    , id = newId 0 time
    , isLast = False
    , isTrimmed = False
    , totalResultCount = 0
    , elapsed = 0
    , sortConfig = []
    , results = []
    , page = 0
    }

next : String -> Posix -> Result -> Result
next query time { incr } =
    { query = query
    , incr = incr + 1
    , id = newId (incr + 1) time
    , isLast = False
    , isTrimmed = False
    , totalResultCount = 0
    , elapsed = 0
    , sortConfig = []
    , results = []
    , page = 0
    }

add : SearchResponse -> Result -> Result
add search result =
    if search.uiId == result.id
    then
        let
            execSort : List SearchResult -> List SearchResult
            execSort =
                if List.isEmpty search.sorting
                then identity
                else List.sortWith
                    (\a b -> compareResult search.sorting a.sorting b.sorting
                    )

            newList = List.append result.results search.result
                |> execSort
        in
            { result
            | isLast = result.isLast || search.isLast
            , isTrimmed = result.isTrimmed || List.length newList > 500
            , totalResultCount = result.totalResultCount + List.length search.result
            , elapsed = max result.elapsed search.elapsedSeconds
            , sortConfig = search.sorting
            , results = List.take 500 newList
            }
    else result

setPage : Int -> Result -> Result
setPage page result =
    { result
    | page = clamp 0
        ((List.length result.results - 1) // 10)
        page
    }

view : (SearchResponse -> Html msg) -> (Int -> msg) -> Result -> Html msg
view viewElement setPageEvent ({ page, results } as result) =
    let
        maxId : Int
        maxId = Debug.log "maxId" <| List.length results - 1

    in div [ class "pane-search-result" ]
        [ div [ class "pane-search-result-stats" ]
            [ Html.text
                <| String.fromInt result.totalResultCount ++ " results in "
                ++ String.fromFloat result.elapsed ++ " seconds"
            ]
        , HK.node "div"
            [ class "pane-search-result-list" ]
            <| if List.isEmpty results
                then List.singleton
                    <| Tuple.pair "0"
                    <| HL.lazy viewElement
                        { query = result.query
                        , uiId = result.id
                        , isLast = result.isLast
                        , order = 0
                        , elapsedSeconds = result.elapsed
                        , sorting = result.sortConfig
                        , result = []
                        }
                else List.indexedMap
                        (\id item ->
                            ( String.fromInt id
                            , HL.lazy viewElement
                                { query = result.query
                                , uiId = result.id
                                , isLast = result.isLast && result.totalResultCount == page * 10 + id + 1
                                , order = id
                                , elapsedSeconds = result.elapsed
                                , sorting = result.sortConfig
                                , result = [ item ]
                                }
                            )
                        )
                    <| List.take 10
                    <| List.drop (page * 10) results
        , if maxId < 10
            then Html.text ""
            else viewPagination setPageEvent
                result.isTrimmed
                (maxId // 10)
                page
        ]

viewPagination : (Int -> msg) -> Bool -> Int -> Int -> Html msg
viewPagination setPageEvent trimmed max current =
    let
        viewButton : Int -> Html msg
        viewButton page =
            div [ HA.classList
                    [ ("pane-search-result-pagination-page", True)
                    , ("pane-search-result-pagination-current", current == page)
                    , ("pane-search-result-pagination-trimmed", trimmed && page == max)
                    ]
                , HE.onClick <| setPageEvent page
                , HA.title <|
                    if trimmed && page == max
                    then "The number of results are trimmed to a reasonable amount."
                    else ""
                ]
                [ Html.text <| String.fromInt <| page + 1 ]
        
        viewSkip : Html msg
        viewSkip =
            div [ class "pane-search-result-pagination-skip" ]
                [ Html.text "..." ]
        
        viewPrev : Html msg
        viewPrev =
            div [ class "pane-search-result-pagination-prev"
                , HE.onClick <| setPageEvent <| current - 1
                , HA.title <| "Navigate to page " ++ String.fromInt current
                ]
                [ Html.text "<" ]

        viewNext : Html msg
        viewNext =
            div [ class "pane-search-result-pagination-next"
                , HE.onClick <| setPageEvent <| current + 1
                , HA.title <| "Navigate to page " ++ String.fromInt (current + 2)
                ]
                [ Html.text ">" ]

    in div [ class "pane-search-result-pagination" ]
        <| List.concat
            [ if current == 0 then []
                else [ viewPrev ]
            , if current < 2 then []
                else [ viewButton 0 ]
            , if current < 3 then []
                else [ viewSkip ]
            , List.map viewButton
                <| List.range
                    (clamp 0 max <| current - 1)
                    (clamp 0 max <| current + 1)
            , if current > max - 3 then []
                else [ viewSkip ]
            , if current > max - 2 then []
                else [ viewButton max ]
            , if current == max then []
                else [ viewNext ]
            ]

--     < 0 1 [2] 3 4 >
-- < 0 ... 2 [3] 4 ... 6 >
