<commit_before>﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using static DspAdpcm.Lib.Helpers;

namespace DspAdpcm.Lib.Adpcm.Formats
{
    /// <summary>
    /// Represents a BCSTM file.
    /// </summary>
    public class Bcstm
    {
        /// <summary>
        /// The underlying <see cref="AdpcmStream"/> used to build the BCSTM file.
        /// </summary>
        public AdpcmStream AudioStream { get; set; }

        /// <summary>
        /// Contains various settings used when building the BCSTM file.
        /// </summary>
        public BcstmConfiguration Configuration { get; } = new BcstmConfiguration();

        private int NumSamples => AudioStream.Looping && Configuration.TrimFile ? LoopEnd : AudioStream.NumSamples;
        private int NumSamplesUntrimmed => AudioStream.Channels?[0]?.NumSamplesUntrimmed ?? 0;
        private int NumChannels => AudioStream.Channels.Count;
        private int NumTracks => AudioStream.Tracks.Count;

        private int AlignmentSamples => GetNextMultiple(AudioStream.LoopStart, Configuration.LoopPointAlignment) - AudioStream.LoopStart;
        private int LoopStart => AudioStream.LoopStart + AlignmentSamples;
        private int LoopEnd => AudioStream.LoopEnd + AlignmentSamples;

        private BcstmCodec Codec { get; } = BcstmCodec.Adpcm;
        private byte Looping => (byte)(AudioStream.Looping ? 1 : 0);
        private int InterleaveSize => GetBytesForAdpcmSamples(SamplesPerInterleave);
        private int SamplesPerInterleave => Configuration.SamplesPerInterleave;
        private int InterleaveCount => NumSamples.DivideByRoundUp(SamplesPerInterleave);
        private int LastBlockSamples => (LoopEnd == 0 ? NumSamples : LoopEnd) - ((InterleaveCount - 1) * SamplesPerInterleave);

        private int AudioDataSize => Configuration.TrimFile
            ? GetBytesForAdpcmSamples(NumSamples)
            : (AudioStream.Channels[0]?.GetAudioData.Length ?? 0);
        private int LastBlockSizeWithoutPadding => GetBytesForAdpcmSamples(NumSamples - ((InterleaveCount - 1) * SamplesPerInterleave));
        private int LastBlockSize => Math.Min(GetNextMultiple(AudioDataSize - ((InterleaveCount - 1) * InterleaveSize), 0x20), InterleaveSize);

        private int SamplesPerSeekTableEntry => Configuration.SamplesPerSeekTableEntry;
        private int NumSeekTableEntries => (NumSamples + 1).DivideByRoundUp(SamplesPerSeekTableEntry);
        private int BytesPerSeekTableEntry => 4;

        private int CstmHeaderLength => 0x40;

        private int HeadChunkOffset => CstmHeaderLength;

        private int InfoChunkOffset => CstmHeaderLength;
        private int InfoChunkLength => GetNextMultiple(InfoChunkHeaderLength + InfoChunkTableLength +
            InfoChunk1Length + InfoChunk2Length + InfoChunk3Length, 0x20);
        private int InfoChunkHeaderLength => 8;
        private int InfoChunkTableLength => 8 * 3;
        private int InfoChunk1Length => 0x38 + (!Configuration.InfoPart1Extra ? 0 : 0xc);
        private int InfoChunk2Length => Configuration.IncludeTrackInformation ? 4 + 8 * NumTracks : 0;
        private int InfoChunk3Length => (4 + 8 * NumChannels) +
            (Configuration.IncludeTrackInformation ? 0x14 * NumTracks : 0) +
            8 * NumChannels +
            ChannelInfoLength * NumChannels;

        private int ChannelInfoLength => 0x2e;

        private int SeekChunkOffset => CstmHeaderLength + InfoChunkLength;
        private int SeekChunkLength => GetNextMultiple(8 + NumSeekTableEntries * NumChannels * BytesPerSeekTableEntry, 0x20);

        private int SamplesToWrite => Configuration.TrimFile ? NumSamples : NumSamplesUntrimmed;
        private int DataChunkOffset => CstmHeaderLength + InfoChunkLength + SeekChunkLength;
        private int DataChunkLength => 0x20 + GetNextMultiple(GetBytesForAdpcmSamples(SamplesToWrite), 0x20) * NumChannels;

        private int? _version;
        private int Version
        {
            get
            {
                return _version ?? VersionDictionary[new Tuple<bool, bool>(Configuration.IncludeTrackInformation, Configuration.InfoPart1Extra)];
            }
            set { _version = value; }
        }
        //All BCSTM files I've seen follow this format except for Kingdom Hearts 3D
        private Dictionary<Tuple<bool, bool>, int> VersionDictionary {get;} = new Dictionary<Tuple<bool, bool>, int>()
        {
            [new Tuple<bool, bool>(true, false)] = 0x200,
            [new Tuple<bool, bool>(true, true)] = 0x201,
            [new Tuple<bool, bool>(false, true)] = 0x202,
            [new Tuple<bool, bool>(false, false)] = 0x200 //Unused in any BCSTM I've seen. Here for completion
        };

        /// <summary>
        /// The size in bytes of the BCSTM file.
        /// </summary>
        public int FileLength => CstmHeaderLength + InfoChunkLength + SeekChunkLength + DataChunkLength;

        /// <summary>
        /// Initializes a new <see cref="Bcstm"/> from an <see cref="AdpcmStream"/>.
        /// </summary>
        /// <param name="stream">The <see cref="AdpcmStream"/> used to
        /// create the <see cref="Bcstm"/>.</param>
        public Bcstm(AdpcmStream stream)
        {
            if (stream.Channels.Count < 1)
            {
                throw new InvalidDataException("Stream must have at least one channel ");
            }

            AudioStream = stream;
        }

        /// <summary>
        /// Initializes a new <see cref="Bcstm"/> by parsing an existing
        /// BCSTM file.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing 
        /// the BCSTM file. Must be seekable.</param>
        public Bcstm(Stream stream)
        {
            if (!stream.CanSeek)
            {
                throw new NotSupportedException("A seekable stream is required");
            }

            ReadBcstmFile(stream);
        }

        private Bcstm() { }

        /// <summary>
        /// Parses the header of a BCSTM file and returns the metadata
        /// and structure data of that file.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing 
        /// the BCSTM file. Must be seekable.</param>
        /// <returns>A <see cref="BcstmStructure"/> containing
        /// the data from the BCSTM header.</returns>
        public static BcstmStructure ReadMetadata(Stream stream)
        {
            if (!stream.CanSeek)
            {
                throw new NotSupportedException("A seekable stream is required");
            }

            var bcstm = new Bcstm();
            return bcstm.ReadBcstmFile(stream, false);
        }
        /// <summary><commit_msg>Add constructor to initialize a Bcstm with a BcstmConfiguration<commit_after>﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using static DspAdpcm.Lib.Helpers;

namespace DspAdpcm.Lib.Adpcm.Formats
{
    /// <summary>
    /// Represents a BCSTM file.
    /// </summary>
    public class Bcstm
    {
        /// <summary>
        /// The underlying <see cref="AdpcmStream"/> used to build the BCSTM file.
        /// </summary>
        public AdpcmStream AudioStream { get; set; }

        /// <summary>
        /// Contains various settings used when building the BCSTM file.
        /// </summary>
        public BcstmConfiguration Configuration { get; } = new BcstmConfiguration();

        private int NumSamples => AudioStream.Looping && Configuration.TrimFile ? LoopEnd : AudioStream.NumSamples;
        private int NumSamplesUntrimmed => AudioStream.Channels?[0]?.NumSamplesUntrimmed ?? 0;
        private int NumChannels => AudioStream.Channels.Count;
        private int NumTracks => AudioStream.Tracks.Count;

        private int AlignmentSamples => GetNextMultiple(AudioStream.LoopStart, Configuration.LoopPointAlignment) - AudioStream.LoopStart;
        private int LoopStart => AudioStream.LoopStart + AlignmentSamples;
        private int LoopEnd => AudioStream.LoopEnd + AlignmentSamples;

        private BcstmCodec Codec { get; } = BcstmCodec.Adpcm;
        private byte Looping => (byte)(AudioStream.Looping ? 1 : 0);
        private int InterleaveSize => GetBytesForAdpcmSamples(SamplesPerInterleave);
        private int SamplesPerInterleave => Configuration.SamplesPerInterleave;
        private int InterleaveCount => NumSamples.DivideByRoundUp(SamplesPerInterleave);
        private int LastBlockSamples => (LoopEnd == 0 ? NumSamples : LoopEnd) - ((InterleaveCount - 1) * SamplesPerInterleave);

        private int AudioDataSize => Configuration.TrimFile
            ? GetBytesForAdpcmSamples(NumSamples)
            : (AudioStream.Channels[0]?.GetAudioData.Length ?? 0);
        private int LastBlockSizeWithoutPadding => GetBytesForAdpcmSamples(NumSamples - ((InterleaveCount - 1) * SamplesPerInterleave));
        private int LastBlockSize => Math.Min(GetNextMultiple(AudioDataSize - ((InterleaveCount - 1) * InterleaveSize), 0x20), InterleaveSize);

        private int SamplesPerSeekTableEntry => Configuration.SamplesPerSeekTableEntry;
        private int NumSeekTableEntries => (NumSamples + 1).DivideByRoundUp(SamplesPerSeekTableEntry);
        private int BytesPerSeekTableEntry => 4;

        private int CstmHeaderLength => 0x40;

        private int HeadChunkOffset => CstmHeaderLength;

        private int InfoChunkOffset => CstmHeaderLength;
        private int InfoChunkLength => GetNextMultiple(InfoChunkHeaderLength + InfoChunkTableLength +
            InfoChunk1Length + InfoChunk2Length + InfoChunk3Length, 0x20);
        private int InfoChunkHeaderLength => 8;
        private int InfoChunkTableLength => 8 * 3;
        private int InfoChunk1Length => 0x38 + (!Configuration.InfoPart1Extra ? 0 : 0xc);
        private int InfoChunk2Length => Configuration.IncludeTrackInformation ? 4 + 8 * NumTracks : 0;
        private int InfoChunk3Length => (4 + 8 * NumChannels) +
            (Configuration.IncludeTrackInformation ? 0x14 * NumTracks : 0) +
            8 * NumChannels +
            ChannelInfoLength * NumChannels;

        private int ChannelInfoLength => 0x2e;

        private int SeekChunkOffset => CstmHeaderLength + InfoChunkLength;
        private int SeekChunkLength => GetNextMultiple(8 + NumSeekTableEntries * NumChannels * BytesPerSeekTableEntry, 0x20);

        private int SamplesToWrite => Configuration.TrimFile ? NumSamples : NumSamplesUntrimmed;
        private int DataChunkOffset => CstmHeaderLength + InfoChunkLength + SeekChunkLength;
        private int DataChunkLength => 0x20 + GetNextMultiple(GetBytesForAdpcmSamples(SamplesToWrite), 0x20) * NumChannels;

        private int? _version;
        private int Version
        {
            get
            {
                return _version ?? VersionDictionary[new Tuple<bool, bool>(Configuration.IncludeTrackInformation, Configuration.InfoPart1Extra)];
            }
            set { _version = value; }
        }
        //All BCSTM files I've seen follow this format except for Kingdom Hearts 3D
        private Dictionary<Tuple<bool, bool>, int> VersionDictionary {get;} = new Dictionary<Tuple<bool, bool>, int>()
        {
            [new Tuple<bool, bool>(true, false)] = 0x200,
            [new Tuple<bool, bool>(true, true)] = 0x201,
            [new Tuple<bool, bool>(false, true)] = 0x202,
            [new Tuple<bool, bool>(false, false)] = 0x200 //Unused in any BCSTM I've seen. Here for completion
        };

        /// <summary>
        /// The size in bytes of the BCSTM file.
        /// </summary>
        public int FileLength => CstmHeaderLength + InfoChunkLength + SeekChunkLength + DataChunkLength;

        /// <summary>
        /// Initializes a new <see cref="Bcstm"/> from an <see cref="AdpcmStream"/>.
        /// </summary>
        /// <param name="stream">The <see cref="AdpcmStream"/> used to
        /// create the <see cref="Bcstm"/>.</param>
        public Bcstm(AdpcmStream stream)
        {
            if (stream.Channels.Count < 1)
            {
                throw new InvalidDataException("Stream must have at least one channel ");
            }

            AudioStream = stream;
        }

        /// <summary>
        /// Initializes a new <see cref="Bcstm"/> by parsing an existing
        /// BCSTM file.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing 
        /// the BCSTM file. Must be seekable.</param>
        public Bcstm(Stream stream)
        {
            if (!stream.CanSeek)
            {
                throw new NotSupportedException("A seekable stream is required");
            }

            ReadBcstmFile(stream);
        }

        /// <summary>
        /// Initializes a new <see cref="Bcstm"/> from an <see cref="AdpcmStream"/>.
        /// </summary>
        /// <param name="stream">The <see cref="AdpcmStream"/> used to
        /// create the <see cref="Bcstm"/>.</param>
        /// <param name="configuration">A <see cref="BcstmConfiguration"/>
        /// to use for the <see cref="Bcstm"/></param>
        public Bcstm(AdpcmStream stream, BcstmConfiguration configuration) : this(stream) 
        {
            Configuration = configuration;
        }

        /// <summary>
        /// Initializes a new <see cref="Bcstm"/> by parsing an existing
        /// BRSTM file.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing 
        /// the BCSTM file. Must be seekable.</param>
        /// <param name="configuration">A <see cref="BcstmConfiguration"/>
        /// to use for the <see cref="Bcstm"/></param>
        public Bcstm(Stream stream, BcstmConfiguration configuration) : this(stream)
        {
            Configuration = configuration;
        }

        private Bcstm() { }

        /// <summary>
        /// Parses the header of a BCSTM file and returns the metadata
        /// and structure data of that file.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing 
        /// the BCSTM file. Must be seekable.</param>
        /// <returns>A <see cref="BcstmStructure"/> containing
        /// the data from the BCSTM header.</returns>
        public static BcstmStructure ReadMetadata(Stream stream)
        {
            if (!stream.CanSeek)
            {
                throw new NotSupportedException("A seekable stream is required");
            }

            var bcstm = new Bcstm();
            return bcstm.ReadBcstmFile(stream, false);
        }
        /// <summary>
{'fraction_non_alphanumeric': 0.10512627472452261, 'fraction_numerical': 0.010540004106495107, 'mean_word_length': 2.4324641766502233, 'pattern_counts': {'":': 0, '<': 109, '<?xml version=': 0, '>': 175, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1550976', 'n_tokens_mistral': 4089, 'n_tokens_neox': 3849, 'n_words': 1052}