{-# LANGUAGE NoImplicitPrelude    #-}
{-# LANGUAGE Rank2Types           #-}
{-# LANGUAGE DataKinds            #-}
{-# LANGUAGE KindSignatures       #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE ConstraintKinds      #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverloadedStrings    #-}

module Crypto.Encoding.BIP39.Dictionary
    ( -- ** Dictionary
      Dictionary(..)
    , WordIndex
    , wordIndex
    , unWordIndex

    , DictionaryError(..)
    ) where

import           Basement.NormalForm
import           Basement.Compat.Typeable
import           Basement.Types.OffsetSize (Offset(..))
import           Basement.From (TryFrom(..))
import           Basement.Imports

-- | this discribe the property of the Dictionary and will alllow to
-- convert from a mnemonic phrase to 'MnemonicSentence'
--
-- This is especially needed to build the BIP39 'Seed'
--
data Dictionary = Dictionary
    { Dictionary -> WordIndex -> String
dictionaryIndexToWord :: WordIndex -> String
      -- ^ This function will retrieve the mnemonic word associated to the
      -- given 'WordIndex'.
    , Dictionary -> String -> Either DictionaryError WordIndex
dictionaryWordToIndex :: String -> Either DictionaryError WordIndex
      -- ^ This function will retrieve the 'WordIndex' from a given mnemonic
      -- word.
    , Dictionary -> String -> Bool
dictionaryTestWord :: String -> Bool
      -- ^ test a given word is in the dictionary
    , Dictionary -> String
dictionaryWordSeparator :: String
      -- ^ joining string (e.g. space for english)
    }
  deriving (Typeable)

-- | Index of the mnemonic word in the 'Dictionary'
--
-- 'WordIndex' are within range of [0..2047]
--
newtype WordIndex = WordIndex { WordIndex -> Offset String
unWordIndex :: Offset String }
    deriving (Int -> WordIndex -> ShowS
[WordIndex] -> ShowS
WordIndex -> String
(Int -> WordIndex -> ShowS)
-> (WordIndex -> String)
-> ([WordIndex] -> ShowS)
-> Show WordIndex
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [WordIndex] -> ShowS
$cshowList :: [WordIndex] -> ShowS
show :: WordIndex -> String
$cshow :: WordIndex -> String
showsPrec :: Int -> WordIndex -> ShowS
$cshowsPrec :: Int -> WordIndex -> ShowS
Show, WordIndex -> WordIndex -> Bool
(WordIndex -> WordIndex -> Bool)
-> (WordIndex -> WordIndex -> Bool) -> Eq WordIndex
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: WordIndex -> WordIndex -> Bool
$c/= :: WordIndex -> WordIndex -> Bool
== :: WordIndex -> WordIndex -> Bool
$c== :: WordIndex -> WordIndex -> Bool
Eq, Eq WordIndex
Eq WordIndex
-> (WordIndex -> WordIndex -> Ordering)
-> (WordIndex -> WordIndex -> Bool)
-> (WordIndex -> WordIndex -> Bool)
-> (WordIndex -> WordIndex -> Bool)
-> (WordIndex -> WordIndex -> Bool)
-> (WordIndex -> WordIndex -> WordIndex)
-> (WordIndex -> WordIndex -> WordIndex)
-> Ord WordIndex
WordIndex -> WordIndex -> Bool
WordIndex -> WordIndex -> Ordering
WordIndex -> WordIndex -> WordIndex
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: WordIndex -> WordIndex -> WordIndex
$cmin :: WordIndex -> WordIndex -> WordIndex
max :: WordIndex -> WordIndex -> WordIndex
$cmax :: WordIndex -> WordIndex -> WordIndex
>= :: WordIndex -> WordIndex -> Bool
$c>= :: WordIndex -> WordIndex -> Bool
> :: WordIndex -> WordIndex -> Bool
$c> :: WordIndex -> WordIndex -> Bool
<= :: WordIndex -> WordIndex -> Bool
$c<= :: WordIndex -> WordIndex -> Bool
< :: WordIndex -> WordIndex -> Bool
$c< :: WordIndex -> WordIndex -> Bool
compare :: WordIndex -> WordIndex -> Ordering
$ccompare :: WordIndex -> WordIndex -> Ordering
$cp1Ord :: Eq WordIndex
Ord, Typeable, WordIndex -> ()
(WordIndex -> ()) -> NormalForm WordIndex
forall a. (a -> ()) -> NormalForm a
toNormalForm :: WordIndex -> ()
$ctoNormalForm :: WordIndex -> ()
NormalForm)
instance Enum WordIndex where
    toEnum :: Int -> WordIndex
toEnum = Offset String -> WordIndex
wordIndex (Offset String -> WordIndex)
-> (Int -> Offset String) -> Int -> WordIndex
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Int -> Offset String
forall a. Enum a => Int -> a
toEnum
    fromEnum :: WordIndex -> Int
fromEnum = Offset String -> Int
forall a. Enum a => a -> Int
fromEnum (Offset String -> Int)
-> (WordIndex -> Offset String) -> WordIndex -> Int
forall k (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. WordIndex -> Offset String
unWordIndex
    succ :: WordIndex -> WordIndex
succ (WordIndex (Offset Int
v))
        | Int
v Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
2047 = Offset String -> WordIndex
WordIndex (Int -> Offset String
forall ty. Int -> Offset ty
Offset (Int -> Int
forall a. Enum a => a -> a
succ Int
v))
        | Bool
otherwise = String -> WordIndex
forall a. HasCallStack => String -> a
error String
"WordIndex out of bound"
    pred :: WordIndex -> WordIndex
pred (WordIndex (Offset Int
v))
        | Int
v Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0 = String -> WordIndex
forall a. HasCallStack => String -> a
error String
"WordIndex out of bound"
        | Bool
otherwise = Offset String -> WordIndex
WordIndex (Int -> Offset String
forall ty. Int -> Offset ty
Offset (Int -> Int
forall a. Enum a => a -> a
pred Int
v))
instance Bounded WordIndex where
    minBound :: WordIndex
minBound = Offset String -> WordIndex
WordIndex (Int -> Offset String
forall ty. Int -> Offset ty
Offset Int
0)
    maxBound :: WordIndex
maxBound = Offset String -> WordIndex
WordIndex (Int -> Offset String
forall ty. Int -> Offset ty
Offset Int
2047)
instance TryFrom (Offset String) WordIndex where
    tryFrom :: Offset String -> Maybe WordIndex
tryFrom Offset String
w
        | Offset String
w Offset String -> Offset String -> Bool
forall a. Ord a => a -> a -> Bool
< Offset String
2048  = WordIndex -> Maybe WordIndex
forall a. a -> Maybe a
Just (Offset String -> WordIndex
WordIndex Offset String
w)
        | Bool
otherwise = Maybe WordIndex
forall a. Maybe a
Nothing
instance TryFrom Int WordIndex where
    tryFrom :: Int -> Maybe WordIndex
tryFrom Int
v
        | Int
v Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0    = Offset String -> Maybe WordIndex
forall a b. TryFrom a b => a -> Maybe b
tryFrom (Int -> Offset String
forall ty. Int -> Offset ty
Offset Int
v :: Offset String)
        | Bool
otherwise = Maybe WordIndex
forall a. Maybe a
Nothing

wordIndex :: Offset String -> WordIndex
wordIndex :: Offset String -> WordIndex
wordIndex Offset String
w = case Offset String -> Maybe WordIndex
forall a b. TryFrom a b => a -> Maybe b
tryFrom Offset String
w of
    Maybe WordIndex
Nothing -> String -> WordIndex
forall a. HasCallStack => String -> a
error (String
"Error: word index should be between 0 to 2047. " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Offset String -> String
forall a. Show a => a -> String
show Offset String
w)
    Just WordIndex
wi -> WordIndex
wi

-- -------------------------------------------------------------------------- --
-- Errors
-- -------------------------------------------------------------------------- --

data DictionaryError
    = ErrInvalidDictionaryWord String
    deriving (Int -> DictionaryError -> ShowS
[DictionaryError] -> ShowS
DictionaryError -> String
(Int -> DictionaryError -> ShowS)
-> (DictionaryError -> String)
-> ([DictionaryError] -> ShowS)
-> Show DictionaryError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DictionaryError] -> ShowS
$cshowList :: [DictionaryError] -> ShowS
show :: DictionaryError -> String
$cshow :: DictionaryError -> String
showsPrec :: Int -> DictionaryError -> ShowS
$cshowsPrec :: Int -> DictionaryError -> ShowS
Show)