{-# LANGUAGE RankNTypes #-}

module Data.Schema
     ( Field
     , Fields
     , field
     , optional
     , alt
     , Schema
     , HasSchema (..)
     , prim
     , const
     , record
     , asList
     , toList
     , oneOf
     , alias
     ) where

import           Control.Functor.HigherOrder
import           Control.Lens
import           Data.Functor.Invariant
import           Data.Hashable               (Hashable)
import           Data.HashMap.Strict         (HashMap)
import qualified Data.List.NonEmpty          as NEL
import           Data.Schema.Internal.Types
import           Data.Text                   (Text)
import           Data.Vector                 (Vector)
import qualified Data.Vector                 as Vector
import           Prelude                     hiding (const, seq)

-- | Define an alternative
alt :: Text -> s b -> Prism' a b -> AltDef s a
alt = AltDef

-- | Define an annotated schema for primitives of type `p`
prim :: p a -> Schema p a
prim primAlg = Schema (HFix $ PrimitiveSchema primAlg)

-- | Define a schema for a type that is always constant
const :: a -> Schema p a
const a = Schema (HFix (RecordSchema $ pure a))

-- | Define the schema of record using the given fields
record :: Fields (Schema p) a -> Schema p a
record ps = Schema (HFix (RecordSchema $ hoistField unwrapSchema ps))

-- | Define the schema of a list based on the element type
asList :: Iso' (Vector a) [a]
asList = iso Vector.toList Vector.fromList

toList :: Schema p (Vector a) -> Schema p [a]
toList = invmap Vector.toList Vector.fromList

-- | Define the schema of an union (coproduct) type based on the given alternatives
oneOf :: [AltDef (Schema p) a] -> Schema p a
oneOf alts = Schema (HFix (UnionSchema $ fmap (hfmap unwrapSchema) $ NEL.fromList alts))

-- | Define an schema alias that is isomorphic to another one using the given ISO transformation
alias :: Iso' a b -> Schema p a -> Schema p b
alias i base = invmap (view i) (view . from $ i) base