module Pages.Home_ exposing (Model, Msg, page)

import Array exposing (Array)
import Effect exposing (Effect)
import Html exposing (Html)
import Html.Events exposing (onClick, onMouseLeave)
import Layouts
import Page exposing (Page)
import Route exposing (Route)
import Shared
import Svg exposing (..)
import Svg.Attributes as SA
import Svg.Events as SE
import Time
import Utils.Constants exposing (nameMatrix)
import View exposing (View)


page : Shared.Model -> Route () -> Page Model Msg
page _ _ =
    Page.new
        { init = init
        , update = update
        , subscriptions = subscriptions
        , view = view
        }
        |> Page.withLayout layout



-- LAYOUT


layout : Model -> Layouts.Layout
layout _ =
    Layouts.Home
        { home = {}
        }



-- INIT


type Cell
    = Alive
    | Dead


type alias Universe a =
    { space : Array a
    , width : Int
    , height : Int
    }


type alias Model =
    { universe : Universe Cell
    , generations : Int
    , generating : Bool
    }

cellSize : Int
cellSize =
    10


init : () -> ( Model, Effect Msg )
init () =
    ( { universe = bigBang 30 30 Dead
      , generating = False
      , generations = 0
      }
    , Effect.none
    )



-- UPDATE


type Msg
    = Ressurect Int Int
    | NextGeneration
    | LiveLife
    | Apocalypse


update : Msg -> Model -> ( Model, Effect Msg )
update msg model =
    case msg of
        Ressurect x y ->
            ( { model
                | universe =
                    buildRelationship model.universe
                        x
                        y
                        invertLife
              }
            , Effect.none
            )

        NextGeneration ->
            let
                universe =
                    nextGeneration model.universe

                generations =
                    model.generations + 1

                generating =
                    model.generating && universe /= model.universe
            in
            ( { model
                | universe = universe
                , generations = generations
                , generating = generating
              }
            , Effect.none
            )

        LiveLife ->
            ( { model | generating = not model.generating }
            , Effect.none
            )

        Apocalypse ->
            ( { model | generating = False }
            , Effect.none
            )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    if model.generating then
        Time.every 500 (always NextGeneration)

    else
        Sub.none



-- VIEW


view : Model -> View Msg
view model =
    { title = "Welcome to my website"
    , body = [ Html.div [ onClick LiveLife, onMouseLeave LiveLife ] [ universeSvg model ] ]
    }


cellToSvg : Int -> Int -> Cell -> Svg Msg
cellToSvg x y cell =
    let
        isName =
            if Array.get ((y - 12) * 24 + x - 3) nameMatrix == Just 1 && x >= 3 && x < 27 then
                True

            else
                False

        color =
            case cell of
                Alive ->
                    if isName then
                        "fill-cyan-500"

                    else
                        "fill-pink-500"

                Dead ->
                    "fill-neutral-800"
    in
    rect
        [ SA.x (String.fromInt (x * cellSize))
        , SA.y (String.fromInt (y * cellSize))
        , SA.width (String.fromInt cellSize)
        , SA.height (String.fromInt cellSize)
        , SA.class color
        , SE.onMouseOver (Ressurect x y)
        ]
        []


universeSvg : Model -> Html Msg
universeSvg model =
    let
        svgWidth =
            String.fromInt (model.universe.width * cellSize)

        svgHeight =
            String.fromInt (model.universe.height * cellSize)
    in
    svg
        [ SA.width "150"
        , SA.height "150"
        , SA.viewBox ("0 0 " ++ svgWidth ++ " " ++ svgHeight)
        , SA.class "stroke-0"
        ]
        (stretchUniverseThin cellToSvg model.universe)



-- UTIL


bigBang : Int -> Int -> a -> Universe a
bigBang width height value =
    { space = Array.repeat (width * height) value
    , width = width
    , height = height
    }


stretchUniverseThin : (Int -> Int -> a -> b) -> Universe a -> List b
stretchUniverseThin f universe =
    Array.toIndexedList universe.space
        |> List.map (\( i, cell ) -> f (modBy universe.width i) (i // universe.width) cell)


nextGeneration : Universe Cell -> Universe Cell
nextGeneration universe =
    { universe
        | space =
            Array.indexedMap
                (decideFateOf universe)
                universe.space
    }


decideFateOf : Universe Cell -> Int -> Cell -> Cell
decideFateOf u i today =
    let
        aliveAtoms =
            countLiveAtoms i u
    in
    case today of
        Alive ->
            if aliveAtoms < 2 || aliveAtoms > 3 then
                Dead

            else
                Alive

        Dead ->
            if aliveAtoms == 3 then
                Alive

            else
                Dead


buildRelationship : Universe a -> Int -> Int -> (a -> a) -> Universe a
buildRelationship universe x y fn =
    case getUniverseAt x y universe of
        Just cell ->
            setUniverseAt x y (fn cell) universe

        Nothing ->
            universe


getUniverseAt : Int -> Int -> Universe a -> Maybe a
getUniverseAt x y u =
    Array.get (y * u.width + x) u.space


setUniverseAt : Int -> Int -> a -> Universe a -> Universe a
setUniverseAt x y value u =
    { u | space = Array.set (y * u.width + x) value u.space }


countLiveAtoms : Int -> Universe Cell -> Int
countLiveAtoms i universe =
    let
        above =
            i - universe.width

        below =
            i + universe.width

        coordinate =
            [ above - 1
            , above
            , above + 1
            , i - 1
            , i + 1
            , below - 1
            , below
            , below + 1
            ]
    in
    coordinate
        |> List.filter
            (\n ->
                abs
                    (modBy universe.width n - modBy universe.width i)
                    <= 1
            )
        |> List.map (\c -> universe.space |> Array.get c)
        |> List.filter (\c -> c == Just Alive)
        |> List.length


invertLife : Cell -> Cell
invertLife cell =
    case cell of
        Alive ->
            Dead

        Dead ->
            Alive
