module Plat.Rendering.Errors
) where
import qualified Data.ByteString.UTF8 as B
import Data.List
import Plat.Context
import Plat.Utils

-- | This is an auxiliary type, which indicates what is the type we expected,
-- or what is the actual type we've got.

data ContextType = CtxString | CtxBool | CtxRecord | CtxArray
instance Show ContextType where
    show CtxString = "string"
    show CtxBool = "boolean"
    show CtxRecord = "record"
    show CtxArray = "array"

-- | This type represents a rendering error. Note that we provide a 'renderErr' function
-- which can be used if you want to present an error to the user.
-- This type internals are exposed for you to pattern-match on them, although they can
-- change in the future versions.

data RenderErr =
    ErrorType ContextType ContextType Pos [B.ByteString] |

    -- ^ The expression evaluates to something of the wrong type.
    -- Here, first type is the actual one, while the second is the expected one.

    ErrorField ContextType Pos [B.ByteString] |

    -- ^ The expression can't be evaluated, since there is no field with the given name
    -- in the record.

    ErrorOption (Pos, Pos) [RenderErr] |

    -- ^ There are several options in the template, but all of them failed for some reason.
    -- Error messages for all options are listed here.

    ErrorCheck Pos [B.ByteString]

    -- ^ Template asks to check for some boolean expression, which happened to be false.

showBS :: [B.ByteString] -> String
showBS bs = concat $ intersperse "." $ map B.toString bs
instance Show RenderErr where
    show (ErrorType c1 c2 p bs) =
        show p ++ ": " ++ showBS bs ++ " - " ++ show c1 ++ " instead of " ++ show c2
    show (ErrorField c p bs) =
        show p ++ ": " ++ showBS bs ++ " - no field in a " ++ show c
    show (ErrorOption (p1, p2) ems) =
        show p1 ++ "-" ++ show p2 ++ ": no valid option - " ++ show ems
    show (ErrorCheck p bs) = show p ++ ": " ++ showBS bs ++ " = false"

-- | This function is similar to 'templateErr'. It allows you to show the rendering error
-- to the user. The context it generates always has a value named \"@pos@\", indicating
-- the position where an error occured; if multiple branches failed, there is also a value
-- \"endpos\", which indicates the position where the list of branches ends. Positions are
-- presented with the 'posContext' function.
-- Other field depend on the error. For a type mismatch, there are values \"expected\" and
-- \"actual\", which are the strings \'string\', \'boolean\', \'array\' or \'record\',
-- depending on what type the value should have and what type it actually has;
-- there is also the \"expr\" field, which presents the list of names in the failing
-- expression with the function 'listValues'.
-- If the required field wasn't found in the record, then there is the value \"field\",
-- which is a list of names in the failing expression, and the value \"type\", which
-- shows what type that expression should have.
-- If there were multiple branches, all of them failing, then, apart from \"pos\" and
-- \"endpos\" fields, there is a list of errors, one for each of the branches. These
-- errors are presented using the 'renderErr' function recursively.
-- Finally, if the boolean value was checked and happened to be false, there would be
-- the field \"check\", which, again, is a list of names in the expression, which
-- evaluated to false.

renderErr :: RenderErr -> Context ()
renderErr (ErrorType c1 c2 p bs) =
    do "pos" =: posContext p
       "expected" =: show c1
       "actual" =: show c2
       "expr" =: listValues bs
renderErr (ErrorField c p bs) =
    do "pos" =: posContext p
       "type" =: show c
       "field" =: listValues bs
renderErr (ErrorOption (p1, p2) ems) =
    do "pos" =: posContext p1
       "endpos" =: posContext p2
       "options" =: listValues (map renderErr ems)
renderErr (ErrorCheck p bs) =
    do "pos" =: posContext p
       "check" =: listValues bs