{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}

module Glazier.Widget
    ( Widget(..)
    , _window
    , _gadget
    , _window'
    , _gadget'
    , _Widget
    , _Widget'
    , _WrappingWidget
    , _WrappingWidget'
    , belowWidget
    , underWidget
    , overWidget
    , aboveWidget
    , mkWidget
    , mkWidget'
    , runWidget
    , runWidget'
    , statically
    , dynamically
    ) where

import Control.Applicative
import Control.Lens
import Data.Functor.Apply
import Data.Maybe
import Data.Semigroup
import Glazier.Class
import Glazier.Gadget
import Glazier.Window

-- | A widget is basically a tuple with Gadget and Window, but with handy instances for implant and dispatch.
data Widget v m r a s n c = Widget
  { window :: WindowT s v m r
  , gadget :: GadgetT a s n c
  }

-- | polymorphic lens to the window of a widget
_window :: Lens (Widget v m r a s n c) (Widget v' m' r' a s n c) (WindowT s v m r) (WindowT s v' m' r')
_window = lens window (\(Widget _ g) w -> Widget w g)
{-# INLINABLE _window #-}

-- | polymorphic lens to the gadget of a widget
_gadget :: Lens (Widget v m r a s n c) (Widget v m r a' s n' c') (GadgetT a s n c) (GadgetT a' s n' c')
_gadget = lens gadget (\(Widget w _) g -> Widget w g)
{-# INLINABLE _gadget #-}

-- | non polymorphic lens to the window of a widget
_window' :: Lens' (Widget v m r a s n c) (WindowT s v m r)
_window' = _window
{-# INLINABLE _window' #-}

-- | non polymorphic lens to the gadget of a widget
_gadget' :: Lens' (Widget v m r a s n c) (GadgetT a s n c)
_gadget' = _gadget
{-# INLINABLE _gadget' #-}

_Widget :: Iso (Widget v m r a s n c) (Widget v' m' r' a' s' n' c')
           (s -> v -> m (r, v), a -> s -> n (c, s)) (s' -> v' -> m' (r', v'), a' -> s' -> n' (c', s'))
_Widget = iso (\(Widget w g) -> (view _WindowT w, view _GadgetT g))
               (\(w, g) -> Widget (review _WindowT w) (review _GadgetT g))
{-# INLINABLE _Widget #-}

-- | Non polymorphic version of _Widget
_Widget' :: Iso' (Widget v m r a s n c) (s -> v -> m (r, v), a -> s -> n (c, s))
_Widget' = _Widget
{-# INLINABLE _Widget' #-}

_WrappingWidget :: Iso (Widget v m r a s n c) (Widget v' m' r' a' s' n' c')
           (WindowT s v m r, GadgetT a s n c) (WindowT s' v' m' r', GadgetT a' s' n' c')
_WrappingWidget = iso (\(Widget w g) -> (w, g))
               (\(w, g) -> Widget w g)
{-# INLINABLE _WrappingWidget #-}

-- | Non polymorphic version of _WrappingWidget
_WrappingWidget' :: Iso' (Widget v m r a s n c) (WindowT s v m r, GadgetT a s n c)
_WrappingWidget' = _WrappingWidget
{-# INLINABLE _WrappingWidget' #-}

mkWidget :: (WindowT s v m r, GadgetT a s n c) -> Widget v m r a s n c
mkWidget = review _WrappingWidget
{-# INLINABLE mkWidget #-}

mkWidget' :: (s -> v -> m (r, v), a -> s -> n (c, s)) -> Widget v m r a s n c
mkWidget' = review _Widget
{-# INLINABLE mkWidget' #-}

runWidget :: Widget v m r a s n c -> (WindowT s v m r, GadgetT a s n c)
runWidget = view _WrappingWidget
{-# INLINABLE runWidget #-}

runWidget' :: Widget v m r a s n c -> (s -> v -> m (r, v), a -> s -> n (c, s))
runWidget' = view _Widget
{-# INLINABLE runWidget' #-}

belowWidget ::
  ((s -> v -> m (r, v), a -> s -> n (c, s))
   -> (s' -> v' -> m' (r', v'), a' -> s' -> n' (c', s')))
  -> Widget v m r a s n c -> Widget v' m' r' a' s' n' c'
belowWidget f = _Widget %~ f
{-# INLINABLE belowWidget #-}

underWidget ::
  ((WindowT s v m r, GadgetT a s n c)
   -> (WindowT s' v' m' r', GadgetT a' s' n' c'))
  -> Widget v m r a s n c -> Widget v' m' r' a' s' n' c'
underWidget f = _WrappingWidget %~ f
{-# INLINABLE underWidget #-}

overWidget ::
  (Widget v m r a s n c -> Widget v' m' r' a' s' n' c')
  -> (WindowT s v m r, GadgetT a s n c)
  -> (WindowT s' v' m' r', GadgetT a' s' n' c')
overWidget f = from _WrappingWidget %~ f
{-# INLINABLE overWidget #-}

aboveWidget ::
  (Widget v m r a s n c -> Widget v' m' r' a' s' n' c')
  -> (s -> v -> m (r, v), a -> s -> n (c, s))
  -> (s' -> v' -> m' (r', v'), a' -> s' -> n' (c', s'))
aboveWidget f = from _Widget %~ f
{-# INLINABLE aboveWidget #-}

instance (Monad m, Monad n, Semigroup r, Semigroup c) => Semigroup (Widget v m r a s n c) where
    w1 <> w2 = Widget
      (window w1 <> window w2)
      (gadget w1 <> gadget w2)
    {-# INLINABLE (<>) #-}

instance (Monad m, Monad n, Monoid r, Monoid c) => Monoid (Widget v m r a s n c) where
    mempty = Widget mempty mempty
    {-# INLINABLE mempty #-}

    mappend w1 w2 = Widget
        (window w1 `mappend` window w2)
        (gadget w1 `mappend` gadget w2)
    {-# INLINABLE mappend #-}

-- | Widget Functor is lawful
-- 1: fmap id  =  id
-- (Widget w g) = Widget w (id <$> g) =  Widget w g
-- 2: fmap (f . g) = fmap f . fmap g
-- (Widget w gad) = Widget w ((f . g) <$> gad) = Widget w ((fmap f . fmap g) gad)
instance Functor n => Functor (Widget v m r a s n) where
    fmap f (Widget w g) = Widget
        w
        (f <$> g)
    {-# INLINABLE fmap #-}

-- | Widget Applicative is lawful
-- Identity: pure id <*> v = v
-- Widget mempty (pure id) <*> Widget vw vg
--     = Widget (mempty <> vw) (pure id <*> vg)
--     = Widget vw vg
-- Composition: pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
-- Widget mempty (pure (.)) <*> Widget uw ug <*> Widget vw vg <*> Widget ww wg =
--     = Widget (mempty <> uw <> vw <> ww) (pure (.) <*> ug <*> vg <*> wg
--     = Widget (uw <> vw <> ww) (ug <*> (vg <*> wg))
--     = Widget (uw <> (vw <> ww)) (ug <*> (vg <*> wg))
--     = Widget uw ug <*> (Widget vw vg <*> Widget ww wg)
-- Interchange: u <*> pure y = pure ($ y) <*> u
-- Widget uw ug <*> Widget mempty (pure y)
--     = Widget (uw <> mempty) (ug <*> pure y)
--     = Widget (mempty <> uw) (pure ($ y) <*> ug)
--     = Widget mempty (pure $y) <*> Widget uw ug
instance (Monad m, Monad n, Monoid r) => Applicative (Widget v m r a s n) where
    pure c = Widget mempty (pure c)
    {-# INLINABLE pure #-}

    (Widget w1 fg) <*> (Widget w2 g) = Widget (w1 `mappend` w2) (fg <*> g)
    {-# INLINABLE (<*>) #-}

statically :: (Monad n, Monoid c) => WindowT s v m r -> Widget v m r a s n c
statically w = Widget w mempty
{-# INLINABLE statically #-}

dynamically :: (Monad m, Monoid r) => GadgetT a s n c -> Widget v m r a s n c
dynamically = Widget mempty
{-# INLINABLE dynamically #-}

type instance Dispatched (Widget v m r a s n c) = Dispatched (GadgetT a s n c)
instance Monad n => Dispatch (Widget v m r a s n c) (Widget v m r b s n c) a b where
    dispatch p w = Widget
        (window w)
        (dispatch p $ gadget w)
    {-# INLINABLE dispatch #-}

type instance Implanted (Widget v m r a s n c) =
     PairMaybeFunctor (Implanted (WindowT s v m r))
       (Implanted (GadgetT a s n c))
instance (Monad m, Monad n) => Implant (Widget v m r a s n c) (Widget v m r a t n c) s t where
    implant l w = Widget
        (implant (fstLensLike l) $ window w)
        (implant (sndLensLike l) $ gadget w)
    {-# INLINABLE implant #-}

-- -------------------------------------------------------------------------------

-- | This can be used to hold two LensLike functors.
-- The inner LensLike functor can be extracted from a @LensLike (PairMaybeFunctor f g) s t a b@
-- using 'fstLensLike' or 'sndLensLike'.
-- NB. The constructor must not be exported to keep 'fstLensLike' and 'sndLensLike' safe.
newtype PairMaybeFunctor f g a = PairMaybeFunctor { getPairMaybeFunctor :: (Maybe (f a), Maybe (g a)) }

instance (Functor f, Functor g) => Functor (PairMaybeFunctor f g) where
    fmap f (PairMaybeFunctor (a, b)) = PairMaybeFunctor (fmap f <$> a, fmap f <$> b)
    {-# INLINABLE fmap #-}

instance (Apply f, Apply g) => Apply (PairMaybeFunctor f g) where
    (PairMaybeFunctor (a, b)) <.> (PairMaybeFunctor (c, d)) = PairMaybeFunctor (liftA2 (Data.Functor.Apply.<.>) a c, liftA2 (Data.Functor.Apply.<.>) b d)
    {-# INLINABLE (<.>) #-}

instance (Applicative f, Applicative g) => Applicative (PairMaybeFunctor f g) where
    pure a = PairMaybeFunctor (Just $ pure a, Just $ pure a)
    {-# INLINABLE pure #-}

    (PairMaybeFunctor (a, b)) <*> (PairMaybeFunctor (c, d)) = PairMaybeFunctor (liftA2 (<*>) a c, liftA2 (<*>) b d)
    {-# INLINABLE (<*>) #-}

instance (Contravariant f, Contravariant g) => Contravariant (PairMaybeFunctor f g) where
    contramap f (PairMaybeFunctor (a, b)) = PairMaybeFunctor (contramap f <$> a, contramap f <$> b)
    {-# INLINABLE contramap #-}

fstLensLike :: LensLike (PairMaybeFunctor f g) s t a b -> LensLike f s t a b
-- fromJust is safe here as the constructor is hidden and we've definitely filled in the fst item of PairMaybeFunctor
fstLensLike l f b = fromJust . fst . getPairMaybeFunctor $ l (\a -> PairMaybeFunctor (Just $ f a, Nothing)) b
{-# INLINABLE fstLensLike #-}

sndLensLike :: LensLike (PairMaybeFunctor f g) s t a b -> LensLike g s t a b
-- fromJust is safe here as the constructor is hidden and we've definitely filled in the snd item of PairMaybeFunctor
sndLensLike l f b = fromJust . snd . getPairMaybeFunctor $ l (\a -> PairMaybeFunctor (Nothing, Just $ f a)) b
{-# INLINABLE sndLensLike #-}