{-# LANGUAGE ConstraintKinds      #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Data.Avro.HasAvroSchema where

import qualified Data.Array           as Ar
import           Data.Avro.Schema     as S
import           Data.Avro.Types      as T
import qualified Data.ByteString      as B
import           Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict  as HashMap
import           Data.Int
import           Data.Ix              (Ix)
import           Data.List.NonEmpty   (NonEmpty (..))
import qualified Data.Map             as Map
import           Data.Monoid          ((<>))
import           Data.Proxy
import qualified Data.Set             as S
import           Data.Tagged
import           Data.Text            (Text)
import qualified Data.Text            as Text
import qualified Data.Text.Lazy       as TL
import qualified Data.Vector          as V
import qualified Data.Vector.Unboxed  as U
import           Data.Word

class HasAvroSchema a where
  schema :: Tagged a Type

schemaOf :: (HasAvroSchema a) => a -> Type
schemaOf = witness schema

instance HasAvroSchema Word8 where
  schema = Tagged S.Int

instance HasAvroSchema Word16 where
  schema = Tagged S.Int

instance HasAvroSchema Word32 where
  schema = Tagged S.Long

instance HasAvroSchema Word64 where
  schema = Tagged S.Long

instance HasAvroSchema Bool where
  schema = Tagged S.Boolean

instance HasAvroSchema () where
  schema = Tagged S.Null

instance HasAvroSchema Int where
  schema = Tagged S.Long

instance HasAvroSchema Int8 where
  schema = Tagged S.Int

instance HasAvroSchema Int16 where
  schema = Tagged S.Int

instance HasAvroSchema Int32 where
  schema = Tagged S.Int

instance HasAvroSchema Int64 where
  schema = Tagged S.Long

instance HasAvroSchema Double where
  schema = Tagged S.Double

instance HasAvroSchema Float where
  schema = Tagged S.Float

instance HasAvroSchema Text.Text where
  schema = Tagged S.String

instance HasAvroSchema TL.Text where
  schema = Tagged S.String

instance HasAvroSchema B.ByteString where
  schema = Tagged S.Bytes

instance HasAvroSchema BL.ByteString where
  schema = Tagged S.Bytes

instance (HasAvroSchema a, HasAvroSchema b) => HasAvroSchema (Either a b) where
  schema = Tagged $ mkUnion (untag (schema :: Tagged a Type) :| [untag (schema :: Tagged b Type)])

instance (HasAvroSchema a) => HasAvroSchema (Map.Map Text a) where
  schema = wrapTag S.Map (schema :: Tagged a Type)

instance (HasAvroSchema a) => HasAvroSchema (HashMap.HashMap Text a) where
  schema = wrapTag S.Map (schema :: Tagged a Type)

instance (HasAvroSchema a) => HasAvroSchema (Map.Map TL.Text a) where
  schema = wrapTag S.Map (schema :: Tagged a Type)

instance (HasAvroSchema a) => HasAvroSchema (HashMap.HashMap TL.Text a) where
  schema = wrapTag S.Map (schema :: Tagged a Type)

instance (HasAvroSchema a) => HasAvroSchema (Map.Map String a) where
  schema = wrapTag S.Map (schema :: Tagged a Type)

instance (HasAvroSchema a) => HasAvroSchema (HashMap.HashMap String a) where
  schema = wrapTag S.Map (schema :: Tagged a Type)

instance (HasAvroSchema a) => HasAvroSchema (Maybe a) where
  schema = Tagged $ mkUnion (S.Null:| [untag (schema :: Tagged a Type)])

instance (HasAvroSchema a) => HasAvroSchema [a] where
  schema = wrapTag S.Array (schema :: Tagged a Type)

instance (HasAvroSchema a, Ix i) => HasAvroSchema (Ar.Array i a) where
  schema = wrapTag S.Array (schema :: Tagged a Type)

instance HasAvroSchema a => HasAvroSchema (V.Vector a) where
  schema = wrapTag S.Array (schema :: Tagged a Type)

instance HasAvroSchema a => HasAvroSchema (U.Vector a) where
  schema = wrapTag S.Array (schema :: Tagged a Type)

instance HasAvroSchema a => HasAvroSchema (S.Set a) where
  schema = wrapTag S.Array (schema :: Tagged a Type)

wrapTag :: (Type -> Type) -> Tagged a Type -> Tagged b Type
wrapTag f = Tagged . f . untag
{-# INLINE wrapTag #-}