{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -Wno-partial-type-signatures #-}
{-# OPTIONS_HADDOCK hide #-}

module Data.Unrestricted.Linear.Internal.Consumable
  ( -- * Consumable
    Consumable (..),
    lseq,
    seqUnit,

    -- * Generic deriving
    GConsumable,
    genericConsume,
  )
where

import Data.List.NonEmpty (NonEmpty)
import qualified Data.Monoid as Monoid
import qualified Data.Replicator.Linear.Internal as Replicator
import qualified Data.Replicator.Linear.Internal.ReplicationStream as ReplicationStream
import qualified Data.Semigroup as Semigroup
import Data.Unrestricted.Linear.Internal.Ur
import qualified Data.Vector as Vector
import Data.Void (Void)
import GHC.Tuple (Solo)
import GHC.Types (Multiplicity (..))
import Prelude.Linear.Generically
import Prelude.Linear.Internal
import qualified Unsafe.Linear as Unsafe
import qualified Prelude as Prelude

class Consumable a where
  consume :: a %1 -> ()

-- | Consume the unit and return the second argument.
-- This is like 'seq' but since the first argument is restricted to be of type
-- @()@ it is consumed, hence @seqUnit@ is linear in its first argument.
seqUnit :: () %1 -> b %1 -> b
seqUnit () b = b

-- | Consume the first argument and return the second argument.
-- This is like 'seq' but the first argument is restricted to be 'Consumable'.
lseq :: (Consumable a) => a %1 -> b %1 -> b
lseq a b = seqUnit (consume a) b

infixr 0 `lseq` -- same fixity as base.seq

-- ----------------
-- Instances

instance Consumable (ReplicationStream.ReplicationStream a) where
  consume = ReplicationStream.consume

instance Consumable (Replicator.Replicator a) where
  consume = Replicator.consume

instance (Consumable a) => Consumable (Vector.Vector a) where
  consume xs = consume (Unsafe.toLinear Vector.toList xs)

-- Prelude and primitive instances

deriving via
  Generically Prelude.Char
  instance
    Consumable Prelude.Char

deriving via
  Generically Prelude.Double
  instance
    Consumable Prelude.Double

deriving via
  Generically Prelude.Float
  instance
    Consumable Prelude.Float

deriving via
  Generically Prelude.Int
  instance
    Consumable Prelude.Int

deriving via
  Generically Prelude.Word
  instance
    Consumable Prelude.Word

deriving via
  Generically Prelude.Ordering
  instance
    Consumable Prelude.Ordering

deriving via
  Generically Prelude.Bool
  instance
    Consumable Prelude.Bool

deriving via
  Generically ()
  instance
    Consumable ()

instance Consumable Void where
  consume = \case {}

deriving via
  Generically (Solo a)
  instance
    (_) => Consumable (Solo a)

deriving via
  Generically (a, b)
  instance
    (_) => Consumable (a, b)

deriving via
  Generically (a, b, c)
  instance
    (_) => Consumable (a, b, c)

deriving via
  Generically (a, b, c, d)
  instance
    (_) => Consumable (a, b, c, d)

deriving via
  Generically (a, b, c, d, e)
  instance
    (_) => Consumable (a, b, c, d, e)

deriving via
  Generically (Prelude.Maybe a)
  instance
    (_) => Consumable (Prelude.Maybe a)

deriving via
  Generically (Prelude.Either e a)
  instance
    (_) => Consumable (Prelude.Either e a)

deriving via
  Generically [a]
  instance
    (_) => Consumable [a]

deriving via
  Generically (NonEmpty a)
  instance
    (_) => Consumable (NonEmpty a)

deriving via
  Generically (Ur a)
  instance
    Consumable (Ur a)

-- Data.Semigroup instances

deriving via
  Generically (Semigroup.Arg a b)
  instance
    (_) => Consumable (Semigroup.Arg a b)

deriving newtype instance (_) => Consumable (Semigroup.Min a)

deriving newtype instance (_) => Consumable (Semigroup.Max a)

deriving newtype instance (_) => Consumable (Semigroup.First a)

deriving newtype instance (_) => Consumable (Semigroup.Last a)

deriving newtype instance (_) => Consumable (Semigroup.WrappedMonoid a)

deriving newtype instance (_) => Consumable (Semigroup.Dual a)

deriving newtype instance Consumable Semigroup.All

deriving newtype instance Consumable Semigroup.Any

deriving newtype instance (_) => Consumable (Semigroup.Sum a)

deriving newtype instance (_) => Consumable (Semigroup.Product a)

-- Data.Monoid instances

deriving newtype instance (_) => Consumable (Monoid.First a)

deriving newtype instance (_) => Consumable (Monoid.Last a)

deriving newtype instance (_) => Consumable (Monoid.Alt f a)

deriving newtype instance (_) => Consumable (Monoid.Ap f a)

-- ----------------
-- Generic deriving

instance (Generic a, GConsumable (Rep a)) => Consumable (Generically a) where
  consume (Generically x) = genericConsume x

genericConsume :: (Generic a, GConsumable (Rep a)) => a %1 -> ()
genericConsume = gconsume . from
{-# INLINEABLE genericConsume #-}

-- | A class for generic representations that can be consumed.
class GConsumable f where
  gconsume :: f p %1 -> ()

instance GConsumable V1 where
  gconsume = \case {}
  {-# INLINE gconsume #-}

instance GConsumable U1 where
  gconsume U1 = ()
  {-# INLINE gconsume #-}

instance (GConsumable f, GConsumable g) => GConsumable (f :+: g) where
  gconsume (L1 a) = gconsume a
  gconsume (R1 a) = gconsume a
  {-# INLINE gconsume #-}

instance (GConsumable f, GConsumable g) => GConsumable (f :*: g) where
  gconsume (a :*: b) = gconsume a `seqUnit` gconsume b
  {-# INLINE gconsume #-}

instance (Consumable c) => GConsumable (K1 i c) where
  gconsume (K1 c) = consume c
  {-# INLINE gconsume #-}

instance (GConsumable f) => GConsumable (M1 i t f) where
  gconsume (M1 a) = gconsume a
  {-# INLINE gconsume #-}

-- This split is a bit awkward. We'd like to be able to *default*
-- the multiplicity to `Many` when it's polymorphic. We'll be able
-- to do that once the Exportable Named Defaults Proposal
-- (https://github.com/ghc-proposals/ghc-proposals/pull/409#issuecomment-931839874)
-- has been implemented. The same goes for Dupable and Movable.
instance GConsumable (MP1 'Many f) where
  gconsume (MP1 _) = ()
  {-# INLINE gconsume #-}

instance (GConsumable f) => GConsumable (MP1 'One f) where
  gconsume (MP1 x) = gconsume x
  {-# INLINE gconsume #-}

-- Instances for unlifted generic representations
--
-- /!\ 'Char#', 'Double#', 'Float#', 'Int#', 'Word#' are unboxed data-types,
-- and therefore they cannot have any linear values hidden in a closure
-- anywhere. Therefore it is safe to call non-linear functions linearly on
-- these types. We refrain from including a 'GConsumable' instance for 'UAddr'
-- for the moment, as that seems potentially confusing—pointers usually
-- must be created, duplicated, and destroyed rather carefully. /!\

instance GConsumable UChar where
  gconsume (UChar x) = Unsafe.toLinear (\_ -> ()) x

instance GConsumable UDouble where
  gconsume (UDouble x) = Unsafe.toLinear (\_ -> ()) x

instance GConsumable UFloat where
  gconsume (UFloat x) = Unsafe.toLinear (\_ -> ()) x

instance GConsumable UInt where
  gconsume (UInt x) = Unsafe.toLinear (\_ -> ()) x

instance GConsumable UWord where
  gconsume (UWord x) = Unsafe.toLinear (\_ -> ()) x
