{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}

-- | Framework for rendering things (or list of things, or things that contain things) that have metadata/header information as well as values.
module Glaze where

import Control.Lens
import Data.Proxy

-- | Glaze is something that knows how to render some header information
-- and given a value, how to render the value.
-- Ie, the value has been glazeed with meta information.
data Glaze a r = Glaze
    { renderedMeta :: r
    , valueRenderer :: a -> r
    } deriving (Functor)

-- FIXME: is this lawful?
instance Applicative (Glaze a) where
    pure a = Glaze a (const a)
    x <*> y = Glaze
        (renderedMeta x $ renderedMeta y)
        (\a -> valueRenderer x a $ valueRenderer y a)

-- | Given a wrapping function for the meta and renderer value,
-- and Glaze instructions for a, run them all together.
unglazeWith :: (r -> r -> b) -> Glaze a r -> a -> b
unglazeWith mainWrapper rdr a =
    mainWrapper (renderedMeta rdr) (valueRenderer rdr a)

-- | Lifts glazing function into an Applicative
-- This is used to transform functions like 'glazeList' into 'glazeListA'
glazedA
    :: (Traversable t, Applicative m)
    => (wrapper -> meta -> t rs -> r) -> m wrapper -> m meta -> t (m rs) -> m r
glazedA f wrapper meta rs = f <$> wrapper <*> meta <*> sequenceA rs

-- | This can be used to make a Glaze for a list of things to render as a table.
-- Given (mainWrapper :: rows -> final render, headerRowWrapper:: fields -> header row, valueRowWrapper -> value row)
-- the rendered meta, and a list of @Glaze a@ columns to use, transform it to a Glaze for a list of as.
glazeList :: ([row] -> r, [field] -> row, [field] -> row) -- how to merge subresults
    -> r -- meta for the final glaze
    -> [Glaze a field] -- descriptions of columns to use
    -> Glaze [a] r -- know how to render list of as.
glazeList (mainWrapper, headerRowWrapper, valueRowWrapper) meta rs =
    Glaze meta glazeList'
  where
    -- rs' :: Glaze a [field]
    rs' = sequenceA rs
    glazeList' as =
        mainWrapper $
        headerRowWrapper (renderedMeta rs') :
        (valueRowWrapper . valueRenderer rs' <$> as)

-- | Applicative version of glazeList
glazeListA
    :: Applicative f
    => f ([row] -> r, [field] -> row, [field] -> row)
    -> f r
    -> [f (Glaze a field)]
    -> f (Glaze [a] r)
glazeListA = glazedA glazeList

-- | This can be used to generate a Glaze for a larger supercomponent.
-- Given (wrapper :: list of (meta, rendered value) to final render)
-- the rendered meta, and a list of @Glaze a@ field to use, transform it to a Glaze for a single a.
-- In this case, use 'reglaze' to generate @Glaze a@ for each subcomponent to use as the list of @Glaze a@
glazeFields :: ([(b, b)] -> r) -- how to merge results
    -> r -- meta for this glaze
    -> [Glaze a b] -- description of sub components to use
    -> Glaze a r -- know how to render the composite a
glazeFields wrapper meta rs =
    Glaze meta (\a -> wrapper $ (\r -> (renderedMeta r, valueRenderer r a)) <$> rs)

-- | Applicative versino of glazeFields
glazeFieldsA
    :: Applicative f
    => f ([(b, b)] -> r) -> f r -> [f (Glaze a b)] -> f (Glaze a r)
glazeFieldsA = glazedA glazeFields

-- | Makes a glaze of the type of a larger component, but only using
-- the information of a smaller component.
-- Given a Glaze and Lens into subcomponent a, create the glaze of the type of the larger component.
reglaze :: Glaze a r -> Getter s a -> Glaze s r
reglaze rdr lns = Glaze
    (renderedMeta rdr)
    (\s -> valueRenderer rdr (s ^. lns))

-- | Applicative version of reglaze
reglazeA :: Applicative f => f (Glaze a r) -> Getter s a -> f (Glaze s r)
reglazeA rdr lns = (\rdr' -> Glaze
        (renderedMeta rdr')
        (\s -> valueRenderer rdr' (s ^. lns))) <$> rdr