<commit_before>module Main exposing (main)

import Browser
import Browser.Dom exposing (getViewport, getViewportOf, setViewport)
import Browser.Events
import Html exposing (Html, a, div)
import Html.Attributes exposing (height, href, src, width)
import Html.Events exposing (onClick, onMouseEnter, onMouseLeave)
import Icons
import Json.Decode as Decode exposing (Decoder)
import List.Zipper as Zipper exposing (Zipper)
import Manifest exposing (Country(..), Filter(..), Image, Location(..), Trip(..), blurURL, countryNames, filterImages, imageURL, locale, locationNames, manifest, sortImages, stringToCountry, stringToLocation, stringToTrip, thumbURL, tripNames)
import Partition exposing (KPartition, greedyK)
import Ports exposing (nearBottom)
import Svg
import Svg.Attributes
import Task


main : Program Int Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }



--- Model


type alias Model =
    { partition : KPartition Int
    , images : List Image
    , layout : Maybe (Zipper Image)
    , filter : Filter
    , filterSelected : ( Radio, String )
    , resizedAfterLoad : Bool
    , rows : Rows
    , window : Viewport
    , gallery : Viewport
    , viewportOffset : Float
    , scrollWidth : Float
    , locale : String
    , zoom : Maybe Image
    , showModal : Bool
    , showDescription : Bool
    , showControls : Bool
    , showMenu : Bool
    }


initialModel : Int -> Model
initialModel scrollWidth =
    { partition = []
    , images = manifest
    , layout = Nothing
    , filter = All
    , filterSelected = ( RadioAll, "" )
    , resizedAfterLoad = False
    , rows = { total = 0, visible = 10 }
    , window = emptyViewport --TODO: Drop this to viewport.height if we don't need anything else from this later
    , gallery = emptyViewport
    , viewportOffset = 0
    , scrollWidth = toFloat scrollWidth
    , locale = ""
    , zoom = Nothing
    , showModal = False
    , showDescription = True
    , showControls = False
    , showMenu = False
    }


type alias Viewport =
    { x : Float
    , y : Float
    , width : Float
    , height : Float
    }


type alias Rows =
    { total : Int
    , visible : Int
    }


emptyViewport : Viewport
emptyViewport =
    { x = 0
    , y = 0
    , width = 0
    , height = 0
    }


init : Int -> ( Model, Cmd Msg )
init scrollWidth =
    ( initialModel scrollWidth
    , getWindow Init
    )


type Event
    = Resize
    | Filter
    | Init


type Radio
    = RadioAll
    | RadioCountry
    | RadioLocation
    | RadioTrip



--- Update


type Msg
    = RePartition
    | Partition Event (Result Browser.Dom.Error Browser.Dom.Viewport)
    | SetWindow Event (Result Browser.Dom.Error Browser.Dom.Viewport)
    | ToggleRadio Radio
    | LazyLoad
    | PutLocale String
    | PopLocale
    | ZoomImage (Maybe Image)
    | SetZoom (Maybe Image) (Result Browser.Dom.Error Browser.Dom.Viewport)
    | NextZoom
    | PreviousZoom
    | ToggleModal
    | ToggleDescription
    | ToggleControls Bool
    | ToggleMenu
    | SetSelection String
    | KeyPress Keyboard
    | NoOp


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        -- VIEWPORT
        SetWindow event result ->
            case result of
                Ok vp ->
                    ( { model | window = vp.viewport }, Task.attempt (Partition event) (getViewportOf "gallery") )

                Err _ ->
                    ( model, Cmd.none )

        -- GALLERY
        RePartition ->
            ( model, getWindow Resize )

        Partition event result ->
            case result of
                Ok vp ->
                    let
                        oldViewport =
                            vp.viewport

                        asideWidth =
                            if oldViewport.width >= 1800 then
                                495

                            else if oldViewport.width >= 1200 then
                                370

                            else if oldViewport.width >= 900 then
                                290

                            else
                                0

                        ratios =
                            getRatios <| filterImages model.filter model.images

                        rowsGuess =
                            -- So we have the old veiwport, and we need to figure out if our new
                            -- viewport will require a scrollbar or not. Take a guess at the new div height
                            optimalRowCount ratios (oldViewport.width - asideWidth) model.window.height

                        toggleResize =
                            case event of
                                Init ->
                                    True

                                _ ->
                                    model.resizedAfterLoad

                        newWidth =
                            case event of
                                Filter ->
                                    case ( oldViewport.height > model.window.height, rowsGuess < 4, model.resizedAfterLoad ) of
                                        ( True, True, _ ) ->
                                            oldViewport.width + model.scrollWidth

                                        ( False, False, True ) ->
                                            oldViewport.width - model.scrollWidth

                                        _ ->
                                            oldViewport.width

                                Init ->
                                    oldViewport.width - model.scrollWidth

                                Resize ->
                                    oldViewport.width

                        rowsBest =
                            optimalRowCount ratios (newWidth - asideWidth) model.window.height

                        rows =
                            model.rows

                        layout =
                            buildLayout model.images model.filter
                    in
                    ( { model
                        | partition = greedyK (weights ratios) rowsBest
                        , resizedAfterLoad = toggleResize
                        , gallery = { oldViewport | width = newWidth - asideWidth }
                        , rows = { rows | total = rowsBest }
                        , layout = layout
                      }
                    , case event of
                        Filter ->
                            Task.attempt (\_ -> NoOp) (setViewport 0 0)

                        _ ->
                            Cmd.none
                    )

                Err _ ->
                    ( model, Cmd.none )

        ToggleRadio selected ->
            let
                ( newModel, runCmd ) =
                    case selected of
                        RadioAll ->
                            let
                                rows =
                                    model.rows

                                filter =
                                    newFilter ( selected, "" ) model.filter
                            in
                            ( { model | rows = { rows | visible = 10 }, filterSelected = ( selected, "" ), filter = filter, showMenu = False }, Task.attempt (Partition Filter) (getViewportOf "gallery") )

                        _ ->
                            ( { model | filterSelected = ( selected, "" ) }, Cmd.none )
            in
            ( newModel, runCmd )

        LazyLoad ->
            let
                rows =
                    model.rows

                newRows =
                    model.rows.visible + 5

                visibleRows =
                    if newRows > model.rows.total then
                        model.rows.total

                    else
                        newRows
            in
            ( { model | rows = { rows | visible = newRows } }, Cmd.none )

        -- VIEW CHANGES
        PutLocale locale ->
            ( { model | locale = locale }, Cmd.none )

        PopLocale ->
            ( { model | locale = "" }, Cmd.none )

        -- IMAGE VIEWER
        ZoomImage image ->
            ( model, Task.attempt (SetZoom image) getViewport )

        SetZoom image result ->
            case result of
                Ok vp ->
                    let
                        layout =
                            case ( model.layout, image ) of
                                ( Just zip, Just _ ) ->
                                    Zipper.findFirst (\i -> Just i == image) zip

                                _ ->
                                    model.layout
                    in
                    ( { model
                        | zoom = image
                        , viewportOffset = vp.viewport.y
                        , layout = layout
                      }
                    , Task.attempt (\_ -> NoOp) (setViewport 0 model.viewportOffset)
                    )

                Err _ ->
                    ( { model | zoom = image }, Cmd.none )

        NextZoom ->
            let
                ( layout, image ) =
                    getNextZoom model
            in
            ( { model | zoom = image, layout = layout }, Cmd.none )

        PreviousZoom ->
            let
                ( layout, image ) =
                    getPreviousZoom model
            in
            ( { model | zoom = image, layout = layout }, Cmd.none )

        ToggleModal ->
            ( { model | showModal = not model.showModal }, Cmd.none )

        ToggleDescription ->
            ( { model | showDescription = not model.showDescription }, Cmd.none )

        ToggleControls setting ->
            ( { model | showControls = setting }, Cmd.none )

        ToggleMenu ->
            ( { model | showMenu = not model.showMenu }, Cmd.none )

        SetSelection selection ->
            let
                rows =
                    model.rows

                ( radio, _ ) =
                    model.filterSelected

                filter =
                    newFilter ( radio, selection ) model.filter
            in
            ( { model | rows = { rows | visible = 10 }, filter = filter, filterSelected = ( radio, selection ), showMenu = False }, Task.attempt (Partition Filter) (getViewportOf "gallery") )

        KeyPress key ->
            case ( key, model.zoom ) of
                ( Left, Just _ ) ->
                    case model.layout of
                        Just zip ->
                            case Zipper.next zip of
                                Just _ ->
                                    let
                                        ( layout, image ) =
                                            getPreviousZoom model
                                    in
                                    ( { model | zoom = image, layout = layout }, Cmd.none )

                                Nothing ->
                                    ( model, Cmd.none )

                        Nothing ->
                            ( model, Cmd.none )

                ( Right, Just _ ) ->
                    case model.layout of
                        Just zip ->
                            case Zipper.previous zip of
                                Just _ ->
                                    let
                                        ( layout, image ) =
                                            getNextZoom model
                                    in
                                    ( { model | zoom = image, layout = layout }, Cmd.none )

                                Nothing ->
                                    ( model, Cmd.none )

                        Nothing ->
                            ( model, Cmd.none )

                ( Escape, Just _ ) ->
                    ( model, Task.attempt (SetZoom Nothing) getViewport )

                _ ->
                    ( model, Cmd.none )

        NoOp ->
            ( model, Cmd.none )


getWindow : Event -> Cmd Msg
getWindow event =
    Task.attempt (SetWindow event) getViewport



--- Subscriptions


subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.batch
        [ Browser.Events.onResize (\w h -> RePartition)
        , Browser.Events.onKeyDown (Decode.map KeyPress keyDecoder)
        , nearBottom (\_ -> LazyLoad)
        ]


type Keyboard
    = Left
    | Right
    | Escape
    | Other


keyDecoder : Decoder Keyboard
keyDecoder =
    Decode.map toKeyboard (Decode.field "key" Decode.string)


toKeyboard : String -> Keyboard
toKeyboard key =
    case key of
        "ArrowLeft" ->
            Left

        "ArrowRight" ->
            Right

        "Escape" ->
            Escape

        _ ->
            Other



--- View


view : Model -> Html Msg
view model =
    case model.zoom of
        Nothing ->
            let
                ( selected, _ ) =
                    model.filterSelected

                layout =
                    case model.layout of
                        Just images ->
                            images |> Zipper.toList

                        Nothing ->
                            []

                asideView =
                    case model.showMenu of
                        True ->
                            Html.Attributes.class "show-aside"

                        False ->
                            Html.Attributes.class ""
            in
            div [ Html.Attributes.class "content" ]
                [ Html.header [ Html.Attributes.id "title" ]
                    [ Html.text "Iridessence"
                    , Html.span [ Html.Attributes.class "burger" ]
                        [ Html.label []
                            [ Html.input
                                [ Html.Attributes.type_ "checkbox"
                                , Html.Attributes.name "menu-toggle"
                                , Html.Events.onClick ToggleMenu
                                , Html.Attributes.checked model.showMenu
                                ]
                                []
                            , Icons.menu
                            ]
                        ]
                    ]
                , Html.section [ Html.Attributes.id "aside", asideView ]
                    [ div [ Html.Attributes.id "map" ] [ drawGlobe ]
                    , Html.header []
                        [ Html.h1 [] [ Html.text "Odyssey" ]
                        , Html.i [ Html.Attributes.class "quote" ]
                            [ Html.text "The world is a book and those who do not travel read only one page."
                            , Html.div [ Html.Attributes.class "right" ] [ Html.text "— <NAME>" ]
                            ]
                        ]
                    , div [ Html.Attributes.class "locale" ] [ Html.text model.locale ]
                    , Html.nav []
                        [ div [ Html.Attributes.class "middle" ]
                            [ radioView RadioAll selected
                            , radioView RadioCountry selected
                            , radioView RadioLocation selected
                            , radioView RadioTrip selected
                            ]
                        , filterMenu model.filterSelected
                        ]
                    , Html.footer []
                        [ Html.ul [ Html.Attributes.class "icons" ]
                            [ Html.li [] [ Html.a [ Html.Attributes.href "https://www.github.com/Libbum/Odyssey" ] [ Icons.github ] ]
                            , Html.li [] [ Html.button [ onClick ToggleModal ] [ Icons.mail ] ]
                            , Html.li [] [ Html.a [ Html.Attributes.href "https://telegram.me/Libbum" ] [ Icons.telegram ] ]
                            ]
                        ]
                    ]
                , Html.main_
                    [ Html.Attributes.id "gallery" ]
                  <|
                    List.take model.rows.visible <|
                        displayImages layout model.gallery.width model.partition []
                , coverView model.showModal
                , modalView model.showModal
                ]

        Just image ->
            let
                ( nextVisible, previousVisible ) =
                    case model.layout of
                        Just zip ->
                            case ( Zipper.previous zip, Zipper.next zip ) of
                                ( Just _, Just _ ) ->
                                    ( True, True )

                                ( Just _, Nothing ) ->
                                    ( True, False )

                                ( Nothing, Just _ ) ->
                                    ( False, True )

                                ( Nothing, Nothing ) ->
                                    ( False, False )

                        Nothing ->
                            ( False, False )
            in
            zoomImage image model.showControls previousVisible nextVisible model.showDescription


displayImages : List Image -> Float -> KPartition Int -> List (Html Msg) -> List (Html Msg)
displayImages images viewportWidth partition imageRows =
    case partition of
        one :: theRest ->
            let
                rowWidth =
                    List.length one

                newImageRows =
                    displayRowOfImages (List.take rowWidth images) viewportWidth :: imageRows
            in
            displayImages (List.drop rowWidth images) viewportWidth theRest newImageRows

        one ->
            let
                rowOfImages =
                    if List.length images /= 1 then
                        List.take (List.length one) images

                    else
                        images
            in
            displayRowOfImages rowOfImages viewportWidth :: imageRows


displayRowOfImages : List Image -> Float -> Html Msg
displayRowOfImages images viewportWidth =
    if List.length images /= 1 then
        let
            revImages =
                List.reverse images

            arSum =
                summedAspectRatios images

            widths =
                List.reverse <| getWidths revImages viewportWidth arSum []

            h =
                floor (viewportWidth / arSum)
        in
        div [ Html.Attributes.class "flex" ] <| List.map2 (\img w -> displayImage img w h) revImages widths

    else
        List.map
            (\img ->
                let
                    width =
                        if img.aspectRatio < 1 then
                            300

                        else
                            300 * img.aspectRatio

                    height =
                        if img.aspectRatio >= 1 then
                            300

                        else
                            floor (300 * img.aspectRatio)
                in
                displayImage img width height
            )
            images
            |> List.head
            |> Maybe.withDefault (Html.text "")


displayImage : Image -> Float -> Int -> Html Msg
displayImage image w h =
    -- Note the - 8 here on the width is to take into account the two 4px margins in the css
    -- We also send in a float as the width attribute to clean up the right edge
    Html.img
        [ src (thumbURL image)
        , Html.Attributes.attribute "width" (String.fromFloat <| w - 8.0)
        , height h
        , onClick (ZoomImage <| Just image)
        , onMouseEnter (PutLocale <| locale image)
        , onMouseLeave PopLocale
        ]
        []


zoomImage : Image -> Bool -> Bool -> Bool -> Bool -> Html Msg
zoomImage image showControls showPrevious showNext showDescription =
    let
        ( description, descriptionIcon ) =
            case showDescription of
                True ->
                    ( div [ Html.Attributes.class "description" ] [ Html.text image.description ], Html.Attributes.class "" )

                _ ->
                    ( Html.text "", Html.Attributes.class "desc-off" )

        controlVisible =
            case showControls of
                True -><commit_msg>Revert 602a2ad8c Main.elm work. Partition now functions, Amsterdam fails.<commit_after>module Main exposing (main)

import Browser
import Browser.Dom exposing (getViewport, getViewportOf, setViewport)
import Browser.Events
import Html exposing (Html, a, div)
import Html.Attributes exposing (height, href, src, width)
import Html.Events exposing (onClick, onMouseEnter, onMouseLeave)
import Icons
import Json.Decode as Decode exposing (Decoder)
import List.Zipper as Zipper exposing (Zipper)
import Manifest exposing (Country(..), Filter(..), Image, Location(..), Trip(..), blurURL, countryNames, filterImages, imageURL, locale, locationNames, manifest, sortImages, stringToCountry, stringToLocation, stringToTrip, thumbURL, tripNames)
import Partition exposing (KPartition, greedyK)
import Ports exposing (nearBottom)
import Svg
import Svg.Attributes
import Task


main : Program Int Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }



--- Model


type alias Model =
    { partition : KPartition Int
    , images : List Image
    , layout : Maybe (Zipper Image)
    , filter : Filter
    , filterSelected : ( Radio, String )
    , resizedAfterLoad : Bool
    , rows : Rows
    , window : Viewport
    , gallery : Viewport
    , viewportOffset : Float
    , scrollWidth : Float
    , locale : String
    , zoom : Maybe Image
    , showModal : Bool
    , showDescription : Bool
    , showControls : Bool
    , showMenu : Bool
    }


initialModel : Int -> Model
initialModel scrollWidth =
    { partition = []
    , images = manifest
    , layout = Nothing
    , filter = All
    , filterSelected = ( RadioAll, "" )
    , resizedAfterLoad = False
    , rows = { total = 0, visible = 10 }
    , window = emptyViewport --TODO: Drop this to viewport.height if we don't need anything else from this later
    , gallery = emptyViewport
    , viewportOffset = 0
    , scrollWidth = toFloat scrollWidth
    , locale = ""
    , zoom = Nothing
    , showModal = False
    , showDescription = True
    , showControls = False
    , showMenu = False
    }


type alias Viewport =
    { x : Float
    , y : Float
    , width : Float
    , height : Float
    }


type alias Rows =
    { total : Int
    , visible : Int
    }


emptyViewport : Viewport
emptyViewport =
    { x = 0
    , y = 0
    , width = 0
    , height = 0
    }


init : Int -> ( Model, Cmd Msg )
init scrollWidth =
    ( initialModel scrollWidth
    , getWindow Init
    )


type Event
    = Resize
    | Filter
    | Init


type Radio
    = RadioAll
    | RadioCountry
    | RadioLocation
    | RadioTrip



--- Update


type Msg
    = RePartition
    | Partition Event (Result Browser.Dom.Error Browser.Dom.Viewport)
    | SetWindow Event (Result Browser.Dom.Error Browser.Dom.Viewport)
    | ToggleRadio Radio
    | LazyLoad
    | PutLocale String
    | PopLocale
    | ZoomImage (Maybe Image)
    | SetZoom (Maybe Image) (Result Browser.Dom.Error Browser.Dom.Viewport)
    | NextZoom
    | PreviousZoom
    | ToggleModal
    | ToggleDescription
    | ToggleControls Bool
    | ToggleMenu
    | SetSelection String
    | KeyPress Keyboard
    | NoOp


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        -- VIEWPORT
        SetWindow event result ->
            case result of
                Ok vp ->
                    ( { model | window = vp.viewport }, Task.attempt (Partition event) (getViewportOf "gallery") )

                Err _ ->
                    ( model, Cmd.none )

        -- GALLERY
        RePartition ->
            ( model, getWindow Resize )

        Partition event result ->
            case result of
                Ok vp ->
                    let
                        oldViewport =
                            vp.viewport

                        asideWidth =
                            if oldViewport.width >= 1800 then
                                495

                            else if oldViewport.width >= 1200 then
                                370

                            else if oldViewport.width >= 900 then
                                290

                            else
                                0

                        ratios =
                            getRatios <| filterImages model.filter model.images

                        rowsGuess =
                            -- So we have the old veiwport, and we need to figure out if our new
                            -- viewport will require a scrollbar or not. Take a guess at the new div height
                            optimalRowCount ratios (oldViewport.width - asideWidth) model.window.height

                        toggleResize =
                            case event of
                                Init ->
                                    True

                                _ ->
                                    model.resizedAfterLoad

                        newWidth =
                            case event of
                                Filter ->
                                    case ( oldViewport.height > model.window.height, rowsGuess < 4, model.resizedAfterLoad ) of
                                        ( True, True, _ ) ->
                                            oldViewport.width + model.scrollWidth

                                        ( False, False, True ) ->
                                            oldViewport.width - model.scrollWidth

                                        _ ->
                                            oldViewport.width

                                Init ->
                                    oldViewport.width - model.scrollWidth

                                Resize ->
                                    oldViewport.width

                        rowsBest =
                            optimalRowCount ratios (newWidth - asideWidth) model.window.height

                        rows =
                            model.rows

                        layout =
                            buildLayout model.images model.filter
                    in
                    ( { model
                        | partition = greedyK (weights ratios) rowsBest
                        , resizedAfterLoad = toggleResize
                        , gallery = { oldViewport | width = newWidth - asideWidth }
                        , rows = { rows | total = rowsBest }
                        , layout = layout
                      }
                    , case event of
                        Filter ->
                            Task.attempt (\_ -> NoOp) (setViewport 0 0)

                        _ ->
                            Cmd.none
                    )

                Err _ ->
                    ( model, Cmd.none )

        ToggleRadio selected ->
            let
                ( newModel, runCmd ) =
                    case selected of
                        RadioAll ->
                            let
                                rows =
                                    model.rows

                                filter =
                                    newFilter ( selected, "" ) model.filter
                            in
                            ( { model | rows = { rows | visible = 10 }, filterSelected = ( selected, "" ), filter = filter, showMenu = False }, Task.attempt (Partition Filter) (getViewportOf "gallery") )

                        _ ->
                            ( { model | filterSelected = ( selected, "" ) }, Cmd.none )
            in
            ( newModel, runCmd )

        LazyLoad ->
            let
                rows =
                    model.rows

                newRows =
                    model.rows.visible + 5

                visibleRows =
                    if newRows > model.rows.total then
                        model.rows.total

                    else
                        newRows
            in
            ( { model | rows = { rows | visible = newRows } }, Cmd.none )

        -- VIEW CHANGES
        PutLocale locale ->
            ( { model | locale = locale }, Cmd.none )

        PopLocale ->
            ( { model | locale = "" }, Cmd.none )

        -- IMAGE VIEWER
        ZoomImage image ->
            ( model, Task.attempt (SetZoom image) getViewport )

        SetZoom image result ->
            case result of
                Ok vp ->
                    let
                        layout =
                            case ( model.layout, image ) of
                                ( Just zip, Just _ ) ->
                                    Zipper.findFirst (\i -> Just i == image) zip

                                _ ->
                                    model.layout
                    in
                    ( { model
                        | zoom = image
                        , viewportOffset = vp.viewport.y
                        , layout = layout
                      }
                    , Task.attempt (\_ -> NoOp) (setViewport 0 model.viewportOffset)
                    )

                Err _ ->
                    ( { model | zoom = image }, Cmd.none )

        NextZoom ->
            let
                ( layout, image ) =
                    getNextZoom model
            in
            ( { model | zoom = image, layout = layout }, Cmd.none )

        PreviousZoom ->
            let
                ( layout, image ) =
                    getPreviousZoom model
            in
            ( { model | zoom = image, layout = layout }, Cmd.none )

        ToggleModal ->
            ( { model | showModal = not model.showModal }, Cmd.none )

        ToggleDescription ->
            ( { model | showDescription = not model.showDescription }, Cmd.none )

        ToggleControls setting ->
            ( { model | showControls = setting }, Cmd.none )

        ToggleMenu ->
            ( { model | showMenu = not model.showMenu }, Cmd.none )

        SetSelection selection ->
            let
                rows =
                    model.rows

                ( radio, _ ) =
                    model.filterSelected

                filter =
                    newFilter ( radio, selection ) model.filter
            in
            ( { model | rows = { rows | visible = 10 }, filter = filter, filterSelected = ( radio, selection ), showMenu = False }, Task.attempt (Partition Filter) (getViewportOf "gallery") )

        KeyPress key ->
            case ( key, model.zoom ) of
                ( Left, Just _ ) ->
                    case model.layout of
                        Just zip ->
                            case Zipper.next zip of
                                Just _ ->
                                    let
                                        ( layout, image ) =
                                            getPreviousZoom model
                                    in
                                    ( { model | zoom = image, layout = layout }, Cmd.none )

                                Nothing ->
                                    ( model, Cmd.none )

                        Nothing ->
                            ( model, Cmd.none )

                ( Right, Just _ ) ->
                    case model.layout of
                        Just zip ->
                            case Zipper.previous zip of
                                Just _ ->
                                    let
                                        ( layout, image ) =
                                            getNextZoom model
                                    in
                                    ( { model | zoom = image, layout = layout }, Cmd.none )

                                Nothing ->
                                    ( model, Cmd.none )

                        Nothing ->
                            ( model, Cmd.none )

                ( Escape, Just _ ) ->
                    ( model, Task.attempt (SetZoom Nothing) getViewport )

                _ ->
                    ( model, Cmd.none )

        NoOp ->
            ( model, Cmd.none )


getWindow : Event -> Cmd Msg
getWindow event =
    Task.attempt (SetWindow event) getViewport



--- Subscriptions


subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.batch
        [ Browser.Events.onResize (\w h -> RePartition)
        , Browser.Events.onKeyDown (Decode.map KeyPress keyDecoder)
        , nearBottom (\_ -> LazyLoad)
        ]


type Keyboard
    = Left
    | Right
    | Escape
    | Other


keyDecoder : Decoder Keyboard
keyDecoder =
    Decode.map toKeyboard (Decode.field "key" Decode.string)


toKeyboard : String -> Keyboard
toKeyboard key =
    case key of
        "ArrowLeft" ->
            Left

        "ArrowRight" ->
            Right

        "Escape" ->
            Escape

        _ ->
            Other



--- View


view : Model -> Html Msg
view model =
    case model.zoom of
        Nothing ->
            let
                ( selected, _ ) =
                    model.filterSelected

                layout =
                    case model.layout of
                        Just images ->
                            images |> Zipper.toList

                        Nothing ->
                            []

                asideView =
                    case model.showMenu of
                        True ->
                            Html.Attributes.class "show-aside"

                        False ->
                            Html.Attributes.class ""
            in
            div [ Html.Attributes.class "content" ]
                [ Html.header [ Html.Attributes.id "title" ]
                    [ Html.text "Iridessence"
                    , Html.span [ Html.Attributes.class "burger" ]
                        [ Html.label []
                            [ Html.input
                                [ Html.Attributes.type_ "checkbox"
                                , Html.Attributes.name "menu-toggle"
                                , Html.Events.onClick ToggleMenu
                                , Html.Attributes.checked model.showMenu
                                ]
                                []
                            , Icons.menu
                            ]
                        ]
                    ]
                , Html.section [ Html.Attributes.id "aside", asideView ]
                    [ div [ Html.Attributes.id "map" ] [ drawGlobe ]
                    , Html.header []
                        [ Html.h1 [] [ Html.text "Odyssey" ]
                        , Html.i [ Html.Attributes.class "quote" ]
                            [ Html.text "The world is a book and those who do not travel read only one page."
                            , Html.div [ Html.Attributes.class "right" ] [ Html.text "— <NAME>" ]
                            ]
                        ]
                    , div [ Html.Attributes.class "locale" ] [ Html.text model.locale ]
                    , Html.nav []
                        [ div [ Html.Attributes.class "middle" ]
                            [ radioView RadioAll selected
                            , radioView RadioCountry selected
                            , radioView RadioLocation selected
                            , radioView RadioTrip selected
                            ]
                        , filterMenu model.filterSelected
                        ]
                    , Html.footer []
                        [ Html.ul [ Html.Attributes.class "icons" ]
                            [ Html.li [] [ Html.a [ Html.Attributes.href "https://www.github.com/Libbum/Odyssey" ] [ Icons.github ] ]
                            , Html.li [] [ Html.button [ onClick ToggleModal ] [ Icons.mail ] ]
                            , Html.li [] [ Html.a [ Html.Attributes.href "https://telegram.me/Libbum" ] [ Icons.telegram ] ]
                            ]
                        ]
                    ]
                , Html.main_
                    [ Html.Attributes.id "gallery" ]
                  <|
                    List.take model.rows.visible <|
                        displayImages layout model.gallery.width model.partition []
                , coverView model.showModal
                , modalView model.showModal
                ]

        Just image ->
            let
                ( nextVisible, previousVisible ) =
                    case model.layout of
                        Just zip ->
                            case ( Zipper.previous zip, Zipper.next zip ) of
                                ( Just _, Just _ ) ->
                                    ( True, True )

                                ( Just _, Nothing ) ->
                                    ( True, False )

                                ( Nothing, Just _ ) ->
                                    ( False, True )

                                ( Nothing, Nothing ) ->
                                    ( False, False )

                        Nothing ->
                            ( False, False )
            in
            zoomImage image model.showControls previousVisible nextVisible model.showDescription


displayImages : List Image -> Float -> KPartition Int -> List (Html Msg) -> List (Html Msg)
displayImages images viewportWidth partition imageRows =
    case partition of
        one :: theRest ->
            let
                rowWidth =
                    List.length one

                newImageRows =
                    displayRowOfImages (List.take rowWidth images) viewportWidth :: imageRows
            in
            displayImages (List.drop rowWidth images) viewportWidth theRest newImageRows

        one ->
            let
                rowOfImages =
                    List.take (List.length one) images
            in
            displayRowOfImages rowOfImages viewportWidth :: imageRows


displayRowOfImages : List Image -> Float -> Html Msg
displayRowOfImages images viewportWidth =
    let
        revImages =
            List.reverse images

        arSum =
            summedAspectRatios images

        widths =
            List.reverse <| getWidths revImages viewportWidth arSum []

        h =
            floor (viewportWidth / arSum)
    in
    div [ Html.Attributes.class "flex" ] <| List.map2 (\img w -> displayImage img w h) revImages widths


displayImage : Image -> Float -> Int -> Html Msg
displayImage image w h =
    -- Note the - 8 here on the width is to take into account the two 4px margins in the css
    -- We also send in a float as the width attribute to clean up the right edge
    Html.img
        [ src (thumbURL image)
        , Html.Attributes.attribute "width" (String.fromFloat <| w - 8.0)
        , height h
        , onClick (ZoomImage <| Just image)
        , onMouseEnter (PutLocale <| locale image)
        , onMouseLeave PopLocale
        ]
        []


zoomImage : Image -> Bool -> Bool -> Bool -> Bool -> Html Msg
zoomImage image showControls showPrevious showNext showDescription =
    let
        ( description, descriptionIcon ) =
            case showDescription of
                True ->
                    ( div [ Html.Attributes.class "description" ] [ Html.text image.description ], Html.Attributes.class "" )

                _ ->
                    ( Html.text "", Html.Attributes.class "desc-off" )

        controlVisible =
            case showControls of
                True ->
{'fraction_non_alphanumeric': 0.07300410900199147, 'fraction_numerical': 0.0027477375280445687, 'mean_word_length': 0.9524559503888178, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 219, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 102, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1489659', 'n_tokens_mistral': 9568, 'n_tokens_neox': 8769, 'n_words': 2450}