/*
 * Copyright (c) 2002 Sergey Chaban <serge@wildwestsoftware.com>
 */

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

namespace Mono.PEToolkit.Metadata {

	/// <summary>
	/// Metadata stream.
	/// </summary>
	public class MDStream {

		/// <summary>
		/// MetaData stream header as described
		/// in ECMA CLI specs, Partition II Metadata, 23.1.2
		/// </summary>
		protected class Header {
			internal uint offs;
			internal uint size;
			internal string name;


			public void Read(BinaryReader reader, MDStream stream)
			{	
				offs = reader.ReadUInt32 ();
				size = reader.ReadUInt32 ();

				StringBuilder name_builder = new StringBuilder ();
				while (true) {
					sbyte c = reader.ReadSByte();
					if (c == 0) 
						break;
					name_builder.Append ((char) c);
				}

				name = name_builder.ToString ();
				if (name.Length == 0)
					throw new BadImageException("Invalid stream name.");

				// Round up to dword boundary.
				long pos = reader.BaseStream.Position;
				if (stream != null) 
					pos -= stream.Root.filePos;
				pos += 3;
				pos &= ~3;
				if (stream != null) 
					pos += stream.Root.filePos;
				
				// Advance file pointer.
				reader.BaseStream.Position = pos;
			}
			
			public void Write (BinaryWriter writer, MDStream stream)
			{
				writer.Write (offs);
				writer.Write (size);

				for (int i=0; i<name.Length; i++)
					writer.Write ((sbyte)name[i]);
				writer.Write ((sbyte) '\0');	
			
				// Round up to dword boundary.
				long pos = writer.BaseStream.Position;
				if (stream != null) 
					pos -= stream.Root.filePos;
				pos += 3;
				pos &= ~3;
				if (stream != null) 
					pos += stream.Root.filePos;

				// Advance file pointer.
				writer.BaseStream.Position = pos;
			}

		} // header



		private MetaDataRoot root;
		private MDHeap heap;
		private Header hdr;
		private byte [] data;


		public MDStream(MetaDataRoot root)
		{
			this.root = root;
			hdr = new Header();
			data = null;
			heap = null;
		}


		public uint Offset {
			get {
				return hdr.offs;
			}
			set {
				hdr.offs = value;
			}
		}

		public uint Size {
			get {
				return hdr.size;
			}
			set {
				hdr.size = value;
			}
		}

		/// <summary>
		/// Name of the stream.
		/// </summary>
		/// <remarks>
		/// Stored on-disk as a null-terminated ASCII string,
		/// rounded up to 4-byte boundary.
		/// </remarks>
		public string Name {
			get {
				return hdr.name;
			}
			set {
				hdr.name = value;
			}
		}

		public byte [] RawData {
			get {
				return data;
			}
		}

		public MetaDataRoot Root {
			get {
				return root;
			}
		}

		public MDHeap Heap {
			get {
				lock (this) {
					if (heap == null) InitHeap();
					return heap;
				}
			}
		}


		/// <summary>
		/// Reads stream header and body from supplied BinaryReader.
		/// </summary>
		/// <remarks>
		/// Reader must be positioned at the first byte of metadata stream.
		/// </remarks>
		/// <param name="reader"></param>
		unsafe public void Read(BinaryReader reader)
		{
			hdr.Read(reader, this);
			long oldPos = reader.BaseStream.Position;

			// Offset field in the stream header is relataive to
			// the start of metadata.
			reader.BaseStream.Position = root.filePos + hdr.offs;
			data = reader.ReadBytes((int) hdr.size);

			// set reader's position to the first byte after
			// stream header.
			reader.BaseStream.Position = oldPos;
		}


		public void Write (BinaryWriter writer)
		{
			hdr.Write (writer, this);
			long old_pos = writer.BaseStream.Position;
			writer.BaseStream.Position = root.filePos + hdr.offs;
			writer.Write (data);
			writer.BaseStream.Position = old_pos;
		}

		/// <summary>
		/// Initializes heap for this stream.
		/// </summary>
		protected void InitHeap()
		{
			heap = MDHeap.Create(this);
		}


		/// <summary>
		/// </summary>
		public void Dump(TextWriter writer) {
			string dump = String.Format(
				"Name        : {0}" + Environment.NewLine +
				"Offset      : 0x{1:x8}" + Environment.NewLine +
				"Size        : 0x{2:x8}" + Environment.NewLine,
				hdr.name, hdr.offs, hdr.size
			);
			writer.WriteLine(dump);
		}


		/// <summary>
		/// </summary>
		public override string ToString()
		{
			StringWriter sw = new StringWriter ();
			Dump(sw);
			return sw.ToString();
		}

	}
}
