module Crypto.Ed25519.Pure (
             PrivateKey
           , PublicKey
           , Signature(..)
           , generatePrivate
           , generatePublic
           , generateKeyPair
           , sign, valid
           -- , makeShared
           -- * Import/Export interface
           , importPublic, importPrivate
           , exportPublic, exportPrivate
           ) where

import Crypto.Random
import Data.ByteString(ByteString)
import qualified Data.ByteString        as B
import qualified Data.ByteString.Unsafe as BU
import Foreign.C.Types
import Foreign.Ptr
import Foreign.Marshal.Alloc (allocaBytes)
import System.IO.Unsafe (unsafePerformIO)

-- |The type of a Ed25519 private key.
newtype PrivateKey = Priv ByteString
            deriving (Show)

-- |The type of a Ed25519 public key.
newtype PublicKey  = Pub ByteString
            deriving (Show)

newtype Signature  = Sig ByteString
            deriving (Show)

-- Randomly generate an Ed25519 private key
generatePrivate :: CryptoRandomGen g => g -> Either GenError (PrivateKey, g)
generatePrivate g =
    case genBytes privateKeySize g of
        Left e          -> Left e
        Right (bs, g')  -> Right (Priv bs, g')

generatePublic :: PrivateKey -> PublicKey
generatePublic (Priv priv) = Pub (ed25519_publickey priv)

generateKeyPair :: CryptoRandomGen g => g -> Either GenError (PrivateKey, PublicKey, g)
generateKeyPair g =
    case generatePrivate g of
        Left e       -> Left e
        Right (q,g2) -> Right (q, generatePublic q, g2)

sign :: ByteString -> PrivateKey -> PublicKey -> Signature
sign = ed25519_sign

-- | Returns true if the signature is valid
valid :: ByteString -> PublicKey -> Signature -> Bool
valid = ed25519_sign_open

--------------------------------------------------------------------------------
--  Lifted-C Functions

ed25519_publickey :: ByteString -> ByteString
ed25519_publickey priv =
    unsafePerformIO $ B.useAsCString priv $ \q -> createByteString publicKeySize (ed25519_publickey_c q)

createByteString :: Int -> (Ptr CChar -> IO ()) -> IO ByteString
createByteString len f = do
       allocaBytes len $ \p -> do
           f p
           B.packCStringLen (p,len)

-- XXX Use c2hs or some such? Probably not worth the overhead to users.
signatureSize :: Int
signatureSize  = 64

publicKeySize, privateKeySize :: Int
publicKeySize  = 32
privateKeySize = 32

ed25519_sign :: ByteString -- ^ Message
             -> PrivateKey
             -> PublicKey
             -> Signature
ed25519_sign msg (Priv q) (Pub p) = unsafePerformIO $ do
    BU.unsafeUseAsCStringLen msg $ \(mp,mlen) -> BU.unsafeUseAsCString q $ \qp -> BU.unsafeUseAsCString p $ \pp ->
        Sig `fmap` (createByteString signatureSize $ \sigPtr -> ed25519_sign_c mp (fromIntegral mlen) qp pp sigPtr)

ed25519_sign_open :: ByteString -- ^ Message
                  -> PublicKey
                  -> Signature
                  -> Bool
ed25519_sign_open bs (Pub p) (Sig s)
    | B.length s /= 64 = False
    | otherwise = unsafePerformIO $ do
    BU.unsafeUseAsCStringLen bs $ \(mp,mlen) -> BU.unsafeUseAsCString p $ \pp -> BU.unsafeUseAsCString s $ \sp ->
        return (0 == ed25519_sign_open_c mp (fromIntegral mlen) pp sp)

-- | Only included in hopes of a 'curve25519_scalarmult' function being
-- added.
curve25519_scalarmult_basepoint :: PrivateKey -> PublicKey
curve25519_scalarmult_basepoint (Priv q) = Pub $ unsafePerformIO $ do
    BU.unsafeUseAsCString q $ \qp -> createByteString 32 (curved25519_scalarmult_basepoint_c qp)

-- curve25519_scalarmult :: PublicKey -> PrivateKey -> ByteString
-- curve25519_scalarmult (Pub p) (Priv q) = unsafePeformIO $ do
--     BU.unsafeUseAsCString q $ \qp -> BU.unsafeUseAsCString p $ \pp -> createByteString multSize $ curved25519_scalarmult_c qp pp

importPublic :: ByteString -> Maybe PublicKey
importPublic bs | B.length bs == 32 = Just (Pub bs)
                | otherwise         = Nothing

exportPublic :: PublicKey -> ByteString
exportPublic (Pub bs) = bs

importPrivate :: ByteString -> Maybe PrivateKey
importPrivate bs | B.length bs == 32 = Just (Priv bs)
                 | otherwise         = Nothing

exportPrivate :: PrivateKey -> ByteString
exportPrivate (Priv bs) = bs

--------------------------------------------------------------------------------
--  C Bindings

foreign import ccall unsafe "ed25519_publickey"
    ed25519_publickey_c :: Ptr CChar -> Ptr CChar -> IO ()

foreign import ccall unsafe "ed25519_sign"
    ed25519_sign_c :: Ptr CChar {- msg -} -> CInt {- mlen -}
                   -> Ptr CChar {- priv key -} -> Ptr CChar {- pub key -}
                   -> Ptr CChar {- signature -} -> IO ()

foreign import ccall unsafe "ed25519_sign_open"
    ed25519_sign_open_c :: Ptr CChar {- msg -} -> CInt {- mlen -}
                        -> Ptr CChar {- pub key -}
                        -> Ptr CChar {- signature -} -> CInt

foreign import ccall unsafe "curved25519_scalarmult_basepoint"
    curved25519_scalarmult_basepoint_c :: Ptr CChar {- pub ey -}
                                       -> Ptr CChar {- priv key -}
                                       -> IO ()