using System;

// example operand formats are at the end of the file

namespace Lomont.Binary.X86
{
    /// <summary>
    /// Represent an x86 instruction operand. An instruction has 
    /// 0, 1, 2, or 3 operands.
    /// </summary>
    public class X86Operand
    {
        /// <summary>
        /// The format of this operand. Tells which other fields are relevant
        /// </summary>
        public Format OperandFormat { get; set; }

        /// <summary>
        /// The immediate value or displacement, depending on the OperandFormat
        /// </summary>
        public long Value { get; set; }

        /// <summary>
        /// The scale n for indirect addressing of the form [n*EAX+EBX+1234h]
        /// n is 1,2,4,or 8
        /// </summary>
        public int Scale { get; set; }

        /// <summary>
        /// The index register for scale, index, base, for SIB addressing.
        /// </summary>
        public X86Register IndexRegister { get; set; }

        /// <summary>
        /// The base register for scale, index, base addressing.
        /// Also used for single register opcodes.
        /// </summary>
        public X86Register BaseRegister { get; set; }

        /// <summary>
        /// Size in bytes of the register/memory access.
        /// Commonly 1,2,4,8, but sometimes 6,10, 16, 32.
        /// </summary>
        public X86Instruction.DataSize Size { get; set; }

        /// <summary>
        /// This operand uses an immediate value.
        /// </summary>
        public bool HasImmediate
        {
            get
            {
                return OperandFormat == Format.Immediate;
            }
        }

        /// <summary>
        /// This operand uses a displacement value.
        /// </summary>
        public bool HasDisplacement
        {
            get
            {
                return
                    OperandFormat == Format.IndirectOffset ||
                    OperandFormat == Format.RIPRelative ||
                    OperandFormat == Format.Direct;
            }
        }
            

        /// <summary>
        /// The format of this operand
        /// </summary>
        public enum Format
        {
            // NOTE: see http://www.ic.unicamp.br/~celio/mc404s2-03/addr_modes/intel_addr.html for overview

            /// <summary>
            /// No operand
            /// </summary>
            Empty,   

            /// <summary>
            /// Operand is a register
            /// </summary>
            Register,

            /// <summary>
            /// Operand of form [n*EAX+EBX] where n=1,2,4,8, offset has various sizes, etc?
            /// </summary>
            Indirect,

            /// <summary>
            /// Operand is of form [offset].
            /// </summary>
            Direct,

            /// <summary>
            /// Operand of form [n*EAX+EBX+offset] where n=1,2,4,8, offset has various sizes, etc?
            /// </summary>
            IndirectOffset,
            
            /// <summary>
            /// A value in the immediate field.
            /// </summary>
            Immediate,

            /// <summary>
            /// Relative to the RIP, available only in 64 bit mode.
            /// Uses a displacement.
            /// </summary>
            RIPRelative
        }

        /// <summary>
        /// Basic string formatting. 
        /// TODO - add formatting option string, and clean up these to work better
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            switch (OperandFormat)
            {
                case Format.Empty:
                    return "";
                case Format.Immediate :
                    return String.Format("{0:X}h",Value);
                case Format.Indirect :
                    if (Scale == 1)
                        return String.Format("[{0}+{1}]", BaseRegister, IndexRegister);
                    else
                        return String.Format("[{0}*{1}+{2}]", Scale, IndexRegister, BaseRegister);
                case Format.IndirectOffset:
                    if (Scale == 1)
                        return String.Format("[{0}+{1}+{2:X}h]",BaseRegister, IndexRegister,Value);
                    else
                        return String.Format("[{0}*{1}+{2}+{3:X}h]", Scale, IndexRegister, BaseRegister, Value);
                case Format.Register:
                    return BaseRegister.ToString();
                case Format.RIPRelative:
                    return "RIP+" + Value;
                case Format.Direct :
                    return String.Format("[{0:X}h]", Value);
                default:
                    throw new FormatException("Unknown operand formatting requirement");
            }
        }


    }
}

/* example opcode patterns this operand must allow
 * 
   add     ah, al
   dec     eax
   sal     byte ptr [ecx], 1
   sbb     [eax], al
   loopne  near ptr loc_49EF9B+4
   dec     eax
   les     ecx, [edi]
   wait
   xor     [eax], ebx
   add     ah, al
   pavgb   mm4, mm6
   into
   xor     [eax], ebx
   adc     [ecx+4], edx
   jo      near ptr loc_49F54C+1
   mov     ecx, [ebp+10h]
   add     dword ptr [ecx], 0E5B0h
   adc     dword ptr [ecx+4], 0
   jmp     short $+2
   cmp     ds:byte_6DAC56, 0
   mov     ecx, ds:dword_6A39E4
   push    72D7h
   rep stosd
   add     byte ptr ds:qword_48E6E0[edi+ecx], bh

 * 
 * 
 * 
 * 
 * 
 * */
