using System;
using System.IO;
using System.Net;
using System.Text;
using NAudio.Utils;

namespace NAudio.SoundFont
{
    internal class RiffChunk
    {
        private string chunkID;
        private uint chunkSize;
        private long dataOffset; // data offset in the file
        private BinaryReader riffFile;

        public static RiffChunk GetTopLevelChunk(BinaryReader file)
        {
            RiffChunk r = new RiffChunk(file);
            r.ReadChunk();
            return r;
        }

        private RiffChunk(BinaryReader file)
        {
            riffFile = file;
            chunkID = "????";
            chunkSize = 0;
            dataOffset = 0;
        }

        /// <summary>
        /// just reads a chunk ID at the current position
        /// </summary>
        /// <returns>chunk ID</returns>
        public string ReadChunkID()
        {
            byte[] cid = riffFile.ReadBytes(4);
            if (cid.Length != 4)
            {
                throw new InvalidDataException("Couldn't read Chunk ID");
            }
            return ByteEncoding.Instance.GetString(cid, 0, cid.Length);
        }

        /// <summary>
        /// reads a chunk at the current position
        /// </summary>
        private void ReadChunk()
        {
            this.chunkID = ReadChunkID();
            this.chunkSize = riffFile.ReadUInt32(); //(uint) IPAddress.NetworkToHostOrder(riffFile.ReadUInt32());
            this.dataOffset = riffFile.BaseStream.Position;
        }

        /// <summary>
        /// creates a new riffchunk from current position checking that we're not
        /// at the end of this chunk first
        /// </summary>
        /// <returns>the new chunk</returns>
        public RiffChunk GetNextSubChunk()
        {
            if (riffFile.BaseStream.Position + 8 < dataOffset + chunkSize)
            {
                RiffChunk chunk = new RiffChunk(riffFile);
                chunk.ReadChunk();
                return chunk;
            }
            //Console.WriteLine("DEBUG Failed to GetNextSubChunk because Position is {0}, dataOffset{1}, chunkSize {2}",riffFile.BaseStream.Position,dataOffset,chunkSize);
            return null;
        }

        public byte[] GetData()
        {
            riffFile.BaseStream.Position = dataOffset;
            byte[] data = riffFile.ReadBytes((int) chunkSize);
            if (data.Length != chunkSize)
            {
                throw new InvalidDataException(String.Format("Couldn't read chunk's data Chunk: {0}, read {1} bytes",
                    this, data.Length));
            }
            return data;
        }

        /// <summary>
        /// useful for chunks that just contain a string
        /// </summary>
        /// <returns>chunk as string</returns>
        public string GetDataAsString()
        {
            byte[] data = GetData();
            if (data == null)
                return null;
            return ByteEncoding.Instance.GetString(data, 0, data.Length);
        }

        public T GetDataAsStructure<T>(StructureBuilder<T> s)
        {
            riffFile.BaseStream.Position = dataOffset;
            if (s.Length != chunkSize)
            {
                throw new InvalidDataException(String.Format("Chunk size is: {0} so can't read structure of: {1}",
                    chunkSize, s.Length));
            }
            return s.Read(riffFile);
        }

        public T[] GetDataAsStructureArray<T>(StructureBuilder<T> s)
        {
            riffFile.BaseStream.Position = dataOffset;
            if (chunkSize % s.Length != 0)
            {
                throw new InvalidDataException(String.Format("Chunk size is: {0} not a multiple of structure size: {1}",
                    chunkSize, s.Length));
            }
            int structuresToRead = (int) (chunkSize / s.Length);
            T[] a = new T[structuresToRead];
            for (int n = 0; n < structuresToRead; n++)
            {
                a[n] = s.Read(riffFile);
            }
            return a;
        }

        public string ChunkID
        {
            get { return chunkID; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("ChunkID may not be null");
                }
                if (value.Length != 4)
                {
                    throw new ArgumentException("ChunkID must be four characters");
                }
                chunkID = value;
            }
        }

        public uint ChunkSize
        {
            get { return chunkSize; }
        }

        public long DataOffset
        {
            get { return dataOffset; }
        }

        public override string ToString()
        {
            return String.Format("RiffChunk ID: {0} Size: {1} Data Offset: {2}", ChunkID, ChunkSize, DataOffset);
        }
    }
}