module Requirements(
         Operation(..),
         Requirement(..),
         requirements
       )
 where

import Data.List(sort)

data Operation = Add
               | BaseOps
               | Barretts
               | Div
               | ModDiv
               | ModExp
               | ModMul
               | ModSq
               | Mul
               | Shifts
               | Square
               | Sub
               | Convert Int
               | SignedAdd
               | SignedBase
               | SignedCmp
               | SignedShift
               | SignedSub
               | SignedMul
               | SignedDiv
               | SignedModInv
               | SigConvert Int
               | SquareRoot
               | EGCD
               | ModInv
               | PrimeGen
               | RSA
               | DSA
               | ECDSA
 deriving (Eq, Ord, Show)

data Requirement = Req Int Operation
 deriving (Eq, Ord, Show)

data Need = Need Operation (Int -> [Requirement])

needs :: [Need]
needs = [ Need RSA         (\ size -> [Req (size `div` 2) Sub,
                                       Req (size `div` 2) Mul,
                                       Req (size `div` 2) PrimeGen,
                                       Req size           BaseOps,
                                       Req size           ModInv,
                                       Req size           ModExp
                                      ])
        , Need DSA         (\ size -> [Req size BaseOps,
                                       Req size Shifts,
                                       Req size Add,
                                       Req size SquareRoot,
                                       Req size PrimeGen,
                                       Req size ModInv,
                                       Req size Mul,
                                       Req (size * 2) Add,
                                       Req (((size * 2) + 64) * 2) Div,
                                       Req size (Convert 512),
                                       Req size (Convert (size + 128)),
                                       Req size (Convert ((size * 2) + 64)),
                                       Req size (Convert (((size * 2) + 64) * 2))
                                       ])
        , Need ECDSA       (\ size -> [Req size SignedSub,
                                       Req size ModDiv,
                                       Req (size + 64) SignedMul,
                                       Req ((size + 64) * 2) SignedSub,
                                       Req ((size + 64) * 2) SignedDiv,
                                       Req ((size + 64) * 2) SignedMul,
                                       Req size (Convert ((size + 64) * 2)),
                                       Req size (SigConvert ((size + 64) * 2))
                                       ])
        , Need PrimeGen    (\ size -> [Req size Div,
                                       Req size Shifts,
                                       Req size ModExp,
                                       Req size EGCD])
        , Need Add         (\ size -> [Req size BaseOps,
                                       Req (size + 64) BaseOps,
                                       Req size (Convert (size + 64))
                                      ])
        , Need Barretts    (\ size -> [Req size BaseOps,
                                       Req (size + 64) BaseOps,
                                       Req (size * 2) BaseOps,
                                       Req ((size * 2) + 64) BaseOps,
                                       Req size (Convert ((size * 2) + 64)),
                                       Req (size + 64) Mul,
                                       Req ((size * 2) + 64) Add,
                                       Req ((size * 2) + 64) Sub,
                                       Req (size + 64) (Convert ((size * 2) + 64)),
                                       Req ((size * 2) + 64) (Convert ((size + 64) * 2)),
                                       Req (size * 2) (Convert ((size * 2) + 64)),
                                       Req (size + 64) (Convert ((size + 64) * 2)),
                                       Req (size + 64) (Convert (size * 2)),
                                       Req (size * 2) Shifts,
                                       Req ((size + 64) * 2) Shifts,
                                       Req ((size * 2) + 64) Div
                                      ])
        , Need Div         (\ size -> [Req size BaseOps,
                                       Req (size * 2) BaseOps,
                                       Req size (Convert (size * 2)),
                                       Req (size * 2) Sub,
                                       Req size Mul,
                                       Req 192 BaseOps,
                                       Req 192 Mul,
                                       Req 384 BaseOps
                                      ])
        , Need ModExp      (\ size -> [Req size BaseOps,
                                       Req size Barretts,
                                       Req size ModSq,
                                       Req size ModMul,
                                       Req size (Convert (size + 64))
                                      ])
        , Need ModMul      (\ size -> [Req size BaseOps,
                                       Req (size * 2) BaseOps,
                                       Req size Barretts,
                                       Req size Mul,
                                       Req size (Convert (size + 64))
                                      ])
        , Need ModDiv      (\ size -> [Req size ModInv,
                                       Req size SignedModInv,
                                       Req size SignedMul,
                                       Req size SignedDiv,
                                       Req (size * 2) SignedDiv,
                                       Req size (SigConvert (size * 2))
                                      ])
        , Need ModSq       (\ size -> [Req size BaseOps,
                                       Req (size * 2) BaseOps,
                                       Req size Barretts,
                                       Req size Square,
                                       Req (size * 2) Div,
                                       Req size (Convert (size * 2)),
                                       Req size (Convert (size + 64))
                                      ])
        , Need Mul         (\ size -> [Req size BaseOps,
                                       Req (size * 2) BaseOps,
                                       Req size (Convert (size * 2))
                                      ])
        , Need Shifts      (\ size -> [Req size BaseOps
                                      ])
        , Need Square      (\ size -> [Req size BaseOps,
                                       Req (size * 2) BaseOps
                                      ])
        , Need Sub         (\ size -> [Req size BaseOps
                                      ])
        , Need SignedAdd   (\ size -> [Req size SignedBase,
                                       Req (size + 64) SignedBase,
                                       Req (size + 64) BaseOps
                                      ])
        , Need SignedBase  (\ size -> [Req size BaseOps])
        , Need SignedCmp   (\ size -> [Req size BaseOps])
        , Need SignedShift (\ size -> [Req size SignedBase,
                                       Req size BaseOps,
                                       Req size Shifts,
                                       Req size Add
                                      ])
        , Need SignedSub   (\ size -> [Req size SignedBase,
                                       Req (size + 64) SignedBase,
                                       Req (size + 64) BaseOps,
                                       Req size Add,
                                       Req size Sub,
                                       Req (size + 64) Sub,
                                       Req size (Convert (size + 64)),
                                       Req size (SigConvert (size + 64))
                                      ])
        , Need SignedMul   (\ size -> [Req size Mul,
                                       Req (size * 2) SignedBase,
                                       Req size (SigConvert (size * 2))
                                      ])
        , Need SignedDiv   (\ size -> [Req size Div,
                                       Req size Add
                                      ])
        , Need EGCD        (\ size -> [Req size SignedBase,
                                       Req size BaseOps,
                                       Req (size + 64) SignedBase,
                                       Req ((size + 64) * 2) SignedBase,
                                       Req size (SigConvert (size + 64)),
                                       Req (size + 64) SignedShift,
                                       Req (size + 64) SignedAdd,
                                       Req (size + 64) SignedSub,
                                       Req (size + 64) SignedCmp,
                                       Req (size + 64) SignedDiv,
                                       Req (size + 64) SignedMul,
                                       Req ((size + 64) * 2) SignedSub,
                                       Req (size + 64) (Convert (((size + 64) * 2) + 64)),
                                       Req (size + 64) (SigConvert (((size + 64) * 2) + 64))
                                      ])
        , Need ModInv      (\ size -> [Req size BaseOps,
                                       Req (size + 64) SignedBase,
                                       Req (size + 64) BaseOps,
                                       Req size (Convert (size + 64)),
                                       Req size EGCD,
                                       Req (size + 64) SignedAdd,
                                       Req size Barretts
                                      ])
        , Need SignedModInv (\ size -> [
                                       Req size EGCD,
                                       Req size SignedModInv
                                      ])
        , Need SquareRoot  (\ size -> [Req size BaseOps,
                                       Req size Shifts,
                                       Req size Add,
                                       Req size Sub
                                      ])
        ]

newRequirements :: Requirement -> [Requirement]
newRequirements (Req size op) = concatMap go needs ++ [Req size BaseOps]
 where
  go (Need op2 generator) | op == op2 = generator size
                          | otherwise = []

rsaSizes :: [Int]
rsaSizes =  [512,1024,2048,3072,4096,8192,15360]

dsaSizes :: [Int]
dsaSizes =  [192,256,1024,2048,3072]

ecdsaSizes :: [Int]
ecdsaSizes = [192,256,384,576]

baseRequirements :: [Requirement]
baseRequirements = concatMap (\ x -> [Req x RSA]) rsaSizes
                ++ concatMap (\ x -> [Req x DSA]) dsaSizes
                ++ concatMap (\ x -> [Req x ECDSA]) ecdsaSizes
                ++ [Req 192 (Convert 1024), Req 256 (Convert 2048), Req 256 (Convert 3072)] -- used in DSA
                ++ [Req 384 (Convert 1024), Req 512 (Convert 2048), Req 512 (Convert 3072)] -- used in DSA
                ++ [Req 192 Add, Req 256 Add, Req 384 Add] -- used for testing
                ++ [Req 192 Mul, Req 384 Mul] -- used for testing
                ++ [Req 448 (Convert 512)] -- used for testing

requirements :: [Requirement]
requirements = go baseRequirements
 where
  step ls = let news  = concatMap newRequirements ls
                ls'   = concatMap sanitizeConverts (news ++ ls)
                ls''  = removeDups (sort ls')
            in ls''
  --
  go ls = let ls' = step ls
          in if ls == ls' then ls else go ls'
  --
  removeDups [] = []
  removeDups (x:xs) | x `elem` xs = removeDups xs
                    | otherwise   = x : removeDups xs
  --
  sanitizeConverts (Req x (Convert y))
    | x == y    = []
    | x < y     = [Req x (Convert y), Req y BaseOps]
    | otherwise = [Req y (Convert x), Req x BaseOps]
  sanitizeConverts x = [x]
