Partial Public Class x8086
    Public Const MemSize = &H100000
    Public Const ROMStart As Integer = &HC0000 '&HF0000

    Public Memory(memSize - 1) As Byte

    Public Const shl3 As Integer = 1 << 3
    Public Const shl2 As Integer = 1 << 2

    Public Class MemoryAccessEventArgs
        Inherits EventArgs

        Public Enum AccessModes
            Read
            Write
        End Enum

        Private mAccessMode As AccessModes
        Private mAddress As Integer

        Public Sub New(address As Integer, accesMode As AccessModes)
            mAddress = address
            mAccessMode = accesMode
        End Sub

        Public ReadOnly Property AccessMode As AccessModes
            Get
                Return mAccessMode
            End Get
        End Property

        Public ReadOnly Property Address As Integer
            Get
                Return mAddress
            End Get
        End Property
    End Class

    Public Event MemoryAccess(sender As Object, e As MemoryAccessEventArgs)

    Public Class GPRegisters
        Implements ICloneable

        Public Enum RegistersTypes
            NONE = -1

            AL = 0
            AH = AL Or shl2
            AX = AL Or shl3

            BL = 3
            BH = BL Or shl2
            BX = BL Or shl3

            CL = 1
            CH = CL Or shl2
            CX = CL Or shl3

            DL = 2
            DH = DL Or shl2
            DX = DL Or shl3

            ES = (AL + BX + 1) Or shl3
            CS = ES + 1
            SS = ES + 2
            DS = ES + 3
            SP = (AH + BX + 1) Or shl3
            BP = (CH + BX + 1) Or shl3
            SI = (DH + BX + 1) Or shl3
            DI = (BH + BX + 1) Or shl3
            IP = DI + 1
        End Enum

        Private mActiveSegmentRegister As GPRegisters.RegistersTypes
        Private mActiveSegmentChanged As Boolean
        Private mActiveSegmentValue As Integer

        Public Sub New()
            mActiveSegmentRegister = RegistersTypes.DS
        End Sub

        Public Property Val(reg As RegistersTypes) As Integer
            Get
                Select Case reg
                    Case RegistersTypes.AX : Return AX
                    Case RegistersTypes.AH : Return AH
                    Case RegistersTypes.AL : Return AL

                    Case RegistersTypes.BX : Return BX
                    Case RegistersTypes.BH : Return BH
                    Case RegistersTypes.BL : Return BL

                    Case RegistersTypes.CX : Return CX
                    Case RegistersTypes.CH : Return CH
                    Case RegistersTypes.CL : Return CL

                    Case RegistersTypes.DX : Return DX
                    Case RegistersTypes.DH : Return DH
                    Case RegistersTypes.DL : Return DL

                    Case RegistersTypes.CS : Return CS
                    Case RegistersTypes.IP : Return IP

                    Case RegistersTypes.SS : Return SS
                    Case RegistersTypes.SP : Return SP

                    Case RegistersTypes.DS : Return DS
                    Case RegistersTypes.SI : Return SI

                    Case RegistersTypes.ES : Return ES
                    Case RegistersTypes.DI : Return DI

                    Case RegistersTypes.BP : Return BP

                    Case Else : Throw New Exception("Invalid Register")
                End Select
            End Get
            Set(value As Integer)
                Select Case reg
                    Case RegistersTypes.AX : AX = value
                    Case RegistersTypes.AH : AH = value
                    Case RegistersTypes.AL : AL = value

                    Case RegistersTypes.BX : BX = value
                    Case RegistersTypes.BH : BH = value
                    Case RegistersTypes.BL : BL = value

                    Case RegistersTypes.CX : CX = value
                    Case RegistersTypes.CH : CH = value
                    Case RegistersTypes.CL : CL = value

                    Case RegistersTypes.DX : DX = value
                    Case RegistersTypes.DH : DH = value
                    Case RegistersTypes.DL : DL = value

                    Case RegistersTypes.CS : CS = value
                    Case RegistersTypes.IP : IP = value

                    Case RegistersTypes.SS : SS = value
                    Case RegistersTypes.SP : SP = value

                    Case RegistersTypes.DS : DS = value
                    Case RegistersTypes.SI : SI = value

                    Case RegistersTypes.ES : ES = value
                    Case RegistersTypes.DI : DI = value

                    Case RegistersTypes.BP : BP = value

                    Case Else : Throw New Exception("Invalid Register")
                End Select
            End Set
        End Property

        Public Property AX As Integer
            Get
                Return (AH << 8) Or AL
            End Get
            Set(value As Integer)
                AH = value >> 8
                AL = value And &HFF
            End Set
        End Property
        Public Property AL As Integer
        Public Property AH As Integer

        Public Property BX As Integer
            Get
                Return (BH << 8) Or BL
            End Get
            Set(value As Integer)
                BH = value >> 8
                BL = value And &HFF
            End Set
        End Property
        Public Property BL As Integer
        Public Property BH As Integer

        Public Property CX As Integer
            Get
                Return (CH << 8) Or CL
            End Get
            Set(value As Integer)
                CH = value >> 8
                CL = value And &HFF
            End Set
        End Property
        Public Property CL As Integer
        Public Property CH As Integer

        Public Property DX As Integer
            Get
                Return (DH << 8) Or DL
            End Get
            Set(value As Integer)
                DH = value >> 8
                DL = value And &HFF
            End Set
        End Property
        Public Property DL As Integer
        Public Property DH As Integer

        Public Property CS As Integer
        Public Property IP As Integer

        Public Property SS As Integer
        Public Property SP As Integer

        Public Property DS As Integer
        Public Property SI As Integer

        Public Property ES As Integer
        Public Property DI As Integer

        Public Property BP As Integer

        Public Property ActiveSegmentRegister As GPRegisters.RegistersTypes
            Get
                Return mActiveSegmentRegister
            End Get
            Set(value As GPRegisters.RegistersTypes)
                mActiveSegmentRegister = value
                mActiveSegmentChanged = (value <> RegistersTypes.DS)
                If mActiveSegmentChanged Then mActiveSegmentValue = Val(mActiveSegmentRegister)
            End Set
        End Property

        Public ReadOnly Property ActiveSegmentValue As Integer
            Get
                If mActiveSegmentChanged Then
                    Return mActiveSegmentValue
                Else
                    Return DS
                End If
            End Get
        End Property

        Public ReadOnly Property ActiveSegmentChanged As Boolean
            Get
                Return mActiveSegmentChanged
            End Get
        End Property

        Public ReadOnly Property PointerAddressToString() As String
            Get
                Return CS.ToHex(DataSize.Word) + ":" + IP.ToHex(DataSize.Word)
            End Get
        End Property

        Public Function Clone() As Object Implements System.ICloneable.Clone
            Dim reg = New GPRegisters()
            reg.AX = AX
            reg.BX = BX
            reg.CX = CX
            reg.DX = DX
            reg.ES = ES
            reg.CS = CS
            reg.SS = SS
            reg.DS = DS
            reg.SP = SP
            reg.BP = BP
            reg.SI = SI
            reg.DI = DI
            reg.IP = IP
            Return reg
        End Function
    End Class

    Public Class GPFlags
        Implements ICloneable

        Public Enum FlagsTypes
            NONE = 0

            CF = 2 ^ 0
            PF = 2 ^ 2
            AF = 2 ^ 4
            ZF = 2 ^ 6
            SF = 2 ^ 7
            TF = 2 ^ 8
            [IF] = 2 ^ 9
            DF = 2 ^ 10
            [OF] = 2 ^ 11
            NT = 2 ^ 14
        End Enum

        Public Property CF As Integer
        Public Property PF As Integer
        Public Property AF As Integer
        Public Property ZF As Integer
        Public Property SF As Integer
        Public Property TF As Integer
        Public Property [IF] As Integer
        Public Property DF As Integer
        Public Property [OF] As Integer
        Public Property NT As Integer

        Public Property EFlags() As Integer
            Get
                Return CF * FlagsTypes.CF +
                        1 * 2 ^ 1 +
                       PF * FlagsTypes.PF +
                        0 * 2 ^ 3 +
                       AF * FlagsTypes.AF +
                        0 * 2 ^ 5 +
                       ZF * FlagsTypes.ZF +
                       SF * FlagsTypes.SF +
                       TF * FlagsTypes.TF +
                     [IF] * FlagsTypes.IF +
                       DF * FlagsTypes.DF +
                     [OF] * FlagsTypes.OF +
                        0 * 2 ^ 12 +
                        0 * 2 ^ 13 '+
                'NT * FlagsTypes.NT +
                ' 0 * 2 ^ 15
            End Get
            Set(value As Integer)
                CF = If((value And FlagsTypes.CF) = FlagsTypes.CF, 1, 0)
                PF = If((value And FlagsTypes.PF) = FlagsTypes.PF, 1, 0)
                AF = If((value And FlagsTypes.AF) = FlagsTypes.AF, 1, 0)
                ZF = If((value And FlagsTypes.ZF) = FlagsTypes.ZF, 1, 0)
                SF = If((value And FlagsTypes.SF) = FlagsTypes.SF, 1, 0)
                TF = If((value And FlagsTypes.TF) = FlagsTypes.TF, 1, 0)
                [IF] = If((value And FlagsTypes.IF) = FlagsTypes.IF, 1, 0)
                DF = If((value And FlagsTypes.DF) = FlagsTypes.DF, 1, 0)
                [OF] = If((value And FlagsTypes.OF) = FlagsTypes.OF, 1, 0)
                'NT = If((value And FlagsTypes.NT) = FlagsTypes.NT, 1, 0)
            End Set
        End Property

        Public Function Clone() As Object Implements System.ICloneable.Clone
            Dim f As GPFlags = New GPFlags()
            f.EFlags = EFlags
            f.CF = CF
            f.PF = PF
            f.AF = AF
            f.ZF = ZF
            f.SF = SF
            f.TF = TF
            f.IF = [IF]
            f.DF = DF
            f.OF = [OF]
            f.NT = NT
            Return f
        End Function
    End Class

    Public Sub LoadBIN(fileName As String, segment As Integer, offset As Integer)
        fileName = x8086.FixPath(fileName)

        If IO.File.Exists(fileName) Then
            CopyToRAM(IO.File.ReadAllBytes(fileName), segment, offset)
        Else
            MsgBox("File Not Found: " + vbCrLf + fileName, MsgBoxStyle.Critical Or MsgBoxStyle.OkOnly, "LoadBIN")
        End If
    End Sub

    Public Sub CopyToRAM(bytes() As Byte, segment As Integer, offset As Integer, Optional ignoreChecks As Boolean = False)
        CopyToRAM(bytes, x8086.SegOffToAbsolute(segment, offset))
    End Sub

    Public Sub CopyToRAM(bytes() As Byte, address As Integer)
        Array.Copy(bytes, 0, Memory, address, bytes.Length)
    End Sub

    Public Sub ReadFromRAM(bytes() As Byte, address As Integer)
        Array.Copy(Memory, address, bytes, 0, bytes.Length)
    End Sub

    Public Property Registers As GPRegisters
        Get
            Return mRegisters
        End Get
        Set(value As GPRegisters)
            mRegisters = value
        End Set
    End Property

    Public Property Flags As GPFlags
        Get
            Return mFlags
        End Get
        Set(value As GPFlags)
            mFlags = value
        End Set
    End Property

    Private Sub PushIntoStack(value As Integer)
        mRegisters.SP = AddValues(mRegisters.SP, -2, DataSize.Word)
        RAM16(mRegisters.SS, mRegisters.SP) = value
    End Sub

    Private Function PopFromStack() As Integer
        Dim value As Integer = RAM16(mRegisters.SS, mRegisters.SP)
        mRegisters.SP = AddValues(mRegisters.SP, 2, DataSize.Word)
        Return value
    End Function

    Public Shared Function SegOffToAbsolute(segment As Integer, offset As Integer) As Integer
        Return (segment << 4) + offset
    End Function

    Public Shared Function AbsoluteToSeg(address As Integer) As Integer
        Return (address >> 4) And &HFFF00
    End Function

    Public Shared Function AbsoluteToOff(address As Integer) As Integer
        Return address And &HFFF
    End Function

    Public Property RAM(address As Integer) As Byte
        Get
            ' "Call 5" Legacy Interface: http://www.os2museum.com/wp/?p=734
            If address >= MemSize Then address = address And &HFFFFF

            'If mDebugMode Then RaiseEvent MemoryAccess(Me, New MemoryAccessEventArgs(address, MemoryAccessEventArgs.AccessModes.Read))
            Return Memory(address)
        End Get
        Set(value As Byte)
            If address >= ROMStart Then Exit Property
            Memory(address) = value
            'If mDebugMode Then RaiseEvent MemoryAccess(Me, New MemoryAccessEventArgs(address, MemoryAccessEventArgs.AccessModes.Write))
        End Set
    End Property

    Public Property RAM8(segment As Integer, offset As Integer, Optional inc As Integer = 0) As Byte
        Get
            Return RAM(SegOffToAbsolute(segment, offset + inc))
        End Get
        Set(value As Byte)
            RAM(SegOffToAbsolute(segment, offset + inc)) = value
        End Set
    End Property

    Public Property RAMn() As Integer
        Get
            If addrMode.Size = DataSize.Byte Then
                Return RAM8(mRegisters.ActiveSegmentValue, addrMode.IndAdr)
            Else
                Return RAM16(mRegisters.ActiveSegmentValue, addrMode.IndAdr)
            End If
        End Get
        Set(value As Integer)
            If addrMode.Size = DataSize.Byte Then
                RAM8(mRegisters.ActiveSegmentValue, addrMode.IndAdr) = value
            Else
                RAM16(mRegisters.ActiveSegmentValue, addrMode.IndAdr) = value
            End If
        End Set
    End Property

    Public Property RAM16(segment As Integer, offset As Integer, Optional inc As Integer = 0) As Integer
        Get
            Dim address As Integer = SegOffToAbsolute(segment, offset + inc)
            'Return RAM8(segment, offset, inc + 1) * &H100 + RAM8(segment, offset, inc)
            Return RAM(address + 1) * &H100 + RAM(address)
        End Get
        Set(value As Integer)
            Dim address As Integer = SegOffToAbsolute(segment, offset + inc)
            'RAM8(segment, offset, inc) = (value And &HFF)
            'RAM8(segment, offset, inc + 1) = (value And &HFF00) >> 8
            RAM(address) = (value And &HFF)
            'RAM(address + 1) = (value And &HFF00) >> 8
            RAM(address + 1) = (value >> 8) And &HFF
        End Set
    End Property
End Class