{-|
Module      : Keystone
Description : The Keystone assembler engine.
Copyright   : (c) Adrian Herrera, 2016
License     :  GPL-2

Keystone is a lightweight multi-platform, multi-architecture assembler
framework.

Further information is available at <http://www.keystone-engine.org>.
-}
module Keystone
    ( -- * Assembler control
      Assembler
    , Engine
    , Architecture(..)
    , Mode(..)
    , OptionType(..)
    , OptionValue(..)
    , runAssembler
    , open
    , option
    , assemble

      -- * Error handling
    , Error(..)
    , errno
    , strerror

      -- * Misc.
    , version
    ) where

import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Either (left, right, runEitherT)
import Data.ByteString (ByteString, packCStringLen)
import Data.List (intercalate)
import Foreign

import Keystone.Internal.Core
import Keystone.Internal.Keystone

-------------------------------------------------------------------------------
-- Assembler control
-------------------------------------------------------------------------------

-- | Run the Keystone assembler and return a result on success, or an 'Error'
-- on failure.
runAssembler :: Assembler a         -- ^ The assembler code to execute
             -> IO (Either Error a) -- ^ A result on success, or an 'Error' on
                                    -- failure
runAssembler =
    runEitherT

-- | Create a new instance of the Keystone assembler.
open :: Architecture        -- ^ CPU architecture
     -> [Mode]              -- ^ CPU hardware mode
     -> Assembler Engine    -- ^ A 'Keystone' engine on success, or an 'Error'
                            -- on failure
open arch mode = do
    (err, ksPtr) <- lift $ ksOpen arch mode
    if err == ErrOk then
        -- Return a pointer to the Keystone engine if ksOpen completed
        -- successfully
        lift $ mkEngine ksPtr
    else
        -- Otherwise return an error
        left err

option :: Engine        -- ^ 'Keystone' engine handle
       -> OptionType    -- ^ Type of option to set
       -> OptionValue   -- ^ Option value corresponding with the type
       -> Assembler ()  -- ^ An 'Error' on failure
option ks optType optValue = do
    err <- lift $ ksOption ks optType optValue
    if err == ErrOk then
        right ()
    else
        left err

-- | Assemble a list of statements.
assemble :: Engine                      -- ^ 'Keystone' engine handle
         -> [String]                    -- ^ List of statements to assemble.
         -> Maybe Word64                -- ^ Optional address of the first
                                        -- assembly instruction
         -> Assembler (ByteString, Int) -- ^ Returns the encoded input assembly
                                        -- string and the number of statements
                                        -- successfully processed. Returns an
                                        -- 'Error' on failure
assemble ks stmts addr = do
    let string = intercalate ";" stmts
    (res, encPtr, encSize, statCount) <- lift $ ksAsm ks string (maybeZ addr)
    if res == 0 then do
        -- If ksAsm completed successfully, pack the encoded bytes into a
        -- ByteString. Once the encoded bytes have been packed the original
        -- encoded bytes can be freed. The ByteString is returned with the
        -- statement count
        bs <- lift $ packCStringLen (castPtr encPtr, encSize)
        lift $ ksFree encPtr
        right (bs, statCount)
    else do
        -- On failure, call errno for error code
        err <- errno ks
        left err
    where maybeZ = maybe 0 id

-------------------------------------------------------------------------------
-- Misc.
-------------------------------------------------------------------------------

-- | Combined API version & major and minor version numbers. Returns a
-- hexadecimal number as (major << 8 | minor), which encodes both major and
-- minor versions.
version :: Int
version =
    ksVersion nullPtr nullPtr

-- | Report the 'Error' number when some API function failed.
errno :: Engine             -- ^ 'Keystone' engine handle
      -> Assembler Error    -- ^ The last 'Error' code
errno =
    lift . ksErrno

-- | Return a string describing the given 'Error'.
strerror :: Error   -- ^ The 'Error' code
         -> String  -- ^ Description of the error code
strerror =
    ksStrerror
