#Region "System Includes"
Imports System.IO
Imports WindowsApplication1.MatrixMath
Imports WindowsApplication1.VectorMath
Imports WindowsApplication1.Utility
Imports TextureExtract
#End Region
#Region "C/C++ types in VB.NET"
Imports qword = System.UInt64
Imports dword = System.UInt32
Imports float = System.Single
Imports word = System.UInt16
Imports bool = System.Boolean
#End Region

Public Class NJCM
    Public Class Types
        'Extensions to the basic data types go here
        Public Class XJModelData
            Public tag As dword
            Public ofs() As dword = New dword(3) {}
            Public fmt() As dword = New dword(3) {}
            Public center As New XYZCoord
            Public radius As float

            Sub New()
                Me.tag = 0
                Me.radius = 0
            End Sub
        End Class
        Public Class BMLEntry
            Implements ICloneable
            Public Filename As String
            Public CompLen As UInt32
            Public UWS As UInt32
            Public RawLen As UInt32
            Public CompPVMLen As UInt32
            Public RAWPVMLen As UInt32
            Sub New()
                With Me
                    .Filename = ""
                    .CompLen = 0
                    .UWS = 0
                    .RawLen = 0
                    .CompPVMLen = 0
                    .RAWPVMLen = 0
                End With
            End Sub
            Sub New(ByVal Nm As String, ByVal A As UInt32, ByVal B As UInt32, ByVal C As UInt32, ByVal D As UInt32)
                With Me
                    .Filename = Nm
                    .CompLen = A
                    .UWS = 0
                    .RawLen = B
                    .CompPVMLen = C
                    .RAWPVMLen = D
                End With
            End Sub
            Public Function Clone() As Object Implements System.ICloneable.Clone
                Return New BMLEntry(Me.Filename, Me.CompLen, Me.RawLen, Me.CompPVMLen, Me.RAWPVMLen)
            End Function
        End Class
        Public Class GSLEntry
            Implements ICloneable
            Public FileName As String
            Public FileFlag As dword
            Public FileLen As dword
            Public uws1 As dword
            Public uws2 As dword

            Sub New()
                Me.FileName = ""
                Me.FileFlag = &H0
                Me.FileLen = &H0
                Me.uws1 = 0
                Me.uws2 = 0
            End Sub

            Sub New(ByVal Name As String, ByVal Flags As dword, ByVal Length As dword, ByVal D As dword, ByVal E As dword)
                Me.FileName = Name
                Me.FileFlag = Flags
                Me.FileLen = Length
                Me.uws1 = D
                Me.uws2 = E
            End Sub
            Public Function Clone() As Object Implements System.ICloneable.Clone
                Return New GSLEntry(Me.FileName, Me.FileFlag, Me.FileLen, Me.uws1, Me.uws2)
            End Function
        End Class
        Public Class Motion
            Public Class MotionEntry
                Implements System.ICloneable

                Public NodeId As UInteger
                Public S As Boolean
                Public Translate As Types.XYZCoord
                Public Rotation As Types.XYZCoord
                Public Scale As Types.XYZCoord

                Sub New()
                    With Me
                        .NodeId = 0
                        .S = False
                        .Translate = New XYZCoord(0, 0, 0)
                        .Rotation = New XYZCoord(0, 0, 0)
                        .Scale = New XYZCoord(0, 0, 0)
                    End With
                End Sub
                Sub New(ByVal ID As Integer, ByVal D As Boolean, ByVal T As XYZCoord, ByVal R As XYZCoord, ByVal S As XYZCoord)
                    With Me
                        .NodeId = ID
                        .S = D
                        .Translate = T.Clone
                        .Rotation = R.Clone
                        .Scale = S.Clone
                    End With
                End Sub
                Public Function Clone() As Object Implements System.ICloneable.Clone
                    Return New MotionEntry(Me.NodeId, Me.S, Me.Translate.Clone, Me.Rotation.Clone, Me.Scale.Clone)
                End Function
            End Class
            Public Class KeyFrame
                Implements System.IDisposable
                Implements System.ICloneable

                Public Nodes As Integer
                Public Motion() As MotionEntry

                Sub New()
                    Me.Nodes = 0
                    Me.Motion = New MotionEntry(0) {}
                End Sub

                Sub New(ByVal N As Integer)
                    Me.Nodes = N
                    Me.Motion = New MotionEntry(N) {}
                End Sub

                Private disposedValue As Boolean = False        ' To detect redundant calls

                ' IDisposable
                Protected Overridable Sub Dispose(ByVal disposing As Boolean)
                    If Not Me.disposedValue Then
                        If disposing Then
                            ' TODO: free managed resources when explicitly called
                            For I As Integer = 0 To Me.Nodes - 1
                                Motion(I) = Nothing
                            Next
                        End If

                        ' TODO: free shared unmanaged resources
                    End If
                    Me.disposedValue = True
                End Sub

#Region " IDisposable Support "
                ' This code added by Visual Basic to correctly implement the disposable pattern.
                Public Sub Dispose() Implements IDisposable.Dispose
                    ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
                    Dispose(True)
                    GC.SuppressFinalize(Me)
                End Sub
#End Region

                Public Function Clone() As Object Implements System.ICloneable.Clone
                    Dim t As New Types.Motion.KeyFrame
                    With t
                        .Nodes = Me.Nodes
                        For I As Integer = 0 To Me.Nodes - 1
                            .Motion(I) = Me.Motion(I).Clone
                        Next
                    End With
                    Return t
                End Function
            End Class
            Public Class MotionData
                Implements System.ICloneable

                Public FrameCount As Integer
                Public NodeCount As Integer
                Public KeyFrames() As KeyFrame

                Sub New()
                    Me.FrameCount = 1
                    Me.NodeCount = 1
                    Me.KeyFrames = New KeyFrame(1) {}
                End Sub

                Sub New(ByVal Frames As Integer, ByVal Nodes As Integer)
                    Me.FrameCount = Frames
                    Me.NodeCount = Nodes
                    Me.KeyFrames = New KeyFrame(Nodes) {}
                End Sub

                Public Function Clone() As Object Implements System.ICloneable.Clone
                    Dim t As New MotionData(Me.FrameCount, Me.NodeCount)
                    With Me
                        For I As Integer = 0 To .FrameCount - 1
                            t.KeyFrames(I) = .KeyFrames(I).Clone
                        Next
                    End With
                    Return t
                End Function
            End Class
            Public Class MotionHeader
                Public ptd As UInteger
                Public nkf As Integer
                Public mep As UShort
                Public ifn As UShort

                ReadOnly Property Translation() As Boolean
                    Get
                        Return BitTest(Me.mep, 0)
                    End Get
                End Property
                ReadOnly Property Rotation() As Boolean
                    Get
                        Return BitTest(Me.mep, 1)
                    End Get
                End Property
                ReadOnly Property Scale() As Boolean
                    Get
                        Return BitTest(Me.mep, 2)
                    End Get
                End Property
                ReadOnly Property ElementPointers() As Integer
                    Get
                        Return CType((ifn And &HF), Integer)
                    End Get
                End Property
                Sub New()
                    With Me
                        .ptd = 0
                        .nkf = 0
                        .mep = 0
                        .ifn = 0
                    End With
                End Sub
                Sub New(ByVal A As UInteger, ByVal B As Integer, ByVal C As UShort, ByVal D As UShort)
                    With Me
                        .ptd = A
                        .nkf = B
                        .mep = C
                        .ifn = D
                    End With
                End Sub
            End Class
            Public Class BaseKey
                'Common Data Elements for all Raw Motion Data Keys
                'ty holds the type of motion read from the data
                Public ty As Integer
                'kf is the keyframe number
                Public kf As Integer
                Sub New()
                    Me.ty = 0
                    Me.kf = 0
                End Sub
                Sub New(ByVal t As Integer, ByVal k As Integer)
                    Me.ty = t
                    Me.kf = k
                End Sub
            End Class
            Public Class NJS_KEY_FA
                Inherits BaseKey
                Public kd As Types.XYZCoord
                Sub New()
                    MyBase.New(1, 0)
                    Me.kd = New XYZCoord(0, 0, 0)
                End Sub
                Sub New(ByVal t As Integer, ByVal k As Integer, ByVal A As XYZCoord)
                    MyBase.New(t, k)
                    Me.kd = A.Clone
                End Sub
            End Class
        End Class
    End Class
    Public Class IO
        'Extensions to the File IO class go here
        Public Class ExBinaryReader
            'Return Point Stack
            Private rtpstack As New Stack(Of dword)
            Public Sub PushPosition()
                Me.rtpstack.Push(Me.Position)
            End Sub
            Public Sub PopPosition()
                Me.Seek(Me.rtpstack.Pop, SeekOrigin.Begin)
            End Sub
            Public Function ReadXJModelData()
                Dim t As New NJCM.Types.XJModelData
                With t
                    .tag = Me.Readdword
                    For I As Integer = 0 To 2
                        .ofs(I) = Me.Readdword
                        .fmt(I) = Me.Readdword
                    Next
                    .center = Me.ReadXYZ
                    .radius = Me.Readfloat
                End With
                Return t
            End Function
            Public Function ReadBMLEntry() As Types.BMLEntry
                Dim t As New Types.BMLEntry
                With Me
                    t.Filename = MakeString(.ReadBytes(&H20))
                    t.CompLen = .ReadUInt32
                    t.UWS = .ReadUInt32
                    t.RawLen = .ReadUInt32
                    t.CompPVMLen = .ReadUInt32
                    t.RAWPVMLen = .ReadUInt32
                    .ReadBytes(&HC)
                End With
                Return t
            End Function
            Public Function ReadGSLEntry() As Types.GSLEntry
                '                         Filename occupies 0x20 bytes    FileFlags      FileLength     Unknown #1     Unknown #2
                Return New Types.GSLEntry(MakeString(Me.ReadBytes(&H20)), Me.ReadUInt32, Me.ReadUInt32, Me.ReadUInt32, Me.ReadUInt32)
            End Function
            Public Function ReadMotionHeader()
                Return New Types.Motion.MotionHeader(Me.ReadUInt32, Me.ReadInt32, Me.ReadUInt16, Me.ReadUInt16)
            End Function
            Public Function ReadTranslationMotion()
                Return New Types.Motion.NJS_KEY_FA(1, Me.ReadInt32, Me.ReadXYZ)
            End Function
            Public Function ReadRotationMotion()
                Return New Types.Motion.NJS_KEY_FA(2, Me.ReadInt32, Me.ReadABC)
            End Function
            Public Function ReadScaleMotion()
                Return New Types.Motion.NJS_KEY_FA(3, Me.ReadInt32, Me.ReadXYZ)
            End Function
        End Class
        Public Class ExBinaryWriter

        End Class
    End Class
    Public Class Model
        'Extensions to the Model Class go here
        Private xkey As dword = 0
        Public Sub XJProcess(ByVal Infile As String)
            Dim test As New NJCM.IO.ExBinaryReader(FileToBuffer(Infile))
            Dim thdr As New Types.NJHeader
            Dim nofs As Integer = 0
            Dim cpos As Integer = 0

            WriteLn(test.Length.ToString & " bytes read.")
            test.Seek(0, SeekOrigin.Begin)
            Do
                test.Seek(nofs, SeekOrigin.Current)
                cpos = test.Position
                thdr = test.ReadNJHeader
                WriteLn(HexOut(cpos) & " : " & thdr.ID)
                If thdr.ID = "NJCM" Then
                    xkey = test.Position
                    XJWalk(test, xkey)
                End If
                nofs = thdr.Nx
                If (cpos + 8) + nofs >= test.Length Then Exit Do
                StepControl()
            Loop While test.Position < test.Length
            WriteLn("End of File.")
            test.Close()
        End Sub
        Private Sub XJNodeReport(ByVal tnode As Types.Node)
            With tnode
                WriteLn("Eval Flags   = " & HexOut(.Flags), 2)
                WriteLn("Model Data   = " & HexOut(.Model), 2)
                WriteLn("Translate    = " & .Translate.ToString, 2)
                WriteLn("Rotation     = " & .Rotate.ToString, 2)
                WriteLn("Scale        = " & .Scale.ToString, 2)
                WriteLn("Child Node   = " & HexOut(.Child), 2)
                WriteLn("Sibling Node = " & HexOut(.Sibling), 2)
            End With
        End Sub
        Private Sub XJModelReport(ByVal tmodel As Types.XJModelData)
            With tmodel
                WriteLn("          Tag = " & HexOut(.tag), 2)
                WriteLn("Vertex Offset = " & HexOut(.ofs(0)), 2)
                WriteLn("     Template = " & HexOut(.fmt(0)), 2)
                WriteLn("  Mesh Offset = " & HexOut(.ofs(1)), 2)
                WriteLn("     Template = " & HexOut(.fmt(1)), 2)
                WriteLn("   ??? Offset = " & HexOut(.ofs(2)), 2)
                WriteLn("     Template = " & HexOut(.fmt(2)), 2)
                WriteLn("       Center = " & .center.ToString, 2)
                WriteLn("       Radius = " & .radius.ToString, 2)
            End With
        End Sub
        Private Sub XJWalk(ByVal wrk As IO.ExBinaryReader, ByVal sp As dword)
            wrk.PushPosition()
            wrk.Seek(sp, SeekOrigin.Begin)
            Dim node As New NJCM.Types.Node
            node = wrk.ReadNode
            With node
                WriteLn("Node Data @ " & HexOut(sp), 1)
                XJNodeReport(node)
                If .Model > 0 Then
                    Dim geom As New Types.XJModelData
                    wrk.PushPosition()
                    WriteLn("Model Data @ " & HexOut(xkey + .Model), 1)
                    wrk.Seek(xkey + .Model, SeekOrigin.Begin)
                    geom = wrk.ReadXJModelData
                    XJModelReport(geom)
                    wrk.PopPosition()
                End If
                If .Child <> 0 Then XJWalk(wrk, xkey + .Child)
                If .Sibling <> 0 Then XJWalk(wrk, xkey + .Sibling)
            End With
            wrk.PopPosition()
        End Sub
    End Class
    Public Class ExNJCMModel
        Public Model As NJCM.Model
        Public Motions() As Types.Motion.MotionData

        Private tmpMotions As New List(Of Types.Motion.MotionData)
        Private anim As IO.ExBinaryReader

        Function AddEmptyAnimation(ByVal NumFrames As Integer, ByVal NumNodes As Integer) As Integer
            'Creates an empty motion of NumFrames frames for NumNodes nodes
            Dim tmd As New Types.Motion.MotionData(NumFrames, NumNodes)
            For I As Integer = 0 To NumFrames - 1
                Using tkf As Types.Motion.KeyFrame = New Types.Motion.KeyFrame(NumNodes)
                    With tkf
                        For J As Integer = 0 To .Nodes - 1
                            .Motion(J) = New Types.Motion.MotionEntry
                        Next
                    End With
                    tmd.KeyFrames(I) = tkf.Clone
                End Using
                'Dispose of KeyFrame after use, so it can be re-used.
                tmpMotions.Add(tmd.Clone)
            Next
            Return tmpMotions.Count - 1
        End Function

        Sub NJMfromBuffer(ByVal buf() As Byte)
            'Extraordinarily Common Indexing Variable
            Dim I As Integer = 0
            'Create a ExBinReader from the memory buffer passed to it
            Dim strm As New IO.ExBinaryReader(buf)
            'Read the Header Block
            Dim a As Types.Motion.MotionHeader = strm.ReadMotionHeader
            'Determine How Many Nodes are Present in the Animation AND model.
            Dim nodes As Integer = (strm.Position - strm.ReadInt32) / (8 * a.ElementPointers)
            WriteLn("Nodes Present : " & HexOut(nodes))
            strm.Seek(a.ptd, SeekOrigin.Begin)
            'setup our animation information
            'idx contains the item number of our animation data
            'we'll need this to access the data when we fill the class
            Dim idx = AddEmptyAnimation(a.nkf, nodes)
            Dim mptr(a.ElementPointers) As Integer
            Dim mkey(a.ElementPointers) As Integer
            Dim mtyp(a.ElementPointers) As Integer
            Dim retv As Integer = 0
            Dim tmp1 As Integer = 1
            Dim tmp2 As Integer = a.mep
            'Need to Determine which motion data elements are present, and what gets read when.
            Do
                If (tmp2 And &H1) = 1 Then
                    mtyp(I) = tmp1
                    tmp2 = tmp2 >> 1
                    I += 1
                End If
            Loop Until I > a.ElementPointers

            For I = 0 To nodes - 1
                'all positions are from start of stream.
                For j As Integer = 0 To (2 * a.ElementPointers) - 1
                    If j < a.ElementPointers Then mptr((j Mod a.ElementPointers) + 1) = strm.ReadInt32
                    If j > a.ElementPointers Then mkey((j Mod a.ElementPointers) + 1) = strm.ReadInt32
                Next
                'Save the position, because we'll need to come back here at the end of the loop
                retv = strm.Position
                For j As Integer = 1 To a.ElementPointers
                    If mptr(j) > 0 Then
                        For k As Integer = 0 To a.nkf - 1
                            With tmpMotions(idx).KeyFrames(k).Motion(I)

                            End With
                            
                        Next
                    End If
                Next

                strm.Seek(retv, SeekOrigin.Begin)
            Next

            strm.Close()
        End Sub
        Sub NJMfromFile(ByVal fn As String)
            Dim fs As New BinaryReader(New FileStream(fn, FileMode.Open))
            fs.BaseStream.Seek(4, SeekOrigin.Begin)
            Dim bs As Integer = fs.ReadInt32
            Dim buf() As Byte = fs.ReadBytes(bs)
            fs.Close()
            NJMfromBuffer(buf)
        End Sub

    End Class
End Class
