using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Collections;
using System.Diagnostics;

using System.Collections.Generic;
using System.Text;

namespace EA.FileTalk
{
	/// <summary>
	/// Summary description for PlatformBinaryWriter.
	/// </summary>
	public sealed class PlatformBinaryWriter
	{
		private bool mBigEndian = false;
		private BinaryWriter mWriter;

		/// <summary>
		/// Construct a PlatformBinaryWriter for a specific platform and stream to write to.
		/// </summary>
		/// <param name="stream">Stream to write to.</param>
		public PlatformBinaryWriter(bool bigEndian, Stream stream)
		{
			mBigEndian = bigEndian;
			mWriter = new BinaryWriter(stream, System.Text.Encoding.ASCII);
		}

		/// <summary>
		/// Closes the PlatformBinaryWriter and the underlying stream.
		/// </summary>
		public void Close()
		{
			mWriter.Close();
		}

		/// <summary>
		/// Clears all buffers for the current writer and causes any buffered data to be written to the underlying device.
		/// </summary>
		public void Flush()
		{
			mWriter.Flush();
		}

		/// <summary>
		/// Gets the position within the current stream.
		/// </summary>
		public long Position
		{
			get { return (mWriter.BaseStream.Position); }
		}

		/// <summary>
		/// Sets the position within the current stream.
		/// </summary>
		/// <param name="offset">A byte offset relative to origin. </param>
		/// <param name="origin">A field of SeekOrigin indicating the reference point from which the new position is to be obtained.</param>
		/// <returns>The position with the current stream.</returns>
		public long Seek(int offset, SeekOrigin origin)
		{
			return mWriter.Seek(offset, origin);
		}

		/// <summary>
		/// Pads the output with the specified number of zero bytes.
		/// </summary>
		/// <param name="pad">Number of bytes to write</param>
		public void Pad(long pad)
		{
			while (pad-- > 0)
				mWriter.Write((byte)0);
		}

		/// <summary>
		/// Pads the output stream with zeroes until the current stream position is
		/// at the specified byte alignment.
		/// </summary>
		/// <param name="alignment">Byte alignment.  Will typically be 2, 4, 8, or 16.</param>
		public void Align(int alignment)
		{
			if (alignment > 0)
			{
				long offset = this.Position % alignment;
				if (offset > 0)
					this.Pad(alignment - offset);
			}
		}

		/// <summary>
		/// Writes a byte array to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(byte[] value)
		{
			mWriter.Write(value);
		}

		/// <summary>
		/// Writes a byte to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(byte value)
		{
			mWriter.Write(value);
		}

		/// <summary>
		/// Writes an sbyte to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(sbyte value)
		{
			mWriter.Write(value);
		}

		/// <summary>
		/// Writes a short to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(short value)
		{
			this.Align(2);
			if (mBigEndian)
			{
				mWriter.Write((byte)(value >> 8));
				mWriter.Write((byte)(value >> 0));
			}
			else
				mWriter.Write(value);
		}

		/// <summary>
		/// Writes a ushort to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(ushort value)
		{
			this.Align(2);
			if (mBigEndian)
			{
				mWriter.Write((byte)(value >> 8));
				mWriter.Write((byte)(value >> 0));
			}
			else
				mWriter.Write(value);
		}

		/// <summary>
		/// Writes an int to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(int value)
		{
			this.Align(4);
			if (mBigEndian)
			{
				mWriter.Write((byte)(value >> 24));
				mWriter.Write((byte)(value >> 16));
				mWriter.Write((byte)(value >> 8));
				mWriter.Write((byte)(value >> 0));
			}
			else
				mWriter.Write(value);
		}

		/// <summary>
		/// Writes a uint to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(uint value)
		{
			this.Align(4);
			if (mBigEndian)
			{
				mWriter.Write((byte)(value >> 24));
				mWriter.Write((byte)(value >> 16));
				mWriter.Write((byte)(value >> 8));
				mWriter.Write((byte)(value >> 0));
			}
			else
				mWriter.Write(value);
		}

		/// <summary>
		/// Writes a long to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(long value)
		{
			this.Align(8);
			if (mBigEndian)
			{
				mWriter.Write((byte)(value >> 56));
				mWriter.Write((byte)(value >> 48));
				mWriter.Write((byte)(value >> 40));
				mWriter.Write((byte)(value >> 32));
				mWriter.Write((byte)(value >> 24));
				mWriter.Write((byte)(value >> 16));
				mWriter.Write((byte)(value >> 8));
				mWriter.Write((byte)(value >> 0));
			}
			else
				mWriter.Write(value);
		}

		/// <summary>
		/// Writes a ulong to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(ulong value)
		{
			this.Align(8);
			if (mBigEndian)
			{
				mWriter.Write((byte)(value >> 56));
				mWriter.Write((byte)(value >> 48));
				mWriter.Write((byte)(value >> 40));
				mWriter.Write((byte)(value >> 32));
				mWriter.Write((byte)(value >> 24));
				mWriter.Write((byte)(value >> 16));
				mWriter.Write((byte)(value >> 8));
				mWriter.Write((byte)(value >> 0));
			}
			else
				mWriter.Write(value);
		}

		/// <summary>
		/// Writes a char to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(char value)
		{
			mWriter.Write(Convert.ToByte(value));
		}

		/// <summary>
		/// Writes a null-terminated string to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(string value)
		{
			char[] chars = value.ToCharArray();
			foreach (char c in chars)
				mWriter.Write(c);
			mWriter.Write((byte)0);
		}

		/// <summary>
		/// Writes a null-terminated string to the underlying stream, padded to the
		/// specified maximum string length.
		/// </summary>
		/// <param name="value">Value to write.</param>
		/// <param name="fixedLength">Fixed number of characters to write.  If the string length is
		/// greater than fixedLength, it will be truncated in the output.  If the string 
		/// length is less than fixedLength, then the output will be padded with 0's.</param>
		public void Write(string value, int fixedLength)
		{
			char[] chars = value.ToCharArray();
			int i;
			for (i = 0; i < fixedLength; i++)
			{
				if (i < fixedLength - 1)
					mWriter.Write(chars[i]);
				else
					mWriter.Write((byte)0);
			}
		}

		/// <summary>
		/// Writes a float to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(float value)
		{
			this.Align(4);
			if (mBigEndian)
			{
				byte[] bytes = BitConverter.GetBytes(value);
				mWriter.Write(bytes[3]);
				mWriter.Write(bytes[2]);
				mWriter.Write(bytes[1]);
				mWriter.Write(bytes[0]);
			}
			else
				mWriter.Write(value);
		}

		/// <summary>
		/// Writes a double to the underlying stream.
		/// </summary>
		/// <param name="value">Value to write.</param>
		public void Write(double value)
		{
			this.Align(8);
			if (mBigEndian)
			{
				byte[] bytes = BitConverter.GetBytes(value);
				mWriter.Write(bytes[7]);
				mWriter.Write(bytes[6]);
				mWriter.Write(bytes[5]);
				mWriter.Write(bytes[4]);
				mWriter.Write(bytes[3]);
				mWriter.Write(bytes[2]);
				mWriter.Write(bytes[1]);
				mWriter.Write(bytes[0]);
			}
			else
				mWriter.Write(value);
		}

		public void Write(bool value)
		{
			mWriter.Write(value);
		}
	}
}