{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Data.Certificate.X509.Internal
	( ParseASN1
	, runParseASN1
	, onNextContainer
	, onNextContainerMaybe
	, getNextContainer
	, getNextContainerMaybe
	, getNext
	, hasNext
	, makeASN1Sequence
	, asn1Container
	, OID
	) where

import Data.ASN1.DER
import Data.ASN1.Stream (getConstructedEnd)
import Control.Monad.State
import Control.Monad.Error

type OID = [Integer]

newtype ParseASN1 a = P { runP :: ErrorT String (State [ASN1]) a }
	deriving (Functor, Monad, MonadError String)

runParseASN1 :: ParseASN1 a -> [ASN1] -> Either String a
runParseASN1 f s =
	case runState (runErrorT (runP f)) s of
		(Left err, _) -> Left err
		(Right r, _) -> Right r

getNext :: ParseASN1 ASN1
getNext = do
	list <- P (lift get)
	case list of
		[]    -> throwError "empty"
		(h:l) -> P (lift (put l)) >> return h

getNextContainer :: ASN1ConstructionType -> ParseASN1 [ASN1]
getNextContainer ty = do
	list <- P (lift get)
	case list of
		[]    -> throwError "empty"
		(h:l) -> if h == Start ty
			then do
				let (l1, l2) = getConstructedEnd 0 l
				P (lift $ put l2) >> return l1
			else throwError "not an expected container"


onNextContainer :: ASN1ConstructionType -> ParseASN1 a -> ParseASN1 a
onNextContainer ty f = do
	n <- getNextContainer ty
	case runParseASN1 f n of
		Left err -> throwError err
		Right r  -> return r

getNextContainerMaybe :: ASN1ConstructionType -> ParseASN1 (Maybe [ASN1])
getNextContainerMaybe ty = do
	list <- P (lift get)
	case list of
		[]    -> return Nothing
		(h:l) -> if h == Start ty
			then do
				let (l1, l2) = getConstructedEnd 0 l
				P (lift $ put l2) >> return (Just l1)
			else return Nothing

onNextContainerMaybe :: ASN1ConstructionType -> ParseASN1 a -> ParseASN1 (Maybe a)
onNextContainerMaybe ty f = do
	n <- getNextContainerMaybe ty
	case n of
		Just l -> case runParseASN1 f l of
			Left err -> throwError err
			Right r  -> return $ Just r
		Nothing -> return Nothing

hasNext :: ParseASN1 Bool
hasNext = do
	list <- P (lift get)
	case list of
		[] -> return False
		_  -> return True

asn1Container :: ASN1ConstructionType -> [ASN1] -> [ASN1]
asn1Container ty l = [Start ty] ++ l ++ [End ty]

makeASN1Sequence :: [ASN1] -> [[ASN1]]
makeASN1Sequence list =
	let (l1, l2) = getConstructedEnd 0 list in
	case l2 of
		[] -> []
		_  -> l1 : makeASN1Sequence l2