using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace inVtero.net.Support
{
    public enum ks_err : int
    {
        KS_ERR_ASM = 128,
        KS_ERR_ASM_ARCH = 512,
        KS_ERR_OK = 0,
        KS_ERR_NOMEM = 1,
        KS_ERR_ARCH = 2,
        KS_ERR_HANDLE = 3,
        KS_ERR_MODE = 4,
        KS_ERR_VERSION = 5,
        KS_ERR_OPT_INVALID = 6,
        KS_ERR_ASM_EXPR_TOKEN = 128,
        KS_ERR_ASM_DIRECTIVE_VALUE_RANGE = 129,
        KS_ERR_ASM_DIRECTIVE_ID = 130,
        KS_ERR_ASM_DIRECTIVE_TOKEN = 131,
        KS_ERR_ASM_DIRECTIVE_STR = 132,
        KS_ERR_ASM_DIRECTIVE_COMMA = 133,
        KS_ERR_ASM_DIRECTIVE_RELOC_NAME = 134,
        KS_ERR_ASM_DIRECTIVE_RELOC_TOKEN = 135,
        KS_ERR_ASM_DIRECTIVE_FPOINT = 136,
        KS_ERR_ASM_DIRECTIVE_UNKNOWN = 137,
        KS_ERR_ASM_DIRECTIVE_EQU = 138,
        KS_ERR_ASM_DIRECTIVE_INVALID = 139,
        KS_ERR_ASM_VARIANT_INVALID = 140,
        KS_ERR_ASM_EXPR_BRACKET = 141,
        KS_ERR_ASM_SYMBOL_MODIFIER = 142,
        KS_ERR_ASM_SYMBOL_REDEFINED = 143,
        KS_ERR_ASM_SYMBOL_MISSING = 144,
        KS_ERR_ASM_RPAREN = 145,
        KS_ERR_ASM_STAT_TOKEN = 146,
        KS_ERR_ASM_UNSUPPORTED = 147,
        KS_ERR_ASM_MACRO_TOKEN = 148,
        KS_ERR_ASM_MACRO_PAREN = 149,
        KS_ERR_ASM_MACRO_EQU = 150,
        KS_ERR_ASM_MACRO_ARGS = 151,
        KS_ERR_ASM_MACRO_LEVELS_EXCEED = 152,
        KS_ERR_ASM_MACRO_STR = 153,
        KS_ERR_ASM_MACRO_INVALID = 154,
        KS_ERR_ASM_ESC_BACKSLASH = 155,
        KS_ERR_ASM_ESC_OCTAL = 156,
        KS_ERR_ASM_ESC_SEQUENCE = 157,
        KS_ERR_ASM_ESC_STR = 158,
        KS_ERR_ASM_TOKEN_INVALID = 159,
        KS_ERR_ASM_INSN_UNSUPPORTED = 160,
        KS_ERR_ASM_FIXUP_INVALID = 161,
        KS_ERR_ASM_LABEL_INVALID = 162,
        KS_ERR_ASM_FRAGMENT_INVALID = 163,
        KS_ERR_ASM_INVALIDOPERAND = 512,
        KS_ERR_ASM_MISSINGFEATURE = 513,
        KS_ERR_ASM_MNEMONICFAIL = 514,
    }

    public enum ks_arch : int
    {
        KS_ARCH_ARM = 1,
        KS_ARCH_ARM64 = 2,
        KS_ARCH_MIPS = 3,
        KS_ARCH_X86 = 4,
        KS_ARCH_PPC = 5,
        KS_ARCH_SPARC = 6,
        KS_ARCH_SYSTEMZ = 7,
        KS_ARCH_HEXAGON = 8,
        KS_ARCH_MAX = 9,
    }

    public enum ks_mode : int
    {
        KS_MODE_LITTLE_ENDIAN = 0,
        KS_MODE_BIG_ENDIAN = 1073741824,
        KS_MODE_ARM = 1,
        KS_MODE_THUMB = 16,
        KS_MODE_V8 = 64,
        KS_MODE_MICRO = 16,
        KS_MODE_MIPS3 = 32,
        KS_MODE_MIPS32R6 = 64,
        KS_MODE_MIPS32 = 4,
        KS_MODE_MIPS64 = 8,
        KS_MODE_16 = 2,
        KS_MODE_32 = 4,
        KS_MODE_64 = 8,
        KS_MODE_PPC32 = 4,
        KS_MODE_PPC64 = 8,
        KS_MODE_QPX = 16,
        KS_MODE_SPARC32 = 4,
        KS_MODE_SPARC64 = 8,
        KS_MODE_V9 = 16,
    }

    [Flags]
    public enum ks_opt_value : uint
    {
        KS_OPT_SYNTAX = 1,
        KS_OPT_SYM_RESOLVER = 2,
        KS_OPT_SYNTAX_INTEL = 1,
        KS_OPT_SYNTAX_ATT = 2,
        KS_OPT_SYNTAX_NASM = 4,
        KS_OPT_SYNTAX_MASM = 8,
        KS_OPT_SYNTAX_GAS = 16,
        KS_OPT_SYNTAX_RADIX16 = 32,
    }

    public static class Keystone
    {
        /// <summary>
        /// Meant for short assembly statements
        /// 
        /// From python shell, e.g.;
        /// 
        /// print Keystone.Assemble("nop", 0, ks_opt_value.KS_OPT_SYNTAX_INTEL)[0].ToString("x")
        /// 90
        /// 
        /// TODO: Improve for huge inputs ;)
        /// </summary>
        /// <param name="assembly">String of input</param>
        /// <param name="Address">Virtual Address</param>
        /// <param name="Opt">Syntax mode options</param>
        /// <returns>byte array</returns>
        public static byte[] Assemble(String assembly, ulong Address, ks_opt_value Opt)
        {
            var rv = new List<byte>();
            var AsmHandle = new IntPtr();
            var Encoded = new IntPtr();
            var stat_count = 0U;
            var Encoded_size = 0U;

            var err = ks_open(ks_arch.KS_ARCH_X86, ks_mode.KS_MODE_64, ref AsmHandle);
            var csopt = ks_option(AsmHandle, 1, Opt);

            var asm = ks_asm(AsmHandle, assembly, 0, ref Encoded, ref Encoded_size, ref stat_count);

            if (asm == 0)
                for (int i=0; i < Encoded_size; i++)
                    rv.Add(Marshal.ReadByte(Encoded, i));

            ks_free(Encoded);
            ks_close(AsmHandle);
            return rv.ToArray();
        }


        [DllImport("keystone.dll")]
        public static extern ks_err ks_open(
            ks_arch arch,
            ks_mode mode,
            ref IntPtr handle);

        [DllImport("keystone.dll")]
        public static extern ks_err ks_option(
            IntPtr handle,
            int mode,
            ks_opt_value value);

        [DllImport("keystone.dll")]
        public static extern int ks_asm(
            IntPtr handle,
            String assembly,
            ulong address,
            ref IntPtr encoding,
            ref uint encoding_size,
            ref uint stat_count);

        [DllImport("keystone.dll")]
        public static extern ks_err ks_errno(
            IntPtr handle);

        [DllImport("keystone.dll")]
        public static extern ks_err ks_close(
            IntPtr handle);

        [DllImport("keystone.dll")]
        public static extern void ks_free(
            IntPtr handle);

        [DllImport("keystone.dll")]
        public static extern int ks_version(
            uint major,
            uint minor);
    }
}
