{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# OPTIONS_GHC -funbox-strict-fields -O #-}
module Data.Terminfo.Parse ( module Data.Terminfo.Parse
                           , Text.ParserCombinators.Parsec.ParseError
                           )
    where

import Control.Applicative ( Applicative(..), pure, (<*>) )  
import Control.Monad ( liftM )
import Control.Monad.Trans
import Control.DeepSeq

import Data.Monoid
import Data.Word

import Foreign.C.Types
import Foreign.Marshal.Array
import Foreign.Ptr

import Text.ParserCombinators.Parsec

type CapBytes = ( Ptr Word8, CSize )

data CapExpression = CapExpression
    { cap_ops :: !CapOps
    , cap_bytes :: !CapBytes
    , source_string :: !String
    , param_count :: !Word
    , param_ops :: !ParamOps
    }

instance NFData CapExpression where
    rnf (CapExpression ops !_bytes !str !c !p_ops) 
        = rnf ops `seq` rnf str `seq` rnf c `seq` rnf p_ops

type CapParam = Word

type CapOps = [CapOp]
data CapOp = 
      Bytes !Int !CSize !Int
    | DecOut | CharOut
    -- This stores a 0-based index to the parameter. However the operation that implies this op is
    -- 1-based
    | PushParam !Word | PushValue !Word
    -- The conditional parts are the sequence of (%t expression, %e expression) pairs.
    -- The %e expression may be NOP
    | Conditional 
      { conditional_expr :: !CapOps
      , conditional_parts :: ![(CapOps, CapOps)]
      }
    | BitwiseOr | BitwiseXOr | BitwiseAnd
    | ArithPlus | ArithMinus
    | CompareEq | CompareLt | CompareGt
    deriving ( Show )

instance NFData CapOp where
    rnf (Bytes offset _count next_offset) = rnf offset `seq` rnf next_offset
    rnf (PushParam pn) = rnf pn
    rnf (PushValue v) = rnf v 
    rnf (Conditional c_expr c_parts) = rnf c_expr `seq` rnf c_parts 
    rnf BitwiseOr = ()
    rnf BitwiseXOr = ()
    rnf BitwiseAnd = ()
    rnf ArithPlus = ()
    rnf ArithMinus = ()
    rnf CompareEq = ()
    rnf CompareLt = ()
    rnf CompareGt = ()
    rnf DecOut = ()
    rnf CharOut = ()

type ParamOps = [ParamOp]
data ParamOp =
      IncFirstTwo
    deriving ( Show )

instance NFData ParamOp where
    rnf IncFirstTwo = ()

parse_cap_expression :: ( Applicative m
                        , MonadIO m
                        )
                     => String 
                     -> m ( Either ParseError CapExpression )
parse_cap_expression cap_string = 
    let v = runParser cap_expression_parser
                           initial_build_state
                           "terminfo cap" 
                           cap_string 
    in case v of
        Left e -> return $ Left e
        Right build_results -> pure Right <*> construct_cap_expression cap_string build_results

construct_cap_expression :: MonadIO m => [Char] -> BuildResults -> m CapExpression
construct_cap_expression cap_string build_results = do
    byte_array <- liftIO $ newArray (map ( toEnum . fromEnum ) cap_string )
    let expr = CapExpression
                { cap_ops = out_cap_ops build_results
                -- The cap bytes are the lower 8 bits of the input string's characters.
                -- \todo Verify the input string actually contains an 8bit byte per character.
                , cap_bytes = ( byte_array, toEnum $! length cap_string )
                , source_string = cap_string
                , param_count = out_param_count build_results
                , param_ops = out_param_ops build_results
                } 
    return $! rnf expr `seq` expr

type CapParser a = GenParser Char BuildState a 

cap_expression_parser :: CapParser BuildResults
cap_expression_parser = do
    rs <- many $ param_escape_parser <|> bytes_op_parser 
    return $ mconcat rs

param_escape_parser :: CapParser BuildResults
param_escape_parser = do
    _ <- char '%'
    inc_offset 1
    literal_percent_parser <|> param_op_parser 

literal_percent_parser :: CapParser BuildResults
literal_percent_parser = do
    _ <- char '%'
    start_offset <- getState >>= return . next_offset
    inc_offset 1
    return $ BuildResults 0 [Bytes start_offset 1 1] []

param_op_parser :: CapParser BuildResults
param_op_parser
    = increment_op_parser 
    <|> push_op_parser
    <|> dec_out_parser
    <|> char_out_parser
    <|> conditional_op_parser
    <|> bitwise_op_parser
    <|> arith_op_parser
    <|> literal_int_op_parser
    <|> compare_op_parser
    <|> char_const_parser

increment_op_parser :: CapParser BuildResults
increment_op_parser = do
    _ <- char 'i'
    inc_offset 1
    return $ BuildResults 0 [] [ IncFirstTwo ]

push_op_parser :: CapParser BuildResults
push_op_parser = do
    _ <- char 'p'
    param_n <- digit >>= return . (\d -> read [d])
    inc_offset 2
    return $ BuildResults param_n [ PushParam $ param_n - 1 ] []

dec_out_parser :: CapParser BuildResults
dec_out_parser = do
    _ <- char 'd'
    inc_offset 1
    return $ BuildResults 0 [ DecOut ] []

char_out_parser :: CapParser BuildResults
char_out_parser = do
    _ <- char 'c'
    inc_offset 1
    return $ BuildResults 0 [ CharOut ] []

conditional_op_parser :: CapParser BuildResults
conditional_op_parser = do
    _ <- char '?'
    inc_offset 1
    cond_part <- many_expr conditional_true_parser
    parts <- many_p 
                ( do
                    true_part <- many_expr $ choice [ try $ lookAhead conditional_end_parser
                                                    , conditional_false_parser 
                                                    ]
                    false_part <- many_expr $ choice [ try $ lookAhead conditional_end_parser
                                                     , conditional_true_parser
                                                     ]
                    return ( true_part, false_part )
                ) 
                conditional_end_parser

    let true_parts = map fst parts
        false_parts = map snd parts
        BuildResults n cond cond_param_ops = cond_part

    let n' = maximum $ n : map out_param_count true_parts
        n'' = maximum $ n' : map out_param_count false_parts

    let true_ops = map out_cap_ops true_parts
        false_ops = map out_cap_ops false_parts
        cond_parts = zip true_ops false_ops

    let true_param_ops = mconcat $ map out_param_ops true_parts
        false_param_ops = mconcat $ map out_param_ops false_parts
        p_ops = mconcat [cond_param_ops, true_param_ops, false_param_ops]

    return $ BuildResults n'' [ Conditional cond cond_parts ] p_ops

    where 
        many_p !p !end = choice 
            [ try end >> return []
            , do !v <- p 
                 !vs <- many_p p end
                 return $! v : vs
            ]
        many_expr end = liftM mconcat $ many_p ( param_escape_parser <|> bytes_op_parser ) end

conditional_true_parser :: CapParser ()
conditional_true_parser = do
    _ <- string "%t"
    inc_offset 2

conditional_false_parser :: CapParser ()
conditional_false_parser = do
    _ <- string "%e"
    inc_offset 2

conditional_end_parser :: CapParser ()
conditional_end_parser = do
    _ <- string "%;"
    inc_offset 2

bitwise_op_parser :: CapParser BuildResults
bitwise_op_parser 
    =   bitwise_or_parser
    <|> bitwise_and_parser
    <|> bitwise_xor_parser

bitwise_or_parser :: CapParser BuildResults
bitwise_or_parser = do
    _ <- char '|'
    inc_offset 1
    return $ BuildResults 0 [ BitwiseOr ] [ ]

bitwise_and_parser :: CapParser BuildResults
bitwise_and_parser = do
    _ <- char '&'
    inc_offset 1
    return $ BuildResults 0 [ BitwiseAnd ] [ ]

bitwise_xor_parser :: CapParser BuildResults
bitwise_xor_parser = do
    _ <- char '^'
    inc_offset 1
    return $ BuildResults 0 [ BitwiseXOr ] [ ]

arith_op_parser :: CapParser BuildResults
arith_op_parser 
    =   plus_op 
    <|> minus_op 
    where
        plus_op = do
            _ <- char '+'
            inc_offset 1
            return $ BuildResults 0 [ ArithPlus ] [ ]
        minus_op = do
            _ <- char '-'
            inc_offset 1
            return $ BuildResults 0 [ ArithMinus ] [ ]

literal_int_op_parser :: CapParser BuildResults
literal_int_op_parser = do
    _ <- char '{'
    inc_offset 1
    n_str <- many1 digit
    inc_offset $ toEnum $ length n_str
    let n :: Word = read n_str
    _ <- char '}'
    inc_offset 1
    return $ BuildResults 0 [ PushValue n ] [ ]

compare_op_parser :: CapParser BuildResults
compare_op_parser 
    =   compare_eq_op
    <|> compare_lt_op 
    <|> compare_gt_op 
    where
        compare_eq_op = do
            _ <- char '='
            inc_offset 1
            return $ BuildResults 0 [ CompareEq ] [ ]
        compare_lt_op = do
            _ <- char '<'
            inc_offset 1
            return $ BuildResults 0 [ CompareLt ] [ ]
        compare_gt_op = do
            _ <- char '>'
            inc_offset 1
            return $ BuildResults 0 [ CompareGt ] [ ]

bytes_op_parser :: CapParser BuildResults
bytes_op_parser = do
    bytes <- many1 $ satisfy (/= '%')
    start_offset <- getState >>= return . next_offset
    let !c = length bytes
    !s <- getState
    let s' = s { next_offset = start_offset + c }
    setState s'
    return $ BuildResults 0 [Bytes start_offset ( toEnum c ) c ] []

char_const_parser :: CapParser BuildResults
char_const_parser = do
    _ <- char '\''
    char_value <- liftM (toEnum . fromEnum) anyChar 
    _ <- char '\''
    inc_offset 3
    return $ BuildResults 0 [ PushValue char_value ] [ ]

data BuildState = BuildState 
    { next_offset :: Int
    } 

inc_offset :: Int -> CapParser ()
inc_offset n = do
    s <- getState
    let s' = s { next_offset = next_offset s + n }
    setState s'

initial_build_state :: BuildState
initial_build_state = BuildState 0

data BuildResults = BuildResults
    { out_param_count :: !Word
    , out_cap_ops :: !CapOps
    , out_param_ops :: !ParamOps
    }

instance Monoid BuildResults where
    mempty = BuildResults 0 [] []
    v0 `mappend` v1 
        = BuildResults
        { out_param_count = (out_param_count v0) `max` (out_param_count v1)
        , out_cap_ops = (out_cap_ops v0) `mappend` (out_cap_ops v1)
        , out_param_ops = (out_param_ops v0) `mappend` (out_param_ops v1)
        }

