module Data.Curve
  (
  -- * Elliptic curves
    Curve(..)
  , mul
  , mul'
  -- ** Elliptic curve forms
  , Form(..)
  -- ** Elliptic curve coordinates
  , Coordinates(..)
  ) where

import Protolude

import Control.Monad.Random (MonadRandom, Random(..), getRandom)
import Data.Field.Galois (GaloisField, PrimeField, fromP)
import Data.Group (Group(..))
import GHC.Natural (Natural)
import Test.Tasty.QuickCheck (Arbitrary(..))
import Text.PrettyPrint.Leijen.Text (Pretty)

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

-- | Elliptic curves.
class (GaloisField q, PrimeField r, Arbitrary (Point f c e q r),
       Eq (Point f c e q r), Generic (Point f c e q r), Group (Point f c e q r),
       NFData (Point f c e q r), Pretty (Point f c e q r), Random (Point f c e q r),
       Show (Point f c e q r)) => Curve (f :: Form) (c :: Coordinates) e q r where
  {-# MINIMAL add, char, cof, dbl, def, disc, frob, fromA, gen,
              id, inv, order, point, pointX, toA, yX #-}

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

  -- Parameters

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

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

  -- | Curve well-defined.
  def :: Point f c e q r -> Bool

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

  -- | Curve order.
  order :: Point f c e q r -> Natural

  -- Operations

  -- | Point addition.
  add :: Point f c e q r -> Point f c e q r -> Point f c e q r

  -- | Point doubling.
  dbl :: Point f c e q r -> Point f c e q r

  -- | Point identity.
  id :: Point f c e q r

  -- | Point inversion.
  inv :: Point f c e q r -> Point f c e q r

  -- Functions

  -- | Frobenius endomorphism.
  frob :: Point f c e q r -> Point f c e q r

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

  -- | Curve generator.
  gen :: Point f c e q r

  -- | 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)

  -- | Random point.
  rnd :: MonadRandom m => m (Point f c e q r)
  rnd = getRandom

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

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

-- | Point multiplication by field element.
mul :: Curve f c e q r => Point f c e q r -> r -> Point f c e q r
mul = (. fromP) . mul'
{-# INLINABLE mul #-}

-- | Point multiplication by integral element.
mul' :: (Curve f c e q r, Integral n) => Point f c e q r -> n -> Point f c e q r
mul' p n
  | n < 0     = inv $ mul' p (-n)
  | n == 0    = id
  | n == 1    = p
  | even n    = p'
  | otherwise = add p p'
  where
    p' = mul' (dbl p) (div n 2)
{-# INLINABLE mul' #-}

{-# SPECIALISE mul' ::
  Curve f c e q r => Point f c e q r -> Int -> Point f c e q r,
  Curve f c e q r => Point f c e q r -> Integer -> Point f c e q r,
  Curve f c e q r => Point f c e q r -> Natural -> Point f c e q r,
  Curve f c e q r => Point f c e q r -> Word -> Point f c 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 groups.
instance Curve f c e q r => Group (Point f c e q r) where

  invert = inv
  {-# INLINABLE invert #-}

  pow = mul'
  {-# INLINABLE pow #-}

-- 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 "Curve.randomR: 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 (<>) #-}