/// <summary>
        /// Generates the scripting context for a cache file.
        /// </summary>
        /// <param name="reader">The stream to read from.</param>
        /// <param name="cache">The cache filr.</param>
        /// <param name="buildinfo">The cache file's build information.</param>
        /// <returns>Returns a <see cref="ScriptingContextCollection"/>.</returns>
        public static ScriptingContextCollection GenerateContext(IReader reader, ICacheFile cache, EngineDescription buildinfo)
        {
            var result  = new ScriptingContextCollection();
            var scnrTag = cache.Tags.FindTagByGroup("scnr");
            var mdlgTag = cache.Tags.FindTagByGroup("mdlg");

            if (scnrTag != null && buildinfo.Layouts.HasLayout("scnr_script_context"))
            {
                var scnrLayout = buildinfo.Layouts.GetLayout("scnr_script_context");
                reader.SeekTo(scnrTag.MetaLocation.AsOffset());
                var scnrValues = CacheStructureReader.ReadStructure(reader, cache, buildinfo, scnrLayout);
                FillContextCollection(reader, cache, buildinfo, scnrValues, result);
            }

            if (mdlgTag != null && buildinfo.Layouts.HasLayout("mdlg_script_context"))
            {
                var mdlgLayout = buildinfo.Layouts.GetLayout("mdlg_script_context");
                reader.SeekTo(mdlgTag.MetaLocation.AsOffset());
                var mdlgValues = CacheStructureReader.ReadStructure(reader, cache, buildinfo, mdlgLayout);
                FillContextCollection(reader, cache, buildinfo, mdlgValues, result);
            }

            return(result);
        }
        /// <summary>
        /// Processes a parent wrapper block and adds its child blocks to a context collection.
        /// </summary>
        /// <param name="block">The parent wrapper block.</param>
        /// <param name="collection">The context collection.</param>
        /// <exception cref="ArgumentException">Thrown when a parent wrapper block has more than one element.</exception>
        private static void HandleParentWrapperBlock(KeyValuePair <string, StructureValueCollection[]> block, ScriptingContextCollection collection)
        {
            if (block.Value.Length != 1)
            {
                throw new ArgumentException($"The wrapper context block {block.Key} doesn't have exactly one element.");
            }

            var innerBlocks = block.Value[0].GetTagBlocks();

            foreach (var inner in innerBlocks)
            {
                collection.AddObjectGroup(CreateContextBlock(inner, false));
            }
        }
        /// <summary>
        /// Fills a context collection with unit seat mappings and context objects.
        /// </summary>
        /// <param name="reader">The stream to read from.</param>
        /// <param name="cache">The cache file containing the scripting context.</param>
        /// <param name="buildinfo">The cache file's build information.</param>
        /// <param name="data">The meta data in which is the context is stored.</param>
        /// <param name="collection">The context collection.</param>
        private static void FillContextCollection(IReader reader, ICacheFile cache, EngineDescription buildinfo, StructureValueCollection data, ScriptingContextCollection collection)
        {
            var tagBlocks = data.GetTagBlocks();

            foreach (var block in tagBlocks)
            {
                if (block.Value.Length == 0)
                {
                    continue;
                }

                if (block.Key == "unit_seat_mapping")
                {
                    HandleUnitSeatMappings(reader, cache, buildinfo, block, collection);
                }
                else if (IsWrapperBlock(block))
                {
                    HandleParentWrapperBlock(block, collection);
                }
                else
                {
                    collection.AddObjectGroup(CreateContextBlock(block, false));
                }
            }
        }
        /// <summary>
        /// Collects all unit seat mappings from a cache file and adds them to a context collection.
        /// </summary>
        /// <param name="reader">The stream to read from.</param>
        /// <param name="cache">The cache file.</param>
        /// <param name="buildInfo">The build information for this cache file.</param>
        /// <param name="block">The cache file's unit seat mappings block.</param>
        /// <param name="collection">The context collection.</param>
        private static void HandleUnitSeatMappings(IReader reader, ICacheFile cache, EngineDescription buildInfo,
                                                   KeyValuePair <string, StructureValueCollection[]> block, ScriptingContextCollection collection)
        {
            MappingInformation[] information = new MappingInformation[block.Value.Length];

            // Collect information for all unti seat mappings in scnr.
            for (short mappingIndex = 0; mappingIndex < block.Value.Length; mappingIndex++)
            {
                var        values       = block.Value[mappingIndex];
                ITag       unit         = values.GetTagReference("Unit");
                int        seatIndeces1 = (int)values.GetInteger("Seats 1");
                int        seatIndeces2 = (int)values.GetInteger("Seats 2");
                List <int> seatIndices  = GetSeatIndices(seatIndeces1, seatIndeces2);
                var        vehiLayout   = buildInfo.Layouts.GetLayout("vehi_seats");

                // Read the vehicle meta data from the cache file.
                reader.SeekTo(unit.MetaLocation.AsOffset());
                var vehiValues = CacheStructureReader.ReadStructure(reader, cache, buildInfo, vehiLayout);
                var seatsBlock = vehiValues.GetTagBlock("seats");

                // Guess the name of each mapping entry in scnr.
                string name = GetVehicleMappingName(seatsBlock, seatIndices);

                // Add the information to the array.
                information[mappingIndex] = new MappingInformation
                {
                    Index       = mappingIndex,
                    Name        = name,
                    SplitName   = name.Split('_'),
                    TagName     = cache.FileNames.GetTagName(unit),
                    SeatIndices = seatIndices
                };
            }

            // Identify and create seat mappings. Mappings will be grouped in this step.
            IEnumerable <UnitSeatMapping> mappings;

            if (buildInfo.Name.Contains("Halo 3"))
            {
                mappings = CreateUnitSeatMappings(information, PostProcessing.Halo3);
            }
            else
            {
                mappings = CreateUnitSeatMappings(information, PostProcessing.HaloReach);
            }

            // Add the final unit seat mapping objects to the result.
            foreach (var mapping in mappings)
            {
                collection.AddUnitSeatMapping(mapping);
            }
        }