module Stage.Loader.UI
  ( Settings(..)

  , UI(..)
  , spawn

  , Observer(..)
  , newObserver
  , observe
  ) where

import RIO

import Control.Monad.Trans.Resource (ResourceT)
import Control.Monad.Trans.Resource qualified as Resource
import Geomancy (vec4)
import Geomancy.Transform qualified as Transform
import RIO.Vector.Storable qualified as Storable
import Vulkan.Core10 qualified as Vk

import Engine.Types qualified as Engine
import Engine.UI.Layout qualified as Layout
import Engine.UI.Message qualified as Message
import Engine.Vulkan.Types (HasVulkan, Queues)
import Engine.Worker qualified as Worker
import Geometry.Quad qualified as Quad
import Render.Samplers qualified as Samplers
import Render.Unlit.Textured.Model qualified as UnlitTextured
import Resource.Buffer qualified as Buffer
import Resource.Combined.Textures qualified as CombinedTextures
import Resource.Font.EvanW qualified as EvanW
import Resource.Model qualified as Model
import Resource.Texture (Texture, Flat)

data Settings fonts textures = Settings
  { titleMessage :: Text
  , backgroundIx :: Int32
  , spinnerIx    :: Int32

  , combined     :: CombinedTextures.Collection textures fonts (Int32, Texture Flat)
  , fonts        :: fonts EvanW.Container

  , smallFont    :: forall a . fonts a -> a
  , largeFont    :: forall a . fonts a -> a
  }

data UI = UI
  { titleP    :: Message.Process
  , subtitleP :: Message.Process

  , progressInput :: Worker.Var Message.Input
  , progressP     :: Message.Process

  , backgroundP :: Worker.Merge StorableAttrs
  , spinnerP    :: Worker.Merge StorableAttrs

  , quadUV :: UnlitTextured.Model 'Buffer.Staged
  }

type StorableAttrs =
  ( Storable.Vector UnlitTextured.TextureParams
  , Storable.Vector UnlitTextured.Transform
  )

spawn
  :: Queues Vk.CommandPool
  -> Layout.BoxProcess
  -> Settings fonts textures
  -> Engine.StageRIO env (Resource.ReleaseKey, UI)
spawn pools screenBoxP Settings{..} = do
  paddingVar <- Worker.newVar 16
  (screenPaddedKey, screenPaddedP) <- Worker.registered $
    Layout.padAbs screenBoxP paddingVar

  -- Messages

  sections <- Layout.splitsRelStatic Layout.sharePadsV screenPaddedP [2, 1, 1]
  sectionsKey <- Worker.registerCollection sections

  (titleBoxP, subtitleBoxP, progressBoxP) <- case sections of
    [titleBoxP, subtitleBoxP, progressBoxP] ->
      pure (titleBoxP, subtitleBoxP, progressBoxP)
    _ ->
      error "assert: shares in Traversable preserve structure"

  (titleKey, titleP) <- Worker.registered $
    Worker.newVar title >>= Message.spawn titleBoxP
  (subtitleKey, subtitleP) <- Worker.registered $
    Worker.newVar subtitle >>= Message.spawn subtitleBoxP

  progressInput <- Worker.newVar progress
  (progressKey, progressP) <- Worker.registered $
    Message.spawn progressBoxP progressInput

  -- Splash

  (backgroundBoxKey, backgroundBoxP) <- Worker.registered $
    Layout.fitPlaceAbs Layout.Center 1.0 screenBoxP

  (backgroundKey, backgroundP) <- Worker.registered $
    Worker.spawnMerge1
      ( \box ->
          UnlitTextured.storableAttrs1
              (Samplers.linear Samplers.indices)
              backgroundIx
              [Layout.boxTransformAbs box]
      )
      backgroundBoxP

  -- Spinner

  (spinnerTransformTimeKey, spinnerTransformTimeP) <- Worker.registered $
    Worker.spawnTimed_ 100 True do
      t <- getMonotonicTime
      pure $ Transform.rotateZ (realToFrac t)

  (spinnerTransformBoxKey, spinnerTransformBoxP) <- Worker.registered $
    Worker.spawnMerge1
      (Layout.boxTransformAbs . snd . Layout.boxFitScale 64)
      subtitleBoxP

  (spinnerKey, spinnerP) <- Worker.registered $
    Worker.spawnMerge2
      ( \place turn ->
          UnlitTextured.storableAttrs1
              (Samplers.linear Samplers.indices)
              spinnerIx
              [turn, place]
      )
      spinnerTransformBoxP
      spinnerTransformTimeP

  -- Models

  context <- ask
  quadUV <- Model.createStagedL context pools (Quad.toVertices Quad.texturedQuad) Nothing
  quadUVKey <- Resource.register $ Model.destroyIndexed context quadUV

  -- Cleanup

  key <- Resource.register $ traverse_ Resource.release
    [ screenPaddedKey
    , sectionsKey
    , titleKey, subtitleKey, progressKey
    , backgroundBoxKey, backgroundKey
    , spinnerTransformBoxKey, spinnerTransformTimeKey, spinnerKey
    , quadUVKey
    ]

  pure (key, UI{..})
  where
    title = Message.Input
      { inputText         = titleMessage
      , inputOrigin       = Layout.CenterBottom
      , inputSize         = 64
      , inputColor        = vec4 1 0.5 0.25 1
      , inputFont         = largeFont fonts
      , inputFontId       = fst . largeFont $ CombinedTextures.fonts combined
      , inputOutline      = vec4 0 0 0 1
      , inputOutlineWidth = 4/16
      , inputSmoothing    = 1/16
      }

    subtitle = Message.Input
      { inputText         = "Loading..."
      , inputOrigin       = Layout.CenterTop
      , inputSize         = 32
      , inputColor        = vec4 0.5 1 0.25 1
      , inputFont         = smallFont fonts
      , inputFontId       = fst . smallFont $ CombinedTextures.fonts combined
      , inputOutline      = vec4 0 0 0 1
      , inputOutlineWidth = 4/16
      , inputSmoothing    = 1/16
      }

    progress = Message.Input
      { inputText         = ""
      , inputOrigin       = Layout.CenterBottom
      , inputSize         = 16
      , inputColor        = vec4 0.25 0.5 1 1
      , inputFont         = smallFont fonts
      , inputFontId       = fst . smallFont $ CombinedTextures.fonts combined
      , inputOutline      = 0
      , inputOutlineWidth = 0
      , inputSmoothing    = 1/16
      }

data Observer = Observer
  { messages   :: [Message.Observer]
  , background :: Worker.ObserverIO (UnlitTextured.InstanceBuffers 'Buffer.Coherent 'Buffer.Coherent)
  , spinner    :: Worker.ObserverIO (UnlitTextured.InstanceBuffers 'Buffer.Coherent 'Buffer.Coherent)
  }

newObserver :: UI -> ResourceT (Engine.StageRIO st) Observer
newObserver UI{..} = do
  -- XXX: Generic?
  messages <- traverse (const $ Message.newObserver 256)
    [ titleP
    , subtitleP
    , progressP
    ]

  let
    newBufferObserver1 =
      UnlitTextured.allocateInstancesCoherent_ 1 >>= Worker.newObserverIO

  background <- newBufferObserver1
  spinner <- newBufferObserver1

  pure Observer{..}

observe
  :: ( HasVulkan env
     )
  => UI
  -> Observer -> RIO env ()
observe UI{..} Observer{..} = do
  traverse_ (uncurry Message.observe) $
    zip [titleP, subtitleP, progressP] messages

  context <- ask

  Worker.observeIO_ backgroundP background $
    UnlitTextured.updateCoherentResize_ context

  Worker.observeIO_ spinnerP spinner $
    UnlitTextured.updateCoherentResize_ context
