module Curve
  ( module Curve
  , module Group
  ) where

import Protolude

import Control.Monad.Random (Random(..))
import GaloisField (GaloisField)
import PrimeField (PrimeField, toInt)
import Test.Tasty.QuickCheck (Arbitrary(..))

import Group (Group(..))

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

-- | Elliptic curves.
class (GaloisField q, PrimeField' r, Group (Point f c e q r))
  => Curve (f :: Form) (c :: Coordinates) e q r where
  {-# MINIMAL char, cof, disc, fromA, point, pointX, toA, yX #-}

  -- Parameters

  -- | Curve characteristic.
  char :: Point f c e q r -> Integer

  -- | Curve cofactor.
  cof :: Point f c e q r -> Integer

  -- | Curve discriminant.
  disc :: Point f c e q r -> q

  -- Points

  -- | Curve point.
  data family Point f c e q r :: *

  -- | Curve point multiplication.
  mul :: Point f c e q r -> r -> Point f c e q r
  mul = (. toInt') . mul'
  {-# INLINABLE mul #-}

  -- | Get point from X and Y coordinates.
  point :: q -> q -> Maybe (Point f c e q r)

  -- | Get point from X coordinate.
  pointX :: q -> Maybe (Point f c e q r)

  -- | Get Y coordinate from X coordinate.
  yX :: Point f c e q r -> q -> Maybe q

  -- Coordinates

  -- | Transform from affine coordinates.
  fromA :: Point f 'Affine e q r -> Point f c e q r

  -- | Transform to affine coordinates.
  toA :: Point f c e q r -> Point f 'Affine e q r

-- | Curve forms.
data Form = Binary
          | Edwards
          | Montgomery
          | Weierstrass

-- | Curve coordinates.
data Coordinates = Affine
                 | Jacobian
                 | Projective

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

-- Elliptic curve points are arbitrary.
instance Curve f c e q r => Arbitrary (Point f c e q r) where

  -- Arbitrary group element.
  arbitrary = mul gen <$> arbitrary
  {- Arbitrary curve point.
  arbitrary = suchThatMap arbitrary pointX
  -}
  {-# INLINABLE arbitrary #-}

-- Elliptic curve points are monoids.
instance Curve f c e q r => Monoid (Point f c e q r) where

  mempty = id
  {-# INLINABLE mempty #-}

-- Elliptic curve points are random.
instance Curve f c e q r => Random (Point f c e q r) where

  -- Random group element.
  random  = first (mul gen) . random
  {- Random curve point.
  random g = case pointX x of
    Just p -> (p, g')
    _      -> random g'
    where
      (x, g') = random g
  -}
  {-# INLINABLE random #-}

  randomR = panic "not implemented."

-- Elliptic curve points are semigroups.
instance Curve f c e q r => Semigroup (Point f c e q r) where

  p <> q = if p == q then dbl p else add p q
  {-# INLINABLE (<>) #-}

-------------------------------------------------------------------------------
-- Temporary
-------------------------------------------------------------------------------

-- Prime field class.
class GaloisField k => PrimeField' k where
  {-# MINIMAL toInt' #-}

  toInt' :: k -> Integer

-- Prime field instance.
instance KnownNat p => PrimeField' (PrimeField p) where

  toInt' = toInt
  {-# INLINABLE toInt' #-}