/// <summary>
        /// Stub for now, creates n materials, which are named 0 to (n-1)
        /// </summary>
        /// <param name="coll"></param>
        /// <param name="tag_data"></param>
        /// <param name="pos"></param>
        /// <returns></returns>
        public long ParseMaterials(CollisionModel coll, BinaryReader reader, int count)
        {
            coll.Materials = new List<CollisionModel.Material>();

            for (uint i = 0; i < count; ++i)
            {
                var material = new CollisionModel.Material();
                material.Name = new StringId(0x140 + i);
                coll.Materials.Add(material);
            }

            return reader.BaseStream.Position + MATERIAL_TAGBLOCK_SIZE * count;
        }
        /// <summary>
        /// Parses regions into Halo Online collision 'Region' tagblocks.
        /// Names are not preserved.
        /// </summary>
        /// <param name="coll"></param>
        /// <param name="reader"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public long ParseRegions(CollisionModel coll, BinaryReader reader, int count)
        {
            var originalPos = reader.BaseStream.Position;

            //The number of permutations that have been parsed in total for all regions
            var n_parsed_permutations = 0;
            //Permutations for all regions follow sequentially after all regions
            var permutations_base_addr = reader.BaseStream.Position
                + count * REGION_TAGBLOCK_SIZE;

            coll.Regions = new List<CollisionModel.Region>();

            for (uint i = 0; i < count; ++i)
            {
                //configure the current region
                CollisionModel.Region region = new CollisionModel.Region();
                region.Name = new StringId(0x140 + i);

                //set up stream for reading number of permutations in current region
                reader.BaseStream.Position = originalPos
                    + (long)(i * REGION_TAGBLOCK_SIZE)
                    + REGION_PERMUTATION_OFFSET;

                uint n_permutations = (uint)BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);
                region.Permutations = new List<CollisionModel.Region.Permutation>();

                for (uint j = 0; j < n_permutations; ++j)
                {
                    var permutation = new CollisionModel.Region.Permutation();
                    permutation.Name = new StringId(0x140 + j);
                    permutation.Bsps = new List<BSP>();
                    region.Permutations.Add(permutation);
                }

                coll.Regions.Add(region);
                n_parsed_permutations += (int)n_permutations;
            }

            return permutations_base_addr + (n_parsed_permutations * PERMUTATION_SIZE);
        }
        /// <summary>
        /// This file parser will parse Halo 1 CE 'model_collision_geometry' tags.
        /// The addresses of the tagblocks inside the tag are likely to be garbage
        /// values. The Halo 1 CE development tool 'guerilla' does not use the 
        /// reflexive address value and expects chunks to occur in the order that
        /// the reflexives occur in the parent struct.
        /// 
        /// The Halo1 CE collision tag is used due to high compatibility and 
        /// availability of 'Tool' - a program which can compile collision tags.
        /// 
        /// The parser expects the following format:
        /// h1ce coll tag format:
        ///main struct
        ///all materials sequential
        ///all regions sequential
        ///all permutations sequential
        ///all path finding spheres sequential
        ///all nodes sequential
        ///bsp 0
        ///	   bsp0 3dnodes sequential
        ///	   ...
        ///	   bsp0 vertices sequential
        ///bsp 1
        ///	   ...
        ///...
        /// </summary>
        /// <param name="fpath"></param>
        /// <returns></returns>
        public bool ParseFromFile(string fpath)
        {

            FileStream fs = null;
            try
            {
                fs = new FileStream(fpath, FileMode.Open, FileAccess.Read);
            }
            catch (FileNotFoundException)
            {
                Console.WriteLine("The system cannot find the file specified.");
                return false;
            }

            var reader = new BinaryReader(fs);
            var coll = new CollisionModel();

            // h1 ce tags will have a 64 byte header. The main struct is immediately after.

            var len = reader.BaseStream.Length;
            reader.BaseStream.Position = MAIN_STRUCT_OFFSET;
            var size = ParseMain(coll, reader);

            if (len != size)
            {
                Console.WriteLine("length expected was not actual.\nexpected: " + len + ", actual: " + size);
                return false;
            }

            //builder succeeded
            Definition = coll;

            return true;
        }
        public long ParseMain(CollisionModel coll, BinaryReader reader)
        {
            // start of the main struct in the reader
            var originalPos = reader.BaseStream.Position;
            //location of the count of materials
            reader.BaseStream.Position += MAIN_MATERIAL_OFFSET;
            var n_materials = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);
            //change pos to the offset where the 'material' tagblocks begin
            reader.BaseStream.Position = originalPos + MAIN_REGION_OFFSET;
            var n_regions = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);
            reader.BaseStream.Position = originalPos + MAIN_PATHF_SPHERES_OFFSET;
            var n_pathf_spheres = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);
            reader.BaseStream.Position = originalPos + MAIN_NODES_OFFSET;
            var n_nodes = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);

            var afterReadPos = originalPos + MAIN_STRUCT_SIZE;
            //get the position after all of the materials
            reader.BaseStream.Position = afterReadPos;
            afterReadPos = ParseMaterials(coll, reader, n_materials);

            //Get the position after all of the sequentially stored regions and sequentially stored permutations
            reader.BaseStream.Position = afterReadPos;

            afterReadPos = ParseRegions(coll, reader, n_regions);
            //set to beginning of sequential list of path finding spheres.
            reader.BaseStream.Position = afterReadPos;
            afterReadPos = ParsePathFindingSpheres(coll, reader, n_pathf_spheres);

            reader.BaseStream.Position = afterReadPos;
            afterReadPos = ParseNodes(coll, reader, n_nodes);

            return afterReadPos;
        }
        public long ParseBSP(CollisionModel.Region.Permutation permutation, BinaryReader reader)
        {
            long originalPos = reader.BaseStream.Position;
            var bsp = new BSP();
            permutation.Bsps.Add(bsp);

            reader.BaseStream.Position = originalPos + BSP_BSP3DNODES_OFFSET;
            int n_3dnodes = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);

            reader.BaseStream.Position = originalPos + BSP_PLANES_OFFSET;
            int n_planes = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);

            reader.BaseStream.Position = originalPos + BSP_LEAVES_OFFSET;
            int n_leaves = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);

            reader.BaseStream.Position = originalPos + BSP_BSP2DREFERENCES_OFFSET;
            int n_2dreferences = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);

            reader.BaseStream.Position = originalPos + BSP_BSP2DNODES_OFFSET;
            int n_2dnodes = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);

            reader.BaseStream.Position = originalPos + BSP_SURFACES_OFFSET;
            int n_surfaces = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);

            reader.BaseStream.Position = originalPos + BSP_EDGES_OFFSET;
            int n_edges = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);

            reader.BaseStream.Position = originalPos + BSP_VERTICES_OFFSET;
            int n_vertices = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);

            reader.BaseStream.Position = originalPos + BSP_SIZE;
            reader.BaseStream.Position = ParseBSP3DNodes(bsp, reader, n_3dnodes);
            reader.BaseStream.Position = ParsePlanes(bsp, reader, n_planes);
            reader.BaseStream.Position = ParseLeaves(bsp, reader, n_leaves);
            reader.BaseStream.Position = ParseBSP2DReferences(bsp, reader, n_2dreferences);
            reader.BaseStream.Position = ParseBSP2DNodes(bsp, reader, n_2dnodes);
            reader.BaseStream.Position = ParseSurfaces(bsp, reader, n_surfaces);
            reader.BaseStream.Position = ParseEdges(bsp, reader, n_edges);
            return ParseVertices(bsp, reader, n_vertices);
        }
        /// <summary>
        /// Parses all H1CE Collision Node tagblocks stored sequentially.
        /// The purpose of 'Node' is similar to 'Region' in Halo Online.
        /// </summary>
        /// <param name="coll"></param>
        /// <param name="reader"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public long ParseNodes(CollisionModel coll, BinaryReader reader, int count)
        {
            var originalPos = reader.BaseStream.Position;
            coll.Nodes = new List<CollisionModel.Node>();
            //destroy the old list of regions, it may not be fine-grained enough
            coll.Regions = new List<CollisionModel.Region>();

            var new_region_count = 0;
            var current_bsp_offset = originalPos + (count * NODE_SIZE);

            for (var i = 0; i < count; ++i)
            {
                var node = new CollisionModel.Node();
                node.Name = new StringId(0x140 + (uint)i);

                //offset of the parent node in the h1 ce node tagblock
                reader.BaseStream.Position = originalPos + (i * NODE_SIZE) + 32;
                short region_idx = BitConverter.ToInt16(reader.ReadBytes(2).Reverse().ToArray(), 0);
                short parent_node_idx = BitConverter.ToInt16(reader.ReadBytes(2).Reverse().ToArray(), 0);
                short next_sibling_idx = BitConverter.ToInt16(reader.ReadBytes(2).Reverse().ToArray(), 0);
                short first_child_idx = BitConverter.ToInt16(reader.ReadBytes(2).Reverse().ToArray(), 0);

                node.ParentNode = parent_node_idx;
                node.NextSiblingNode = next_sibling_idx;
                node.FirstChildNode = first_child_idx;

                coll.Nodes.Add(node);

                //there exists a region when the region index of the h1 ce collision node tagblock is not null
                if (region_idx >= 0)
                {
                    var region = new CollisionModel.Region();
                    coll.Regions.Add(region);
                    region.Name = new StringId(0x140 + (uint)new_region_count);
                    reader.BaseStream.Position = originalPos + (i * NODE_SIZE) + 52; //bsp tagblock count

                    //each bsp is placed into a separate permutation. In h1 ce
                    // a node referencing a region with n permutations has n bsps
                    region.Permutations = new List<CollisionModel.Region.Permutation>();
                    CollisionModel.Region.Permutation permutation = new CollisionModel.Region.Permutation();
                    region.Permutations.Add(permutation);
                    permutation.Bsps = new List<BSP>();
                    uint n_bsps = (uint)BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);
                    for (uint j = 0; j < n_bsps; ++j)
                    {
                        reader.BaseStream.Position = current_bsp_offset;
                        current_bsp_offset = ParseBSP(permutation, reader);
                    }
                    new_region_count++;
                }
            }

            return current_bsp_offset;
        }
        public long ParsePathFindingSpheres(CollisionModel coll, BinaryReader reader, int count)
        {
            var originalPos = reader.BaseStream.Position;

            coll.PathfindingSpheres = new List<CollisionModel.PathfindingSphere>();

            for (uint i = 0; i < count; ++i)
            {
                var pfSphere = new CollisionModel.PathfindingSphere();
                reader.BaseStream.Position = originalPos + (i * PATHF_SPHERE_SIZE);
                var node_idx = BitConverter.ToInt16(reader.ReadBytes(2).Reverse().ToArray(), 0);
                reader.BaseStream.Position += 14; //14 bytes between node index and sphere location,radius
                var center_x = BitConverter.ToSingle(reader.ReadBytes(4).Reverse().ToArray(), 0);
                var center_y = BitConverter.ToSingle(reader.ReadBytes(4).Reverse().ToArray(), 0);
                var center_z = BitConverter.ToSingle(reader.ReadBytes(4).Reverse().ToArray(), 0);
                var radius = BitConverter.ToSingle(reader.ReadBytes(4).Reverse().ToArray(), 0);

                pfSphere.CenterX = center_x;
                pfSphere.CenterY = center_y;
                pfSphere.CenterZ = center_z;
                pfSphere.Radius = radius;
                pfSphere.Node = node_idx;

                coll.PathfindingSpheres.Add(pfSphere);
            }

            return originalPos + (count * PATHF_SPHERE_SIZE);
        }