{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NamedFieldPuns #-}
module Main where

import Data.Marshalling

import Data.Terminfo.Eval 
import Data.Terminfo.Parse
import Control.DeepSeq

import qualified System.Console.Terminfo as Terminfo

import Verify

import Control.Exception ( try, SomeException(..) )

import Control.Monad ( mapM_, forM_ )

import Data.Array.Unboxed
import Data.Maybe ( fromJust )
import Data.Word

import Numeric

-- A list of terminals that ubuntu includes a terminfo cap file for. 
-- Assuming that is a good place to start.
terminals_of_interest = 
    [ "wsvt25"
    , "wsvt25m"
    , "vt52"
    , "vt100"
    , "vt220"
    , "vt102"
    , "xterm-r5"
    , "xterm-xfree86"
    , "xterm-r6"
    , "xterm-256color"
    , "xterm-vt220"
    , "xterm-debian"
    , "xterm-mono"
    , "xterm-color"
    , "xterm"
    , "mach"
    , "mach-bold"
    , "mach-color"
    , "linux"
    , "ansi"
    , "hurd"
    , "Eterm"
    , "pcansi"
    , "screen-256color"
    , "screen-bce"
    , "screen-s"
    , "screen-w"
    , "screen"
    , "screen-256color-bce"
    , "sun"
    , "rxvt"
    , "rxvt-unicode"
    , "rxvt-basic"
    , "cygwin"
    , "cons25"
    , "dumb"
    ]

-- If a terminal defines one of the caps then it's expected to be parsable.
caps_of_interest = 
    [ "cup"
    , "sc"
    , "rc"
    , "setf"
    , "setb"
    , "setaf"
    , "setab"
    , "op"
    , "cnorm"
    , "civis"
    , "smcup"
    , "rmcup"
    , "clear"
    , "hpa"
    , "vpa"
    , "sgr"
    , "sgr0"
    ]

from_capname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)

main = do
    run_test $ do
        eval_buffer :: Ptr Word8 <- liftIO $ mallocBytes (1024 * 1024) -- Should be big enough for all termcaps ;-)
        forM_ terminals_of_interest $ \term_name -> do
            liftIO $ putStrLn $ "testing parsing of caps for terminal: " ++ term_name
            mti <- liftIO $ try $ Terminfo.setupTerm term_name
            case mti of
                Left (_e :: SomeException) 
                    -> return ()
                Right ti -> do
                    forM_ caps_of_interest $ \cap_name -> do
                        liftIO $ putStrLn $ "\tevaluating cap: " ++ cap_name
                        case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
                            Just cap_def -> do
                                parse_result <- parse_cap_expression cap_def
                                let test_name = "\teval cap " ++ cap_name ++ " -> " ++ show cap_def
                                _ <- case parse_result of
                                    Left error -> verify test_name ( liftResult $ failed 
                                                                                $ result { reason = "prase error " ++ show error } )
                                    Right !cap_expr -> verify test_name ( verify_eval_cap eval_buffer cap_expr )
                                return ()
                            Nothing      -> do
                                return ()
        return ()
    return ()

{-# NOINLINE verify_eval_cap #-}
verify_eval_cap :: Ptr Word8 -> CapExpression -> Int -> Property
verify_eval_cap eval_buffer expr !junk_int = do
    forAll (vector 9) $ \input_values -> 
        let !byte_count = cap_expression_required_bytes expr input_values
        in liftIOResult $ do
            let start_ptr :: Ptr Word8 = eval_buffer
            forM_ [0..100] $ \i -> serialize_cap_expression expr input_values start_ptr
            end_ptr <- serialize_cap_expression expr input_values start_ptr
            case end_ptr `minusPtr` start_ptr of
                count | count < 0        -> 
                            return $ failed $ result { reason = "End pointer before start pointer." }
                      | toEnum count > byte_count -> 
                            return $ failed $ result { reason = "End pointer past end of buffer by " 
                                                              ++ show (toEnum count - byte_count) 
                                                     }
                      | otherwise        -> 
                            return succeeded

