{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies          #-}

module Data.String.Interpolate.Conversion.Classes
  ( B(..)
  , IsCustomSink, InterpSink(..), Interpolatable(..)
  )
where

import Data.Kind  ( Type )
import Data.Proxy

import qualified Data.ByteString         as B
import qualified Data.ByteString.Builder as LB
import qualified Data.ByteString.Lazy    as LB
import qualified Data.Text               as T
import qualified Data.Text.Lazy          as LT
import qualified Data.Text.Lazy.Builder  as LT

-- |
-- We wrap the builders in B so that we can add a phantom type parameter.
-- This gives the inner `interpolate's enough information to know where
-- they're going and pick an instance, forcing all the types into lockstep.
newtype B dst a = B { B dst a -> a
unB :: a }
  deriving (B dst a -> B dst a -> Bool
(B dst a -> B dst a -> Bool)
-> (B dst a -> B dst a -> Bool) -> Eq (B dst a)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall dst a. Eq a => B dst a -> B dst a -> Bool
/= :: B dst a -> B dst a -> Bool
$c/= :: forall dst a. Eq a => B dst a -> B dst a -> Bool
== :: B dst a -> B dst a -> Bool
$c== :: forall dst a. Eq a => B dst a -> B dst a -> Bool
Eq, Int -> B dst a -> ShowS
[B dst a] -> ShowS
B dst a -> String
(Int -> B dst a -> ShowS)
-> (B dst a -> String) -> ([B dst a] -> ShowS) -> Show (B dst a)
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall dst a. Show a => Int -> B dst a -> ShowS
forall dst a. Show a => [B dst a] -> ShowS
forall dst a. Show a => B dst a -> String
showList :: [B dst a] -> ShowS
$cshowList :: forall dst a. Show a => [B dst a] -> ShowS
show :: B dst a -> String
$cshow :: forall dst a. Show a => B dst a -> String
showsPrec :: Int -> B dst a -> ShowS
$cshowsPrec :: forall dst a. Show a => Int -> B dst a -> ShowS
Show)

-- | Does this type require special behavior when something is interpolated /into/ it?
type family IsCustomSink dst where
  IsCustomSink T.Text = 'True
  IsCustomSink LT.Text = 'True
  IsCustomSink LT.Builder = 'True
  IsCustomSink B.ByteString = 'True
  IsCustomSink LB.ByteString = 'True
  IsCustomSink LB.Builder = 'True
  IsCustomSink _ = 'False

-- | Something that can be interpolated into.
class IsCustomSink dst ~ flag => InterpSink (flag :: Bool) dst where
  type Builder flag dst :: Type

  -- | Meant to be used only for verbatim parts of the interpolation.
  ofString :: Proxy flag -> String -> B dst (Builder flag dst)
  -- |
  -- `build' should be 'in-order'; that is, the left builder comes from
  -- a string on the left, and the right builder comes from a string on the right.
  build :: Proxy flag -> B dst (Builder flag dst) -> B dst (Builder flag dst) -> B dst (Builder flag dst)
  finalize :: Proxy flag -> B dst (Builder flag dst) -> dst

-- |
-- Represents that we can interpolate objects of type src into a an
-- interpolation string that returns type dst.
class InterpSink flag dst => Interpolatable (flag :: Bool) src dst where
  interpolate :: Proxy flag -> src -> B dst (Builder flag dst)