-- |
-- Maintainer: Thomas.DuBuisson@gmail.com
-- Stability: beta
-- Portability: portable 
--
--
-- NIST KAT files are composed of properties, such as:
--
-- >    [SHA-1]
-- >    [PredictionResistance = True]
-- >    [EntropyInputSize = 128]
--
-- and individual known answer tests using these properties, ex:
--
-- >    COUNT = 0
-- >    EntropyInput = 7
-- >    PersonalizationString =
-- >    Result = 8
-- >
-- >    COUNT = 1
-- >    EntropyInput = 4
-- >    PersonalizationString = 
-- >    Result = 2
--
-- Using 'many parseCategory' this input would be converted to a
-- single element list of 'TestCategory':
--
-- >    [([("SHA-1",""), ("PredictionResistance", "True"), ("EntropyInputSize", "128")],
-- >            [   [("COUNT", "0"), ("EntropyInput", "7"), ("PersonalizationString", ""), ("Result", "8")], 
-- >              , [("COUNT", "1"), ("EntropyInput", "4"), ("PersonalizationString", ""), ("Result", "2")]])]
--
-- that is, a list of tuples, the first element is a list of properties (key/value pairs) and
-- the second element is a list of tests.  Each test is itself a list of records (key/value pairs).
-- Properties apply to all tests contained in the second element of the tuple.
module Test.ParseNistKATs
        ( parseCategories --, parseCategory, parseProperty
        , Properties, Record, NistTest, TypedTest, TestCategory
        ) where

import Data.Char (isSpace)
import Data.Maybe (listToMaybe)
import Control.Arrow (first, second)

type Properties = [(String, String)]
type Record = (String, String)
type NistTest = [Record]
type TypedTest = (String, [NistTest])

type TestCategory = (Properties, [NistTest])

parseCategories :: String -> String -> [(Properties, [NistTest])]
parseCategories delim file =
        getCategories delim . elimWhite . elimComments . lines $ file

elimComments = filter ((/= Just '#') . listToMaybe)
elimWhite = map (filter (/= '\r')) . filter (notNull . filter (not . isSpace))
getCategories :: String -> [String] -> [(Properties, [NistTest])]
getCategories _ [] = []
getCategories delim ls =
        let (tt, rest) = getCategory delim ls
        in tt : getCategories delim rest
getCategory delim ls =
        let (props,rest1) = break ((/= Just '[') . listToMaybe) ls
            (tests,rest2) = break ((== Just '[') . listToMaybe) rest1
        in ((map parseProp props, parseTests delim tests), rest2)
parseProp = first trim . second trim . second (drop 1) . break (== '=') . filter (`notElem` "[]")
parseTests :: String -> [String] -> [NistTest]
parseTests delim = filter notNull . chunk ((== delim) . fst) . map parseRecord
parseRecord = second (drop 1) . break (== '=') . filter (not . isSpace)
notNull = not . null
trim :: String -> String
trim = dropWhile isSpace . (reverse . dropWhile isSpace . reverse)

chunk :: (a -> Bool) -> [a] -> [[a]]
chunk f xs = snd (go xs)
  where
  go [] = ([],[])
  go (a:as) = if f a
                then let (t,ts) = go as in ([], (a:t):ts)
                else let (t, ts) = go as
                     in (a:t, ts)
