module Group.Field
  ( module Group
  , module Group.Field
  ) where

import Protolude

import Control.Monad.Random (Random(..))
import GaloisField (GaloisField(..))
import Test.Tasty.QuickCheck (Arbitrary(..))
import Text.PrettyPrint.Leijen.Text (Pretty(..))

import Curve (PrimeField')
import Group (Group(..))

-------------------------------------------------------------------------------
-- Types
-------------------------------------------------------------------------------

-- | Field groups.
class (GaloisField q, PrimeField' r) => FGroup r q where
  {-# MINIMAL g_, h_, q_, r_ #-}
  g_ :: Element r q            -- ^ Group generator.
  h_ :: Element r q -> Integer -- ^ Group cofactor.
  q_ :: Element r q -> Integer -- ^ Group characteristic.
  r_ :: Element r q -> Integer -- ^ Group order.

-- | Field elements.
newtype Element r q = F q
  deriving (Eq, Functor, Generic, NFData, Read, Show)

-------------------------------------------------------------------------------
-- Operations
-------------------------------------------------------------------------------

-- Field elements are groups.
instance FGroup r q => Group (Element r q) where

  add = (<>)
  {-# INLINABLE add #-}

  dbl = join (<>)
  {-# INLINABLE dbl #-}

  def (F x) = x /= 0
  {-# INLINABLE def #-}

  gen = g_
  {-# INLINABLE gen #-}

  id = mempty
  {-# INLINABLE id #-}

  inv = (<$>) recip
  {-# INLINABLE inv #-}

  mul' = (. flip pow) . flip (<$>)
  {-# INLINABLE mul' #-}

  order = r_
  {-# INLINABLE order #-}

-- Field elements are monoids.
instance FGroup r q => Monoid (Element r q) where

  mempty = F 1
  {-# INLINABLE mempty #-}

-- Field elements are semigroups.
instance FGroup r q => Semigroup (Element r q) where

  F x <> F y = F (x * y)
  {-# INLINABLE (<>) #-}

-------------------------------------------------------------------------------
-- Instances
-------------------------------------------------------------------------------

-- Field elements are arbitrary.
instance FGroup r q => Arbitrary (Element r q) where

  -- Arbitrary group element.
  arbitrary = mul' gen <$> arbitrary
  {- Arbitrary field element.
  arbitrary = suchThatMap arbitrary defX
    where
      defX 0 = Nothing
      defX x = Just (F x)
  -}
  {-# INLINABLE arbitrary #-}

-- Field elements are pretty.
instance FGroup r q => Pretty (Element r q) where

  pretty (F x) = pretty x

-- Field elements are random.
instance FGroup r q => Random (Element r q) where

  -- Random group element.
  random = first (mul' gen) . random
  {- Random field element.
  random g = case random g of
    (0, g') -> random g'
    (x, g') -> (F x, g')
  -}
  {-# INLINABLE random #-}

  randomR  = panic "not implemented."