# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#

import math
from mathutils import Vector
from mathutils import Quaternion
from . import kex_utils
from . import kex_archive

MAP_CHUNK_ROOT_VERSION = 0
MAP_CHUNK_ROOT_WORLD = 1
MAP_CHUNK_ROOT_WORLDSKYMAT = 2
MAP_CHUNK_ROOT_COLLISION = 3
MAP_CHUNK_ROOT_GRIDBOUNDS = 4
MAP_CHUNK_ROOT_GRIDSECTIONS = 5
MAP_CHUNK_ROOT_ACTORS = 6
MAP_CHUNK_ROOT_VISIBILITY = 7
MAP_CHUNK_ROOT_COUNT = 8

MAP_CHUNK_WORLD_SUNDIRECTION = 0
MAP_CHUNK_WORLD_SUNCOLOR = 1
MAP_CHUNK_WORLD_AMBIENTCOLOR = 2
MAP_CHUNK_WORLD_COUNT = 3

MAP_CHUNK_COLLISION_VERTICES = 0
MAP_CHUNK_COLLISION_SECTORSETS = 1
MAP_CHUNK_COLLISION_SECTORS = 2
MAP_CHUNK_COLLISION_COUNT = 3

MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE = 0
MAP_CHUNK_GRIDBOUNDS_MIN = 1
MAP_CHUNK_GRIDBOUNDS_MAX = 2
MAP_CHUNK_GRIDBOUNDS_COUNT = 3

MAP_CHUNK_GRIDSECTIONS_MESHES = 0
MAP_CHUNK_GRIDSECTIONS_MODELFILES = 1
MAP_CHUNK_GRIDSECTIONS_COUNT = 2

MAP_CHUNK_ACTOR_INFO = 0
MAP_CHUNK_ACTOR_MODELFILES = 1
MAP_CHUNK_ACTOR_ANIMFILES = 2
MAP_CHUNK_ACTOR_COUNT = 3

SIZEOF_VERSION = 4
SIZEOF_VECTOR3 = 12
SIZEOF_VECTOR4 = 16
SIZEOF_COLLISION_VERTICE = 16
SIZEOF_COLLISION_SECTORSET = 64
SIZEOF_COLLISION_SECTOR = 16
SIZEOF_COLLISION_SECTOREX = 18
SIZEOF_STATICMESH = 88
SIZEOF_ACTORINFO = 140

# -----------------------------------------------------------------------------
# 
class Map:
    def __init__(self):
        self.version = 1
        self.worldInfo = {
            "sunlightDirection": Vector((0.0, 0.0, 0.0)),
            "sunlightColor": (0.0, 0.0, 0.0, 1.0),
            "ambientColor": (0.24705882, 0.24705882, 0.24705882, 1.0),
            "material": ""
        }
        self.collisionVertices = []     #array of vec4 (0.0, 0.0, 0.0, 0.0)
        self.collisionSectorSets = []   #array of SectorSet
        self.collisionSectors = []      #array of Sector
        self.gridBounds = GridBounds()
        self.actors = []                #array of Actor
        self.visibility = None          #None or bytes object
        self.visibilityCount = 0
        self.visibilitySize = 0
    #end func
    
    def read(self, data, visTables):
        arcMap = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_INDEXED, data, 0)
        
        #Read Root Version
        data.seek(arcMap.get_index_offset(MAP_CHUNK_ROOT_VERSION))
        self.version = kex_utils.read32(data, False) #should be 1
        
        #Read Root World Properties
        arcWorld = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_INDEXED, data, arcMap.get_index_offset(MAP_CHUNK_ROOT_WORLD))
        data.seek(arcWorld.get_index_offset(MAP_CHUNK_WORLD_SUNDIRECTION))
        self.worldInfo["sunlightDirection"] = kex_utils.readVector(data)
        data.seek(arcWorld.get_index_offset(MAP_CHUNK_WORLD_SUNCOLOR))
        self.worldInfo["sunlightColor"] = kex_utils.readVector4(data)
        data.seek(arcWorld.get_index_offset(MAP_CHUNK_WORLD_AMBIENTCOLOR))
        self.worldInfo["ambientColor"] = kex_utils.readVector4(data)
        self.worldInfo["material"] = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data, arcMap.get_index_offset(MAP_CHUNK_ROOT_WORLDSKYMAT)).as_string(data)
        
        #Read Root Collision
        arcCollision = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_INDEXED, data, arcMap.get_index_offset(MAP_CHUNK_ROOT_COLLISION))
        
        #Read Collision Vertices
        arcCollisionVerts = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data, arcCollision.get_index_offset(MAP_CHUNK_COLLISION_VERTICES))
        self.collisionVertices = [None] * arcCollisionVerts.count
        for i in range(arcCollisionVerts.count):
            data.seek(arcCollisionVerts.get_index_offset(i))
            self.collisionVertices[i] = kex_utils.readVector4(data)
        #end for
        
        #Read Collision SectorSets
        arcCollisionSectorSets = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data, arcCollision.get_index_offset(MAP_CHUNK_COLLISION_SECTORSETS))
        self.collisionSectorSets = [None] * arcCollisionSectorSets.count
        for i in range(arcCollisionSectorSets.count):
            self.collisionSectorSets[i] = SectorSet()
            self.collisionSectorSets[i].read(data, arcCollisionSectorSets.get_index_offset(i))
        #end for
        
        #Read Collision Sectors
        arcCollisionSectors = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data, arcCollision.get_index_offset(MAP_CHUNK_COLLISION_SECTORS))
        self.collisionSectors = [None] * arcCollisionSectors.count
        for i in range(arcCollisionSectors.count):
            self.collisionSectors[i] = Sector()
            self.collisionSectors[i].read(data, arcCollisionSectors.get_index_offset(i), arcCollisionSectors.arcDatasetSize)
        #end for
        
        #Read Root Grid Bounds and Grid Sections
        self.gridBounds.read_from_grid_bounds(data, kex_archive.Archive.from_read_buffer(kex_archive.TYPE_INDEXED, data, arcMap.get_index_offset(MAP_CHUNK_ROOT_GRIDBOUNDS)))
        self.gridBounds.read_from_grid_sections(data, kex_archive.Archive.from_read_buffer(kex_archive.TYPE_INDEXED, data, arcMap.get_index_offset(MAP_CHUNK_ROOT_GRIDSECTIONS)))
        
        #Read Root Actors
        arcActors = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_INDEXED, data, arcMap.get_index_offset(MAP_CHUNK_ROOT_ACTORS))
        
        arcActorsInfo = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data, arcActors.get_index_offset(MAP_CHUNK_ACTOR_INFO))
        self.actors = [None] * arcActorsInfo.count
        for i in range(arcActorsInfo.count):
            self.actors[i] = Actor()
            self.actors[i].read(data, arcActorsInfo.get_index_offset(i))
        #end for
        arcActorsModelFiles = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_INDEXED, data, arcActors.get_index_offset(MAP_CHUNK_ACTOR_MODELFILES))
        for i in range(arcActorsModelFiles.count):
            self.actors[i].modelPath = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data, arcActorsModelFiles.get_index_offset(i)).as_string(data)
        #end for
        arcActorsAnimFiles = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_INDEXED, data, arcActors.get_index_offset(MAP_CHUNK_ACTOR_ANIMFILES))
        for i in range(arcActorsAnimFiles.count):
            self.actors[i].animPath = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data, arcActorsAnimFiles.get_index_offset(i)).as_string(data)
        #end for

        #Read Root Visibility (if has it)
        if visTables and arcMap.count > MAP_CHUNK_ROOT_VISIBILITY:
            arcVis = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data, arcMap.get_index_offset(MAP_CHUNK_ROOT_VISIBILITY))
            bytesLength = arcVis.count * arcVis.arcDatasetSize
            self.visibilityCount = arcVis.count
            self.visibilitySize = arcVis.arcDatasetSize
            data.seek(arcVis.get_index_offset(0))
            self.visibility = kex_utils.readBytes(data, bytesLength)
        else:
            self.visibility = None
            self.visibilityCount = 0
            self.visibilitySize = 0
        #end if
    #end func
    
    def write(self, data):
        #-----Create Archives of this map structure for writing
        rootChunks = MAP_CHUNK_ROOT_COUNT
        if self.visibilityCount == 0:
            rootChunks -= 1
        arcMap = kex_archive.Archive(kex_archive.TYPE_INDEXED, 0, rootChunks)
        
        #version
        arcMap.set_block_size(MAP_CHUNK_ROOT_VERSION, None, SIZEOF_VERSION + 4) #has 4 bytes of padding
        
        #World Properties
        arcWorld = kex_archive.Archive(kex_archive.TYPE_INDEXED, arcMap.get_index_offset(MAP_CHUNK_ROOT_WORLD), MAP_CHUNK_WORLD_COUNT)
        arcWorld.set_block_size(MAP_CHUNK_WORLD_SUNDIRECTION, None, SIZEOF_VECTOR3 + 4) #has 4 bytes of padding
        arcWorld.set_block_size(MAP_CHUNK_WORLD_SUNCOLOR, None, SIZEOF_VECTOR4)
        arcWorld.set_block_size(MAP_CHUNK_WORLD_AMBIENTCOLOR, None, SIZEOF_VECTOR4)
        
        arcMap.set_block(MAP_CHUNK_ROOT_WORLD, arcWorld)
        arcMap.set_block(MAP_CHUNK_ROOT_WORLDSKYMAT, kex_archive.Archive.from_string(arcMap.get_index_offset(MAP_CHUNK_ROOT_WORLDSKYMAT), self.worldInfo["material"]))
        
        #Collision
        arcCollision = kex_archive.Archive(kex_archive.TYPE_INDEXED, arcMap.get_index_offset(MAP_CHUNK_ROOT_COLLISION), MAP_CHUNK_COLLISION_COUNT)
        
        #Collision Vertices
        arcCollisionVerts = kex_archive.Archive(kex_archive.TYPE_DATASET, arcCollision.get_index_offset(MAP_CHUNK_COLLISION_VERTICES), len(self.collisionVertices), SIZEOF_COLLISION_VERTICE)
        arcCollision.set_block(MAP_CHUNK_COLLISION_VERTICES, arcCollisionVerts)
        
        #Collision SectorSets
        arcCollisionSectorSets = kex_archive.Archive(kex_archive.TYPE_DATASET, arcCollision.get_index_offset(MAP_CHUNK_COLLISION_SECTORSETS), len(self.collisionSectorSets), SIZEOF_COLLISION_SECTORSET)
        arcCollision.set_block(MAP_CHUNK_COLLISION_SECTORSETS, arcCollisionSectorSets)
        
        #Collision Sectors
        arcCollisionSectors = kex_archive.Archive(kex_archive.TYPE_DATASET, arcCollision.get_index_offset(MAP_CHUNK_COLLISION_SECTORS), len(self.collisionSectors), SIZEOF_COLLISION_SECTOREX)
        arcCollision.set_block(MAP_CHUNK_COLLISION_SECTORS, arcCollisionSectors)
        
        arcMap.set_block(MAP_CHUNK_ROOT_COLLISION, arcCollision)
        
        #GridBounds
        arcGridBounds = kex_archive.Archive(kex_archive.TYPE_INDEXED, arcMap.get_index_offset(MAP_CHUNK_ROOT_GRIDBOUNDS), MAP_CHUNK_GRIDBOUNDS_COUNT)
        
        #GridBounds BlockSize
        arcGridBoundsBlockSize = kex_archive.Archive(kex_archive.TYPE_DATASET, arcGridBounds.get_index_offset(MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE), 2, 2)
        arcGridBounds.set_block(MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE, arcGridBoundsBlockSize)
        
        #GridBounds Mins
        arcGridBoundsMins = kex_archive.Archive(kex_archive.TYPE_DATASET, arcGridBounds.get_index_offset(MAP_CHUNK_GRIDBOUNDS_MIN), len(self.gridBounds.sections), 8)
        arcGridBounds.set_block(MAP_CHUNK_GRIDBOUNDS_MIN, arcGridBoundsMins)
        
        #GridBounds Maxs
        arcGridBoundsMaxs = kex_archive.Archive(kex_archive.TYPE_DATASET, arcGridBounds.get_index_offset(MAP_CHUNK_GRIDBOUNDS_MAX), len(self.gridBounds.sections), 8)
        arcGridBounds.set_block(MAP_CHUNK_GRIDBOUNDS_MAX, arcGridBoundsMaxs)
        
        arcMap.set_block(MAP_CHUNK_ROOT_GRIDBOUNDS, arcGridBounds)
        
        #GridSections
        arcGridSections = kex_archive.Archive(kex_archive.TYPE_INDEXED, arcMap.get_index_offset(MAP_CHUNK_ROOT_GRIDSECTIONS), len(self.gridBounds.sections))
        for i in range(arcGridSections.count):
            arcGSection = None
            if len(self.gridBounds.sections[i].staticMeshes) > 0:
                arcGSection = kex_archive.Archive(kex_archive.TYPE_INDEXED, arcGridSections.get_index_offset(i), MAP_CHUNK_GRIDSECTIONS_COUNT)
                #StaticMeshes
                arcStaticMeshes = kex_archive.Archive(kex_archive.TYPE_DATASET, arcGSection.get_index_offset(MAP_CHUNK_GRIDSECTIONS_MESHES), len(self.gridBounds.sections[i].staticMeshes), SIZEOF_STATICMESH)
                arcGSection.set_block(MAP_CHUNK_GRIDSECTIONS_MESHES, arcStaticMeshes)
                
                #ModelFiles
                arcModelFiles = kex_archive.Archive(kex_archive.TYPE_INDEXED, arcGSection.get_index_offset(MAP_CHUNK_GRIDSECTIONS_MODELFILES), len(self.gridBounds.sections[i].staticMeshes))
                for i2 in range(len(self.gridBounds.sections[i].staticMeshes)):
                    arcModelName = kex_archive.Archive.from_string(arcModelFiles.get_index_offset(i2), self.gridBounds.sections[i].staticMeshes[i2].modelPath)
                    arcModelFiles.set_block(i2, arcModelName)
                #end for
                arcGSection.set_block(MAP_CHUNK_GRIDSECTIONS_MODELFILES, arcModelFiles)
            else:
                arcGSection = kex_archive.Archive(kex_archive.TYPE_INDEXED, arcGridSections.get_index_offset(i), 0)
            #end if
            arcGridSections.set_block(i, arcGSection)
        #end for
        
        arcMap.set_block(MAP_CHUNK_ROOT_GRIDSECTIONS, arcGridSections)
        
        #Actors
        arcActors = kex_archive.Archive(kex_archive.TYPE_INDEXED, arcMap.get_index_offset(MAP_CHUNK_ROOT_ACTORS), MAP_CHUNK_ACTOR_COUNT)
        
        arcActorsInfo = kex_archive.Archive(kex_archive.TYPE_DATASET, arcActors.get_index_offset(MAP_CHUNK_ACTOR_INFO), len(self.actors), SIZEOF_ACTORINFO)
        arcActors.set_block(MAP_CHUNK_ACTOR_INFO, arcActorsInfo)
        
        arcActorsModelFile = kex_archive.Archive(kex_archive.TYPE_INDEXED, arcActors.get_index_offset(MAP_CHUNK_ACTOR_MODELFILES), len(self.actors))
        for i in range(len(self.actors)):
            arcActorsModelFile.set_block(i, kex_archive.Archive.from_string(arcActorsModelFile.get_index_offset(i), self.actors[i].modelPath))
        #end for
        arcActors.set_block(MAP_CHUNK_ACTOR_MODELFILES, arcActorsModelFile)
        
        arcActorsAnimFile = kex_archive.Archive(kex_archive.TYPE_INDEXED, arcActors.get_index_offset(MAP_CHUNK_ACTOR_ANIMFILES), len(self.actors))
        for i in range(len(self.actors)):
            arcActorsAnimFile.set_block(i, kex_archive.Archive.from_string(arcActorsAnimFile.get_index_offset(i), self.actors[i].animPath))
        #end for
        arcActors.set_block(MAP_CHUNK_ACTOR_ANIMFILES, arcActorsAnimFile)
        
        arcMap.set_block(MAP_CHUNK_ROOT_ACTORS, arcActors)
        
        #Visibility
        arcVis = None
        if self.visibilityCount != 0:
            arcVis = kex_archive.Archive(kex_archive.TYPE_DATASET, arcMap.get_index_offset(MAP_CHUNK_ROOT_VISIBILITY), self.visibilityCount, self.visibilitySize)
            arcMap.set_block(MAP_CHUNK_ROOT_VISIBILITY, arcVis)
        #end if
        
        #----------Start Write----------
        arcMap.write_header(data)
        
        #Write Version
        data.seek(arcMap.get_index_offset(MAP_CHUNK_ROOT_VERSION))
        kex_utils.write32(data, self.version, False)
        
        arcWorld.write_header(data)
        data.seek(arcWorld.get_index_offset(MAP_CHUNK_WORLD_SUNDIRECTION))
        kex_utils.writeVector(data, self.worldInfo["sunlightDirection"])
        data.seek(arcWorld.get_index_offset(MAP_CHUNK_WORLD_SUNCOLOR))
        kex_utils.writeVector4(data, self.worldInfo["sunlightColor"])
        data.seek(arcWorld.get_index_offset(MAP_CHUNK_WORLD_AMBIENTCOLOR))
        kex_utils.writeVector4(data, self.worldInfo["ambientColor"])
        
        arcWorldSkyMat = arcMap.arcIndexArchives[MAP_CHUNK_ROOT_WORLDSKYMAT]
        arcWorldSkyMat.write_header(data)
        data.seek(arcWorldSkyMat.get_index_offset(0))
        kex_utils.writeString(data, self.worldInfo["material"])
        
        arcCollision.write_header(data)
        #Write Collision Vertices
        arcCollisionVerts.write_header(data)
        for i in range(arcCollisionVerts.count):
            data.seek(arcCollisionVerts.get_index_offset(i))
            kex_utils.writeVector4(data, self.collisionVertices[i])
        #end for

        #Write Collision SectorSets
        arcCollisionSectorSets.write_header(data)
        for i in range(arcCollisionSectorSets.count):
            self.collisionSectorSets[i].write(data, arcCollisionSectorSets.get_index_offset(i))
        #end for
        
        #Write Collision Sectors
        arcCollisionSectors.write_header(data)
        for i in range(arcCollisionSectors.count):
            self.collisionSectors[i].write(data, arcCollisionSectors.get_index_offset(i))
        #end for
        
        arcGridBounds.write_header(data)
        #Write Gridbounds Block Size
        arcGridBoundsBlockSize.write_header(data)
        data.seek(arcGridBoundsBlockSize.get_index_offset(0))
        kex_utils.write16(data, self.gridBounds.width, False)
        data.seek(arcGridBoundsBlockSize.get_index_offset(1))
        kex_utils.write16(data, self.gridBounds.height, False)

        #Write Gridbounds Mins
        arcGridBoundsMins.write_header(data)
        for i in range(arcGridBoundsMins.count):
            data.seek(arcGridBoundsMins.get_index_offset(i))
            kex_utils.writeVector2(data, self.gridBounds.sections[i].boundsMin)
        #end for
        
        #Write Gridbounds Maxs
        arcGridBoundsMaxs.write_header(data)
        for i in range(arcGridBoundsMaxs.count):
            data.seek(arcGridBoundsMaxs.get_index_offset(i))
            kex_utils.writeVector2(data, self.gridBounds.sections[i].boundsMax)
        #end for
        
        #Write GridSections
        arcGridSections.write_header(data)
        for i in range(arcGridSections.count):
            if len(self.gridBounds.sections[i].staticMeshes) > 0:
                arcSection = arcGridSections.arcIndexArchives[i]
                arcSection.write_header(data)
                
                #StaticMeshes
                arcStaticMeshes = arcSection.arcIndexArchives[MAP_CHUNK_GRIDSECTIONS_MESHES]
                arcStaticMeshes.write_header(data)
                for i2 in range(arcStaticMeshes.count):
                    self.gridBounds.sections[i].staticMeshes[i2].write(data, arcStaticMeshes.get_index_offset(i2))
                #end for
                
                #ModelFiles
                arcModelFiles = arcSection.arcIndexArchives[MAP_CHUNK_GRIDSECTIONS_MODELFILES]
                arcModelFiles.write_header(data)
                for i2 in range(arcModelFiles.count):
                    arcModelName = arcModelFiles.arcIndexArchives[i2]
                    arcModelName.write_header(data)
                    data.seek(arcModelName.get_index_offset(0))
                    kex_utils.writeString(data, self.gridBounds.sections[i].staticMeshes[i2].modelPath)
                #end for
            #end if
        #end for
        
        #Write Actors
        arcActors.write_header(data)
        arcActorsInfo.write_header(data)
        for i in range(arcActorsInfo.count):
            self.actors[i].write(data, arcActorsInfo.get_index_offset(i))
        #end for
        arcActorsModelFile.write_header(data)
        for i in range(arcActorsModelFile.count):
            arcModelFile = arcActorsModelFile.arcIndexArchives[i]
            arcModelFile.write_header(data)
            data.seek(arcModelFile.get_index_offset(0))
            kex_utils.writeString(data, self.actors[i].modelPath)
        #end for
        arcActorsAnimFile.write_header(data)
        for i in range(arcActorsAnimFile.count):
            arcAnimFile = arcActorsAnimFile.arcIndexArchives[i]
            arcAnimFile.write_header(data)
            data.seek(arcAnimFile.get_index_offset(0))
            kex_utils.writeString(data, self.actors[i].animPath)
        #end for
        
        #Write Visibility
        if arcVis is not None:
            arcVis.write_header(data)
            data.seek(arcVis.get_index_offset(0))
            kex_utils.writeBytes(data, self.visibility)
        #end if
    #end func
    
#end class

# -----------------------------------------------------------------------------
# 
class SectorSet:
    def __init__(self):
        self.flags = 0
        self.fogColor = (0, 0, 0, 255)
        self.waterColor = (0, 0, 0, 255)
        self.fogZFar = 0
        self.waterZFar = 0
        self.fogStart = 0
        self.waterStart = 0
        self.waterHeight = 0
        self.skyHeight = 0
        self.skySpeed = 0 #unused in Turok EX
        self.blendLength = 0
        self.args = [0] * 6
        self.floorImpactID = 0
        self.wallImpactID = 0
        self.ambience = 0
        self.automapColor = 0
        self.music = 0
        self.cullBits = 0 #default to 255
        self.unused1 = 0 #just padding in the mapfile
        self.unused2 = 0 #just padding in the mapfile
    #end func
    # -----------------------------------------------------------------------------
    # 
    def read(self, data, offset):
        data.seek(offset)
        self.flags = kex_utils.read32(data, True)
        self.fogColor = kex_utils.readColorByteRGBA(data)
        self.waterColor = kex_utils.readColorByteRGBA(data)
        self.fogZFar = kex_utils.readFloat(data)
        self.waterZFar = kex_utils.readFloat(data)
        self.fogStart = kex_utils.readFloat(data)
        self.waterStart = kex_utils.readFloat(data)
        self.waterHeight = kex_utils.readFloat(data)
        self.skyHeight = kex_utils.readFloat(data)
        self.skySpeed = kex_utils.readFloat(data)
        self.blendLength = kex_utils.readFloat(data)
        for i in range(len(self.args)):
            self.args[i] = kex_utils.read16(data, True)
        self.floorImpactID = kex_utils.read8(data, False)
        self.wallImpactID = kex_utils.read8(data, False)
        self.ambience = kex_utils.read8(data, False)
        self.automapColor = kex_utils.read8(data, False)
        self.music = kex_utils.read8(data, False)
        self.cullBits = kex_utils.read8(data, False)
        self.unused1 = kex_utils.read8(data, False)
        self.unused2 = kex_utils.read8(data, False)
    #end func
    # -----------------------------------------------------------------------------
    # 
    def write(self, data, offset):
        data.seek(offset)
        kex_utils.write32(data, self.flags, True)
        kex_utils.writeColorByteRGBA(data, self.fogColor)
        kex_utils.writeColorByteRGBA(data, self.waterColor)
        kex_utils.writeFloat(data, self.fogZFar)
        kex_utils.writeFloat(data, self.waterZFar)
        kex_utils.writeFloat(data, self.fogStart)
        kex_utils.writeFloat(data, self.waterStart)
        kex_utils.writeFloat(data, self.waterHeight)
        kex_utils.writeFloat(data, self.skyHeight)
        kex_utils.writeFloat(data, self.skySpeed)
        kex_utils.writeFloat(data, self.blendLength)
        for i in range(len(self.args)):
            kex_utils.write16(data, self.args[i], True)
        kex_utils.write8(data, self.floorImpactID, False)
        kex_utils.write8(data, self.wallImpactID, False)
        kex_utils.write8(data, self.ambience, False)
        kex_utils.write8(data, self.automapColor, False)
        kex_utils.write8(data, self.music, False)
        kex_utils.write8(data, self.cullBits, False)
        kex_utils.write8(data, self.unused1, False)
        kex_utils.write8(data, self.unused2, False)
    #end func
#end class

# -----------------------------------------------------------------------------
# 
class Sector:
    def __init__(self):
        self.sectorSetIndex = 0
        self.flags = 0
        self.indices = [0] * 3
        self.edgeLinks = [0] * 3
        self.drawOrder = 0
    #end func
    # -----------------------------------------------------------------------------
    # 
    def read(self, data, offset, size):
        data.seek(offset)
        self.sectorSetIndex = kex_utils.read16(data, False)
        self.flags = kex_utils.read16(data, True)
        for i in range(len(self.indices)):
            self.indices[i] = kex_utils.read16(data, False)
        for i in range(len(self.edgeLinks)):
            self.edgeLinks[i] = kex_utils.read16(data, False)
        if size != SIZEOF_COLLISION_SECTOR:
            self.drawOrder = kex_utils.read16(data, True)
        else:
            self.drawOrder = 0
    #end func
    # -----------------------------------------------------------------------------
    # 
    def write(self, data, offset):
        data.seek(offset)
        kex_utils.write16(data, self.sectorSetIndex, False)
        kex_utils.write16(data, self.flags, True)
        for i in range(len(self.indices)):
            kex_utils.write16(data, self.indices[i], False)
        for i in range(len(self.edgeLinks)):
            kex_utils.write16(data, self.edgeLinks[i], False)
        kex_utils.write16(data, self.drawOrder, True)
    #end func
#end class

# -----------------------------------------------------------------------------
# 
class GridBounds:
    def __init__(self):
        self.width = 0
        self.height = 0
        self.sections = [] #array of GridSection
    #end func
    # -----------------------------------------------------------------------------
    # 
    def read_from_grid_bounds(self, data, arcGridBounds):
        #arcGridBounds should have 3 blocks: blocksize, min, max

        #Read Grid Bounds Block Size (should be 2, width/height)
        arcBlockSize = (kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data,
                        arcGridBounds.get_index_offset(MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE)))
        data.seek(arcBlockSize.get_index_offset(0))
        self.width = kex_utils.read16(data, False)
        data.seek(arcBlockSize.get_index_offset(1))
        self.height = kex_utils.read16(data, False)
        
        self.sections = [None] * (self.width * self.height)
        
        #Read Grid Bounds Sections Bounds Min
        arcBMins = (kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data,
                        arcGridBounds.get_index_offset(MAP_CHUNK_GRIDBOUNDS_MIN))) #should be same number as width*height
        for i in range(arcBMins.count):
            self.sections[i] = GridSection()
            data.seek(arcBMins.get_index_offset(i))
            self.sections[i].boundsMin = kex_utils.readVector2(data)
        #end for
        
        #Read Grid Bounds Sections Bounds Max
        arcBMaxs = (kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data,
                        arcGridBounds.get_index_offset(MAP_CHUNK_GRIDBOUNDS_MAX))) #should be same number as width*height
        for i in range(arcBMaxs.count):
            data.seek(arcBMaxs.get_index_offset(i))
            self.sections[i].boundsMax = kex_utils.readVector2(data)
        #end for
    #end func
    
    # -----------------------------------------------------------------------------
    # 
    def read_from_grid_sections(self, data, arcGridSections):
        #gridSectionsArc should be have same number of blocks as as width*height
        for i in range(arcGridSections.count):
            self.sections[i].read_from_grid_section(data, kex_archive.Archive.from_read_buffer(kex_archive.TYPE_INDEXED, data, arcGridSections.get_index_offset(i)))
        #end for
    #end func
#end class

# -----------------------------------------------------------------------------
# 
class GridSection:
    def __init__(self):
        self.boundsMin = (0.0, 0.0)
        self.boundsMax = (0.0, 0.0)
        self.staticMeshes = [] #array of StaticMesh
    #end func
    
    # -----------------------------------------------------------------------------
    # 
    def read_from_grid_section(self, data, arcSection):
        #sectionArc should have 2 blocks (meshes and model file paths) or 0
        if arcSection.count > 0:
            #Read Grid Sections Meshes
            arcMeshes = (kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data,
                            arcSection.get_index_offset(MAP_CHUNK_GRIDSECTIONS_MESHES)))
            self.staticMeshes = [None] * arcMeshes.count
            for i in range(arcMeshes.count):
                self.staticMeshes[i] = StaticMesh()
                self.staticMeshes[i].read(data, arcMeshes.get_index_offset(i))
            #end for
            
            #Read Grid Sections Model Files
            arcModelFiles = (kex_archive.Archive.from_read_buffer(kex_archive.TYPE_INDEXED, data,
                                arcSection.get_index_offset(MAP_CHUNK_GRIDSECTIONS_MODELFILES))) #should be same amount as meshesArc count
            for i in range(arcModelFiles.count):
                self.staticMeshes[i].modelPath = kex_archive.Archive.from_read_buffer(kex_archive.TYPE_DATASET, data, arcModelFiles.get_index_offset(i)).as_string(data)
            #end for
        #end if
    #end func
#end class

# -----------------------------------------------------------------------------
# 
class StaticMesh:
    def __init__(self):
        self.modelPath = ""
        self.origin = Vector((0.0, 0.0, 0.0))
        self.scale = Vector((0.35, 0.35, 0.35))
        self.rotation = Quaternion((1.0, 0.0, 0.0, 0.0))
        self.boundsMin = Vector((-0.5, -0.5, -0.5))
        self.boundsMax = Vector((0.0, 0.0, 0.0))
        self.texIndex = 0
        self.flags = 0
        self.sectorIndex = -1
        self.collisionWidth = 0.0
        self.collisionHeight = 0.0
        self.cullBits = -1
    #end func
    # -----------------------------------------------------------------------------
    # Doesn't set model path
    def read(self, data, offset):
        data.seek(offset)
        self.origin = kex_utils.readVector(data)
        self.scale = kex_utils.readVector(data)
        self.rotation = kex_utils.readQuat(data)
        self.boundsMin = kex_utils.readVector(data)
        self.boundsMax = kex_utils.readVector(data)
        self.texIndex = kex_utils.read32(data, True)
        self.flags = kex_utils.read32(data, True)
        self.sectorIndex = kex_utils.read32(data, True)
        self.collisionWidth = kex_utils.readFloat(data)
        self.collisionHeight = kex_utils.readFloat(data)
        self.cullBits = kex_utils.read32(data, True)
    #end func
    # -----------------------------------------------------------------------------
    # Doesn't write the model path
    def write(self, data, offset):
        data.seek(offset)
        kex_utils.writeVector(data, self.origin)
        kex_utils.writeVector(data, self.scale)
        kex_utils.writeQuat(data, self.rotation)
        kex_utils.writeVector(data, self.boundsMin)
        kex_utils.writeVector(data, self.boundsMax)
        kex_utils.write32(data, self.texIndex, True)
        kex_utils.write32(data, self.flags, True)
        kex_utils.write32(data, self.sectorIndex, True)
        kex_utils.writeFloat(data, self.collisionWidth)
        kex_utils.writeFloat(data, self.collisionHeight)
        kex_utils.write32(data, self.cullBits, True)
    #end func
#end class

# -----------------------------------------------------------------------------
# 
class Actor:
    def __init__(self):
        self.modelPath = ""
        self.animPath = ""
        self.actorID = 0                            #AKA Type in Turok EX
        self.origin = Vector((0.0, 0.0, 0.0))
        self.scale = Vector((0.5, 0.5, 0.5))
        self.yaw = 0.0                              #in rads
        self.sectorIndex = 0
        self.boundsMin = Vector((-0.5, -0.5, -0.5))
        self.boundsMax = Vector((0.5, 0.5, 0.5))
        self.sightRange = 0.0                       #squared
        self.sightFOV = 0.0                         #in rads
        self.loudRange = 0.0                        #squared
        self.quietRange = 0.0                       #squared
        self.attackRange = 0.0                      #squared
        self.flyHeight = 0.0                        #squared
        self.width = 0.0
        self.wallWidth = 0.0
        self.height = 0.0
        self.deadHeight = 0.0                       #AKA Step Height
        self.meleeRange = 0.0                       #squared
        self.leashRadius = 0.0                      #squared
        self.triggerDelay = 0.0
        self.speciesMask = 255                      #Unused in Turok EX
        self.spawnFlags1 = 0
        self.spawnFlags2 = 0
        self.spawnFlags3 = 0
        self.health = 0
        self.TID = 0
        self.targetTID = 0
        self.maxRegenerations = 0
        self.attackChance = 0
        self.triggerAnim = 0
        self.variant = 0
        self.texture = 0
        self.params1 = 0
        self.params2 = 0
    #end func
    # -----------------------------------------------------------------------------
    # Doesn't set model path
    def read(self, data, offset):
        data.seek(offset)
        self.actorID = kex_utils.read32(data, True)
        self.origin = kex_utils.readVector(data)
        self.scale = kex_utils.readVector(data)
        self.yaw = kex_utils.readFloat(data)
        self.sectorIndex = kex_utils.read32(data, True)
        self.boundsMin = kex_utils.readVector(data)
        self.boundsMax = kex_utils.readVector(data)
        self.sightRange = kex_utils.readFloat(data)
        self.sightFOV = kex_utils.readFloat(data)
        self.loudRange = kex_utils.readFloat(data)
        self.quietRange = kex_utils.readFloat(data)
        self.attackRange = kex_utils.readFloat(data)
        self.flyHeight = kex_utils.readFloat(data)
        self.width = kex_utils.readFloat(data)
        self.wallWidth = kex_utils.readFloat(data)
        self.height = kex_utils.readFloat(data)
        self.deadHeight = kex_utils.readFloat(data)
        self.meleeRange = kex_utils.readFloat(data)
        self.leashRadius = kex_utils.readFloat(data)
        self.triggerDelay = kex_utils.readFloat(data)
        self.speciesMask = kex_utils.read32(data, True)
        self.spawnFlags1 = kex_utils.read32(data, True)
        self.spawnFlags2 = kex_utils.read32(data, True)
        self.spawnFlags3 = kex_utils.read16(data, True)
        self.health = kex_utils.read16(data, True)
        self.TID = kex_utils.read16(data, True)
        self.targetTID = kex_utils.read16(data, True)
        self.maxRegenerations = kex_utils.read16(data, True)
        self.attackChance = kex_utils.read8(data, False)
        self.triggerAnim = kex_utils.read8(data, False)
        self.variant = kex_utils.read8(data, True)
        self.texture = kex_utils.read8(data, True)
        self.params1 = kex_utils.read8(data, True)
        self.params2 = kex_utils.read8(data, True)
    #end func
    # -----------------------------------------------------------------------------
    # Doesn't write the model path
    def write(self, data, offset):
        data.seek(offset)
        kex_utils.write32(data, self.actorID, True)
        kex_utils.writeVector(data, self.origin)
        kex_utils.writeVector(data, self.scale)
        kex_utils.writeFloat(data, self.yaw)
        kex_utils.write32(data, self.sectorIndex, True)
        kex_utils.writeVector(data, self.boundsMin)
        kex_utils.writeVector(data, self.boundsMax)
        kex_utils.writeFloat(data, self.sightRange) #math.pow(self.sightRange, 2)
        kex_utils.writeFloat(data, self.sightFOV)
        kex_utils.writeFloat(data, self.loudRange)
        kex_utils.writeFloat(data, self.quietRange)
        kex_utils.writeFloat(data, self.attackRange)
        kex_utils.writeFloat(data, self.flyHeight)
        kex_utils.writeFloat(data, self.width)
        kex_utils.writeFloat(data, self.wallWidth)
        kex_utils.writeFloat(data, self.height)
        kex_utils.writeFloat(data, self.deadHeight)
        kex_utils.writeFloat(data, self.meleeRange)
        kex_utils.writeFloat(data, self.leashRadius)
        kex_utils.writeFloat(data, self.triggerDelay)
        kex_utils.write32(data, self.speciesMask, True)
        kex_utils.write32(data, self.spawnFlags1, True)
        kex_utils.write32(data, self.spawnFlags2, True)
        kex_utils.write16(data, self.spawnFlags3, True)
        kex_utils.write16(data, self.health, True)
        kex_utils.write16(data, self.TID, True)
        kex_utils.write16(data, self.targetTID, True)
        kex_utils.write16(data, self.maxRegenerations, True)
        kex_utils.write8(data, self.attackChance, False)
        kex_utils.write8(data, self.triggerAnim, False)
        kex_utils.write8(data, self.variant, True)
        kex_utils.write8(data, self.texture, True)
        kex_utils.write8(data, self.params1, True)
        kex_utils.write8(data, self.params2, True)
    #end func
#end class
