﻿using System;
using System.Text;
using System.IO;

namespace FSO.Files.Utils
{

    /// <summary>
    /// IOBuffer is a very basic wrapper over System.BinaryReader that inherits from IDisposable.
    /// </summary>
    public class IoWriter : IDisposable, BCFWriteProxy
    {
        private Stream Stream;
        private BinaryWriter Writer;
        private bool FloatSwap = false;
        public ByteOrder ByteOrder = ByteOrder.BIG_ENDIAN;

        /// <summary>
        /// Creates a new IOBuffer instance from a stream.
        /// </summary>
        /// <param name="stream"></param>
        public IoWriter(Stream stream)
        {
            this.Stream = stream;
            this.Writer = new BinaryWriter(stream);
        }

        /// <summary>
        /// More to read in this stream?
        /// </summary>
        public bool HasMore
        {
            get
            {
                return Stream.Position < Stream.Length - 1;
            }
        }

        /// <summary>
        /// Skips a number of bytes in the current stream, starting from the current position.
        /// </summary>
        /// <param name="numBytes">Number of bytes to skip.</param>
        public void Skip(long numBytes)
        {
            Writer.BaseStream.Seek(numBytes, SeekOrigin.Current);
        }

        /// <summary>
        /// Seeks in the current stream.
        /// </summary>
        /// <param name="origin">Where to start from.</param>
        /// <param name="offset">The offset to seek to.</param>
        public void Seek(SeekOrigin origin, long offset)
        {
            Writer.BaseStream.Seek(offset, origin);
        }

        /// <summary>
        /// Writes a variable length unsigned integer to the current stream
        /// </summary>
        /// <param name="value">Value to write.</param>
        public void WriteVarLen(uint value)
        {
            bool first = true;
            while (value > 0 || first)
            {
                WriteByte((byte)(((value > 127)?(uint)128:0) | (value & 127)));
                value >>= 7;
                first = false;
            }
        }

        /// <summary>
        /// Writes an unsigned 16bit integer to the current stream. 
        /// </summary>
        /// <returns>A ushort.</returns>
        public void WriteUInt16(ushort value)
        {
            if (ByteOrder == ByteOrder.BIG_ENDIAN)
            {
                value = Endian.SwapUInt16(value);
            }
            Writer.Write(value);
        }

        /// <summary>
        /// Writes a 16bit integer to the current stream. 
        /// </summary>
        /// <returns>A short.</returns>
        public void WriteInt16(short value)
        {
            if (ByteOrder == ByteOrder.BIG_ENDIAN)
            {
                value = Endian.SwapInt16(value);
            }
            Writer.Write(value);
        }

        /// <summary>
        /// Writes a 32bit integer to the current stream. 
        /// </summary>
        /// <returns>An int.</returns>
        public void WriteInt32(int value)
        {
            if (ByteOrder == ByteOrder.BIG_ENDIAN)
            {
                value = Endian.SwapInt32(value);
            }
            Writer.Write(value);
        }

        /// <summary>
        /// Writes a 32bit integer to the current stream. 
        /// </summary>
        /// <returns>An int.</returns>
        public void WriteInt64(long value)
        {
            if (ByteOrder == ByteOrder.BIG_ENDIAN)
            {
                value = Endian.SwapInt64(value);
            }
            Writer.Write(value);
        }


        /// <summary>
        /// Writes an unsigned 32bit integer from to current stream. 
        /// </summary>
        /// <returns>A uint.</returns>
        public void WriteUInt32(uint value)
        {
            if (ByteOrder == ByteOrder.BIG_ENDIAN)
            {
                value = Endian.SwapUInt32(value);
            }
            Writer.Write(value);
        }

        /// <summary>
        /// Writes a number of ASCII characters to the current stream.
        /// </summary>
        /// <param name="value">The string to write.</param>
        /// <param name="num">The number of bytes to write into.</param>
        public void WriteCString(string value, int num)
        {
            value = value.PadRight(num, '\0');
            Writer.Write(ASCIIEncoding.ASCII.GetBytes(value));
        }

        public void WriteCString(string value)
        {
            WriteCString(value, value.Length + 1);
        }


        /// <summary>
        /// Writes a byte to the current stream.
        /// </summary>
        public void WriteByte(byte value)
        {
            Writer.Write(value);
        }

        /// <summary>
        /// Writes a number of bytes to the current stream.
        /// </summary>
        /// <param name="bytes">Bytes to write out.</param>
        public void WriteBytes(byte[] bytes)
        {
            Writer.Write(bytes);
        }

        /// <summary>
        /// Writes a pascal string to the current stream, which is prefixed by a 16bit short.
        /// </summary>
        public void WriteLongPascalString(string value)
        {
            WriteInt16((short)value.Length);
            WriteBytes(Encoding.ASCII.GetBytes(value));
        }

        /// <summary>
        /// Writes a C string to the current stream.
        /// </summary>
        public void WriteNullTerminatedString(string value)
        {
            if (value != null) WriteBytes(Encoding.ASCII.GetBytes(value));
            WriteByte(0);
        }

        /// <summary>
        /// Writes a pascal string to the current stream.
        /// </summary>
        public void WriteVariableLengthPascalString(string value)
        {
            Writer.Write((value == null)?"":value);
        }

        /// <summary>
        /// Writes a pascal string to the current stream, prefixed by a byte.
        /// </summary>
        public void WritePascalString(string value)
        {
            WriteByte((byte)value.Length);
            WriteBytes(Encoding.ASCII.GetBytes(value));
        }

        /// <summary>
        /// Writes a float to the current stream.
        /// </summary>
        public void WriteFloat(float value)
        {
            var bytes = BitConverter.GetBytes(value);

            if (ByteOrder == ByteOrder.BIG_ENDIAN && FloatSwap)
            {
                Array.Reverse(bytes);
            }

            Writer.Write(bytes);
        }

        #region IDisposable Members

        public void Dispose()
        {
        }

        #endregion

        /// <summary>
        /// Creates a new IoWriter instance from a stream.
        /// </summary>
        /// <param name="stream">A stream.</param>
        /// <returns>A new IoWriter instance.</returns>
        public static IoWriter FromStream(Stream stream)
        {
            return new IoWriter(stream);
        }

        /// <summary>
        /// Creates a new IoWriter instance from a stream, using a specified byte order.
        /// </summary>
        /// <param name="stream">A stream.</param>
        /// <param name="order">Byte order to use.</param>
        /// <returns>A new IoWriter instance.</returns>
        public static IoWriter FromStream(Stream stream, ByteOrder order)
        {
            var item = FromStream(stream);
            item.ByteOrder = order;
            return item;
        }

        /// <summary>
        /// Creates a new IOBuffer instance from a byte array.
        /// </summary>
        /// <param name="bytes">The byte array to use.</param>
        /// <returns>A new IOBuffer instance.</returns>
        public static IoWriter FromBytes(byte[] bytes)
        {
            return FromStream(new MemoryStream(bytes));
        }

        /// <summary>
        /// Creates a new IOBuffer instance from a byte array, using a specified byte order.
        /// </summary>
        /// <param name="bytes">The byte array to use.</param>
        /// <param name="order">Byte order to use.</param>
        /// <returns>A new IOBuffer instance.</returns>
        public static IoWriter FromBytes(byte[] bytes, ByteOrder order)
        {
            return FromStream(new MemoryStream(bytes), order);
        }
        
        /// <summary>
        /// Used by BCFWriteProxy's string mode, but does not do anything here.
        /// </summary>
        /// <param name="groupSize">The size of value groups</param>
        public void SetGrouping(int groupSize)
        {

        }
    }
}
