{-# OPTIONS_GHC -fno-warn-unticked-promoted-constructors #-}
{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedLabels      #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeApplications      #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}

-- | A declarative representation of 'Gtk.Bin' in GTK.
module GI.Gtk.Declarative.Bin
  ( Bin(..)
  , bin
  )
where

import           Data.Typeable
import           Data.Vector                             (Vector)
import qualified GI.Gtk                                  as Gtk

import           GI.Gtk.Declarative.Attributes
import           GI.Gtk.Declarative.Attributes.Collected
import           GI.Gtk.Declarative.Attributes.Internal
import           GI.Gtk.Declarative.EventSource
import           GI.Gtk.Declarative.Patch
import           GI.Gtk.Declarative.State
import           GI.Gtk.Declarative.Widget


-- | Declarative version of a /bin/ widget, i.e. a widget with exactly one
-- child.
data Bin widget event where
  Bin
    :: ( Typeable widget
       , Gtk.IsContainer widget
       , Gtk.IsBin widget
       , Gtk.IsWidget widget
       )
    => (Gtk.ManagedPtr widget -> widget)
    -> Vector (Attribute widget event)
    -> Widget event
    -> Bin widget event

instance Functor (Bin widget) where
  fmap f (Bin ctor attrs child) =
    Bin ctor (fmap f <$> attrs) (fmap f child)

-- | Construct a /bin/ widget, i.e. a widget with exactly one child.
bin
  :: ( Typeable widget
     , Gtk.IsContainer widget
     , Gtk.IsBin widget
     , Gtk.IsWidget widget
     , FromWidget (Bin widget) target
     )
  => (Gtk.ManagedPtr widget -> widget) -- ^ A bin widget constructor from the underlying gi-gtk library.
  -> Vector (Attribute widget event)   -- ^ List of 'Attribute's.
  -> Widget event                       -- ^ The bin's child widget
  -> target event                      -- ^ The target, whose type is decided by 'FromWidget'.
bin ctor attrs = fromWidget . Bin ctor attrs

--
-- Patchable
--

instance (Gtk.IsBin parent) => Patchable (Bin parent) where
  create (Bin (ctor :: Gtk.ManagedPtr w -> w) attrs child) = do
    let collected = collectAttributes attrs
    widget' <- Gtk.new ctor (constructProperties collected)
    Gtk.widgetShow widget'

    sc <- Gtk.widgetGetStyleContext widget'
    updateClasses sc mempty (collectedClasses collected)

    mapM_ (applyAfterCreated widget') attrs

    childState <- create child
    childWidget <- someStateWidget childState
    maybe (pure ()) Gtk.widgetDestroy =<< Gtk.binGetChild widget'
    Gtk.containerAdd widget' childWidget
    return (SomeState (StateTreeBin (StateTreeNode widget' sc collected ()) childState))

  patch (SomeState (st :: StateTree stateType w1 c1 e1 cs))
        (Bin _ _ oldChild)
        (Bin (ctor :: Gtk.ManagedPtr w2 -> w2) newAttributes newChild) =
    case (st, eqT @w1 @w2) of
      (StateTreeBin top oldChildState, Just Refl) ->
        Modify $ do
          binWidget <- Gtk.unsafeCastTo ctor (stateTreeWidget top)
          let oldCollected = stateTreeCollectedAttributes top
              newCollected = collectAttributes newAttributes
          updateProperties binWidget (collectedProperties oldCollected) (collectedProperties newCollected)
          updateClasses (stateTreeStyleContext top) (collectedClasses oldCollected) (collectedClasses newCollected)

          let top' = top { stateTreeCollectedAttributes = newCollected }
          case patch oldChildState oldChild newChild of
            Modify modify -> SomeState . StateTreeBin top' <$> modify
            Replace createNew -> do
              Gtk.widgetDestroy =<< someStateWidget oldChildState
              newChildState <- createNew
              childWidget <- someStateWidget newChildState
              Gtk.widgetShow childWidget
              maybe (pure ()) Gtk.widgetDestroy =<< Gtk.binGetChild binWidget
              Gtk.containerAdd binWidget childWidget
              return (SomeState (StateTreeBin top' newChildState))
            Keep -> return (SomeState st)
      _ -> Replace (create (Bin ctor newAttributes newChild))

--
-- EventSource
--

instance Gtk.IsBin parent => EventSource (Bin parent) where
  subscribe (Bin ctor props child) (SomeState st) cb =
    case st of
      StateTreeBin top childState -> do
        binWidget <- Gtk.unsafeCastTo ctor (stateTreeWidget top)
        handlers' <- foldMap (addSignalHandler cb binWidget) props
        (<> handlers') <$> subscribe child childState cb
      _ -> error "Cannot subscribe to Bin events with a non-bin state tree."

instance a ~ b => FromWidget (Bin a) (Bin b) where
  fromWidget = id