-- |
-- 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 (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 = 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

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)