{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE DeriveFoldable             #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

{-# OPTIONS_HADDOCK not-home #-}

module Colonnade.Internal
  ( Colonnade(..)
  , OneColonnade(..)
  , Headed(..)
  , Headless(..)
  ) where

import Data.Vector (Vector)
import Data.Functor.Contravariant (Contravariant(..))
import Data.Functor.Contravariant.Divisible (Divisible(..))
import Control.Exception (Exception)
import Data.Typeable (Typeable)
import qualified Data.Vector as Vector

-- | As the first argument to the 'Colonnade' type 
--   constructor, this indictates that the columnar encoding has 
--   a header. This type is isomorphic to 'Identity' but is 
--   given a new name to clarify its intent:
--
-- > example :: Colonnade Headed Text Foo
--
--   The term @example@ represents a columnar encoding of @Foo@
--   in which the columns have headings.
newtype Headed a = Headed { getHeaded :: a }
  deriving (Eq,Ord,Functor,Show,Read,Foldable)

-- | As the first argument to the 'Colonnade' type 
--   constructor, this indictates that the columnar encoding does not have 
--   a header. This type is isomorphic to 'Proxy' but is 
--   given a new name to clarify its intent:
--
-- > example :: Colonnade Headless Text Foo
--
--   The term @example@ represents a columnar encoding of @Foo@
--   in which the columns do not have headings.
data Headless a = Headless
  deriving (Eq,Ord,Functor,Show,Read,Foldable)

instance Contravariant Headless where
  contramap _ Headless = Headless

-- | Encodes a header and a cell.
data OneColonnade h content a = OneColonnade
  { oneColonnadeHead   :: !(h content)
  , oneColonnadeEncode :: !(a -> content)
  }

instance Contravariant (OneColonnade h content) where
  contramap f (OneColonnade h e) = OneColonnade h (e . f)

-- | An columnar encoding of @a@. The type variable @h@ determines what
--   is present in each column in the header row. It is typically instantiated
--   to 'Headed' and occasionally to 'Headless'. There is nothing that
--   restricts it to these two types, although they satisfy the majority
--   of use cases. The type variable @c@ is the content type. This can
--   be @Text@, @String@, or @ByteString@. In the companion libraries
--   @reflex-dom-colonnade@ and @yesod-colonnade@, additional types
--   that represent HTML with element attributes are provided that serve
--   as the content type. Presented more visually:
--
-- >             +---- Content (Text, ByteString, Html, etc.)
-- >             |
-- >             v
-- > Colonnade h c a
-- >           ^   ^
-- >           |   |
-- >           |   +-- Value consumed to build a row
-- >           |
-- >           +------ Headedness (Headed or Headless)
--
--   Internally, a 'Colonnade' is represented as a 'Vector' of individual
--   column encodings. It is possible to use any collection type with
--   'Alternative' and 'Foldable' instances. However, 'Vector' was chosen to
--   optimize the data structure for the use case of building the structure
--   once and then folding over it many times. It is recommended that
--   'Colonnade's are defined at the top-level so that GHC avoids reconstructing
--   them every time they are used.
newtype Colonnade h c a = Colonnade
  { getColonnade :: Vector (OneColonnade h c a)
  } deriving (Monoid)

instance Contravariant (Colonnade h content) where
  contramap f (Colonnade v) = Colonnade
    (Vector.map (contramap f) v)

instance Divisible (Colonnade h content) where
  conquer = Colonnade Vector.empty
  divide f (Colonnade a) (Colonnade b) =
    Colonnade $ (Vector.++)
      (Vector.map (contramap (fst . f)) a)
      (Vector.map (contramap (snd . f)) b)