-- |
-- Module      : Data.OpenADT.VarF
-- Copyright   : Copyright (c) Jordan Woehr, 2018
-- License     : BSD
-- Maintainer  : Jordan Woehr
-- Stability   : experimental
--
-- This module defines the 'VarF' type and related functions and instances.
-- This type wraps a variant of types that have all had the same type applied
-- to them. Most often this will be a variant constructed with a row of
-- functors.

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

module Data.OpenADT.VarF where

import           Control.Arrow                            ( (+++) )
import           Data.Constraint
import           Data.Functor.Classes
import           Data.Functor.Const
import           Data.Functor.Product
import           Data.Maybe                               ( fromMaybe )
import           Data.Proxy
import           Data.Row
import           Data.Row.Internal
import           Data.Row.Variants

-- | Apply a type to a 'Row'.
type family ApplyRow (x :: *) (r :: Row (* -> *)) :: Row * where
  ApplyRow x ('R lt) = 'R (ApplyLT x lt)

-- | Apply a type to each element of an 'LT'.
type family ApplyLT (x :: *) (r :: [LT (* -> *)]) :: [LT *] where
  ApplyLT _ '[] = '[]
  ApplyLT x (l ':-> f ': fs) = ((l ':-> f x) ': ApplyLT x fs)

-- | A newtype that wraps a variant. The variant is a row made up of
--   __(* -> *)__ that all have the type __x__ applied to them with 'ApplyRow'.
newtype VarF (r :: Row (* -> *)) x = VarF { unVarF :: Var (ApplyRow x r) }

deriving instance Forall (ApplyRow x r) Eq => Eq (VarF r x)
deriving instance Forall (ApplyRow x r) Show => Show (VarF r x)

-- | A helper for writing functions with 'metamorph''. This type reverses the
--   argument order of 'VarF' so the 'Row' parameter is last.
newtype VarF' x (r :: Row (* -> *)) = VarF' { unVarF' :: Var (ApplyRow x r) }

-- | A helper for writing functions with 'metamorph''. This type wraps an
--   __f a__ but takes the type arguments in the order __a f__.
newtype FlipApp (a :: *) (f :: * -> *) = FlipApp (f a)

-- | Apply a function to the variant within a 'VarF'.
--
-- @since 1.0.0
mapVarF :: (Var (ApplyRow x u) -> Var (ApplyRow x v)) -> VarF u x -> VarF v x
mapVarF f (VarF v) = VarF (f v)

-- | This function is useful for implementing functions that are used as
--   catamorphisms, and sometimes 'VarF' instances. The function applies its
--   first argument to whatever variant is wrapped by __VarF r x__ provided all
--   elements of the row __r__ are constrained by __c__.
--
--   For an example, see the 'Show1' instance implementation.
--
-- @since 1.0.0
varFAlg
  :: forall (c :: (* -> *) -> Constraint) (r :: Row (* -> *)) (x :: *) (y :: *)
   . (Forall r c)
  => (forall f . (c f) => f x -> y)
  -> VarF r x
  -> y
varFAlg f =
  getConst
    . metamorph' @_ @r @c @(VarF' x) @(Const y) @(FlipApp x) Proxy
                                                             doNil
                                                             doUncons
                                                             doCons
    . VarF'
    . unVarF
 where
  doNil = impossible . unVarF'

  doUncons l = (FlipApp +++ VarF') . flip trial l . unVarF'

  doCons
    :: forall  τ ρ
     . (c τ)
    => Label 
    -> Either (FlipApp x τ) (Const y ( 'R ρ))
    -> Const y ( 'R ( ':-> τ ': ρ))
  doCons _ (Left  (FlipApp v)) = Const (f v)
  doCons _ (Right (Const   y)) = Const y

-- | The same as 'varFAlg', but with the constraint fixed to 'Unconstrained1'.
--
-- @since 1.0.0
varFAlg'
  :: forall (r :: Row (* -> *)) (x :: *) (y :: *)
   . (Forall r Unconstrained1)
  => (forall f . (Unconstrained1 f) => f x -> y)
  -> VarF r x
  -> y
varFAlg' = varFAlg @Unconstrained1 @r @x @y

-- | RowFromTo fs b := for (l,a) in fs; SUM [ l :-> (a -> b) ]
type family RowFromTo (a :: Row *) (b :: *) :: Row * where
  RowFromTo ('R r) b = 'R (RowFromToR r b)

-- | 'RowFromTo' over a list of 'LT'.
type family RowFromToR (a :: [LT *]) (b :: *) :: [LT *] where
  RowFromToR '[] x = '[]
  RowFromToR (l ':-> a ': rs) b = l ':-> (a -> b) ': RowFromToR rs b

-- | Given a record of functions, use those functions to remove the
-- corresponding rows from the input. Type errors will ensue if the record
-- contains fields of the output variant.
--
-- @since 1.0.0
reduceVarF
  :: forall r s t x r' s' t'
   . ( t  r .\\ s
     , r' ~ ApplyRow x r
     , s' ~ ApplyRow x s
     , s'  r' .\\ t'
     , t'  r' .\\ s'
     , Disjoint s' t'
     , Switch t' (RowFromTo t' (VarF s x)) (VarF s x)
     )
  => Rec (RowFromTo t' (VarF s x))
  -> VarF r x
  -> VarF s x
reduceVarF f (VarF v) = case multiTrial @t' @r' v of
  Left  x -> caseon f x
  Right x -> VarF x

instance Forall r Functor => Functor (VarF r) where
  fmap :: forall a b. (a -> b) -> VarF r a -> VarF r b
  fmap f = VarF . unVarF' . go . VarF' . unVarF

    where
      go = metamorph' @_ @r @Functor @(VarF' a) @(VarF' b) @(FlipApp a)
             Proxy doNil doUncons doCons

      doNil = impossible . unVarF'

      doUncons l = (FlipApp +++ VarF') . flip trial l . unVarF'

      doCons :: forall l f s. (KnownSymbol l, Functor f)
             => Label l
             -> Either (FlipApp a f) (VarF' b ('R s))
             -> VarF' b ('R (l ':-> f ': s))
      doCons l (Left (FlipApp x)) = VarF' $ unsafeMakeVar l $ f <$> x
      doCons _ (Right (VarF' v)) = VarF' $ unsafeInjectFront v

instance Forall r Eq1 => Eq1 (VarF r) where
  liftEq :: forall a b. (a -> b -> Bool) -> VarF r a -> VarF r b -> Bool
  liftEq f (VarF x) (VarF y) = fromMaybe False $ getConst $ metamorph' @_ @r @Eq1
      @(Product (VarF' a) (VarF' b)) @(Const (Maybe Bool)) @(Const (Maybe Bool))
      Proxy doNil doUncons doCons (Pair (VarF' x) (VarF' y))

    where doNil :: Product (VarF' a) (VarF' b) Empty
                -> Const (Maybe Bool) (Empty :: Row (* -> *))
          doNil _ = Const Nothing

          doUncons :: forall  τ ρ. (KnownSymbol , Eq1 τ)
                   => Label 
                   -> Product (VarF' a) (VarF' b) ('R ( ':-> τ ': ρ))
                   -> Either (Const (Maybe Bool) τ)
                             (Product (VarF' a) (VarF' b) ('R ρ))
          doUncons l (Pair (VarF' r1) (VarF' r2)) =
            case (trial r1 l, trial r2 l) of
              (Left u, Left v)   -> Left $ Const $ Just $ liftEq f u v
              (Right u, Right v) -> Right $ Pair (VarF' u) (VarF' v)
              _                  -> Left $ Const Nothing

          doCons :: forall  (τ :: * -> *) ρ
                  . Label 
                 -> Either (Const (Maybe Bool) τ) (Const (Maybe Bool) ('R ρ))
                 -> Const (Maybe Bool) ('R ( ':-> τ ': ρ))
          doCons _ (Left (Const w))  = Const w
          doCons _ (Right (Const w)) = Const w

instance Forall r Show1 => Show1 (VarF r) where
  liftShowsPrec ::
    forall a. (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> VarF r a -> ShowS
  liftShowsPrec sa sl p =
    let f :: forall f. (Show1 f) => f a -> ShowS
        f x = showParen (p > 10) (showString "VarF " . liftShowsPrec sa sl p x)
    in varFAlg @Show1 @r @a @ShowS f

-- | A type constraint synonym for convenience that can be used in, for
--   example, patterns. The variables __r__ (representing a Row) and __v__
--   (representing the type applied to __f__) are generally left abstract. The
--   variable __l__ is the label corresponding to __f v__.
--
--   The order of variables are in the same order as the equality constraint
--   in the synonym, making it easy to remember.
--
-- @since 1.0.0
type OpenAlg r l f v = ( ApplyRow v r .! l  f v
                       , AllUniqueLabels (ApplyRow v r)
                       )