#undef GENERICS
//#define GENERICS
//#if NET_2_0

#region "  © Copyright 2005-07 to Marcos Meli - http://www.marcosmeli.com.ar" 

// Errors, suggestions, contributions, send a mail to: marcos@filehelpers.com.

#endregion

using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text;
using System.Diagnostics;

namespace FileHelpers
{
	/// <summary>
	/// This class allow you to convert the records of a file to a different record format.
	/// </summary>
	/// <seealso href="quick_start.html">Quick Start Guide</seealso>
	/// <seealso href="class_diagram.html">Class Diagram</seealso>
	/// <seealso href="examples.html">Examples of Use</seealso>
	/// <seealso href="example_datalink.html">Example of the DataLink</seealso>
	/// <seealso href="attributes.html">Attributes List</seealso>
#if NET_2_0
    [DebuggerDisplay("FileTransformanEngine for types: {SourceType.Name} --> {DestinationType.Name}. Source Encoding: {SourceEncoding.EncodingName}. Destination Encoding: {DestinationEncoding.EncodingName}")]
#endif
#if ! GENERICS
	public sealed class FileTransformEngine
	{
#else
    /// <typeparam name="Source">The source record type.</typeparam>
    /// <typeparam name="Destination">The destination record type.</typeparam>
    public sealed class FileTransformEngine<Source, Destination>
	{
#endif

        #region "  Constructor  "

        /// <summary>Create a new instance of the class.</summary>
		/// <param name="sourceType">The source record Type.</param>
		/// <param name="destType">The destination record Type.</param>

#if ! GENERICS
		public FileTransformEngine(Type sourceType, Type destType)
		{
#else
		public FileTransformEngine()
		{
			Type sourceType = typeof(Source);
			Type destType = typeof(Destination);

#endif
			//throw new NotImplementedException("This feature is not ready yet. In the next release maybe work =)");
			ExHelper.CheckNullParam(sourceType, "sourceType");
			ExHelper.CheckNullParam(destType, "destType");
			ExHelper.CheckDifferentsParams(sourceType, "sourceType", destType, "destType");

			mSourceType = sourceType;
			mDestinationType = destType;

			ValidateRecordTypes();
		}

		#endregion

		#region "  Private Fields  "

#if NET_2_0
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
#endif
        private static object[] mEmptyArray = new object[] { };

#if NET_2_0
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
#endif
        private Type mSourceType;
#if NET_2_0
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
#endif
        private Type mDestinationType;

#if NET_2_0
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
#endif
        private Encoding mSourceEncoding = Encoding.Default;
#if NET_2_0
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
#endif
        private Encoding mDestinationEncoding = Encoding.Default;


		#endregion

		#region "  TransformFile  " 

		/// <summary>Transform the contents of the sourceFile and write them to the destFile.(use only if you need the array of the transformed records, TransformFileAsync is faster)</summary>
		/// <param name="sourceFile">The source file.</param>
		/// <param name="destFile">The destination file.</param>
		/// <returns>The transformed records.</returns>
#if ! GENERICS
		public object[] TransformFile(string sourceFile, string destFile)
#else
		public Destination[] TransformFile(string sourceFile, string destFile)
#endif
		{
			ExHelper.CheckNullParam(sourceFile, "sourceFile");
			ExHelper.CheckNullParam(destFile, "destFile");
			ExHelper.CheckDifferentsParams(sourceFile, "sourceFile", destFile, "destFile");

			if (mConvert1to2 == null)
				throw new BadUsageException("You must define a method in the class " + SourceType.Name + " with the attribute [TransfortToRecord(typeof(" + DestinationType.Name + "))] that return an object of type " + DestinationType.Name);

			return CoreTransformFile(sourceFile, destFile, mSourceType, mDestinationType, mConvert1to2);
		}


		/// <summary>Transform the contents of the sourceFile and write them to the destFile. (faster and use less memory, best choice for big files)</summary>
		/// <param name="sourceFile">The source file.</param>
		/// <param name="destFile">The destination file.</param>
		/// <returns>The number of transformed records.</returns>
		public int TransformFileAsync(string sourceFile, string destFile)
		{
			ExHelper.CheckNullParam(sourceFile, "sourceFile");
			ExHelper.CheckNullParam(destFile, "destFile");
			ExHelper.CheckDifferentsParams(sourceFile, "sourceFile", destFile, "destFile");

			if (mConvert1to2 == null)
				throw new BadUsageException("You must define a method in the class " + SourceType.Name + " with the attribute [TransfortToRecord(typeof(" + DestinationType.Name + "))] that return an object of type " + DestinationType.Name);

			return CoreTransformAsync(sourceFile, destFile, mSourceType, mDestinationType, mConvert1to2);
		}


		#endregion

//		public string TransformString(string sourceData)
//		{
//			if (mConvert1to2 == null)
//				throw new BadUsageException("You must define a method in the class " + SourceType.Name + " with the attribute [TransfortToRecord(typeof(" + DestinationType.Name + "))] that return an object of type " + DestinationType.Name);
//
//			return CoreTransformAsync(sourceFile, destFile, mSourceType, mDestinationType, mConvert1to2);
//		}


		/// <summary>Transform an array of records from the source type to the destination type</summary>
		/// <param name="sourceRecords">An array of the source records.</param>
		/// <returns>The transformed records.</returns>
#if ! GENERICS
		public object[] TransformRecords(object[] sourceRecords)
#else
		public Destination[] TransformRecords(Source[] sourceRecords)
#endif
		{
			if (mConvert1to2 == null)
				throw new BadUsageException("You must define a method in the class " + SourceType.Name + " with the attribute [TransfortToRecord(typeof(" + DestinationType.Name + "))] that return an object of type " + DestinationType.Name);

			return CoreTransformRecords(sourceRecords, mConvert1to2);
			//return CoreTransformAsync(sourceFile, destFile, mSourceType, mDestinationType, mConvert1to2);
		}

		/// <summary>Transform a file that contains source records to an array of the destination type</summary>
		/// <param name="sourceFile">A file containing the source records.</param>
		/// <returns>The transformed records.</returns>

#if ! GENERICS
		public object[] ReadAndTransformRecords(string sourceFile)
#else
		public Destination[] ReadAndTransformRecords(string sourceFile)
#endif
		{
			if (mConvert1to2 == null)
				throw new BadUsageException("You must define a method in the class " + SourceType.Name + " with the attribute [TransfortToRecord(typeof(" + DestinationType.Name + "))] that return an object of type " + DestinationType.Name);

			FileHelperAsyncEngine engine = new FileHelperAsyncEngine(mSourceType, mSourceEncoding);

			ArrayList res = new ArrayList();

			engine.BeginReadFile(sourceFile);
#if ! GENERICS
			foreach (object record in engine)
#else
			foreach (Source record in engine)
#endif
			{
				res.Add(CoreTransformOneRecord(record, mConvert1to2));
			}
			engine.Close();

#if ! GENERICS
			return (object[]) res.ToArray(mDestinationType);
#else
			return (Destination[]) res.ToArray(mDestinationType);
#endif
		}

		#region "  Transform Internal Methods  "


			
#if ! GENERICS
        private object[] CoreTransform(StreamReader sourceFile, StreamWriter destFile, Type sourceType, Type destType, MethodInfo method)
		{

            FileHelperEngine sourceEngine = new FileHelperEngine(sourceType, mSourceEncoding);
			FileHelperEngine destEngine = new FileHelperEngine(destType, mDestinationEncoding);

            object[] source = sourceEngine.ReadStream(sourceFile);
			object[] transformed = CoreTransformRecords(source, method);
#else
        private Destination[] CoreTransform(StreamReader sourceFile, StreamWriter destFile, Type sourceType, Type destType, MethodInfo method)
        {

            FileHelperEngine<Source> sourceEngine = new FileHelperEngine<Source>(mSourceEncoding);
            FileHelperEngine<Destination> destEngine = new FileHelperEngine<Destination>(mDestinationEncoding);

            Source[] source = sourceEngine.ReadStream(sourceFile);
			Destination[] transformed = CoreTransformRecords(source, method);
#endif 

			destEngine.WriteStream(destFile, transformed);

			return transformed;
		}

#if ! GENERICS
		private object[] CoreTransformRecords(object[] sourceRecords, MethodInfo method)
#else
		private Destination[] CoreTransformRecords(Source[] sourceRecords, MethodInfo method)
#endif 
		{
			ArrayList res = new ArrayList(sourceRecords.Length);
			
			for (int i = 0; i < sourceRecords.Length; i++)
			{
				res.Add(CoreTransformOneRecord(sourceRecords[i], method));
			}
#if ! GENERICS
			return (object[]) res.ToArray(mDestinationType);
#else
			return (Destination[]) res.ToArray(mDestinationType);
#endif 
		}

		
#if ! GENERICS
		private object[] CoreTransformFile(string sourceFile, string destFile, Type sourceType, Type destType, MethodInfo method)
		{
			object[] tempRes;
#else
		private Destination[] CoreTransformFile(string sourceFile, string destFile, Type sourceType, Type destType, MethodInfo method)
		{
			Destination[] tempRes;
#endif 

			using (StreamReader fs = new StreamReader(sourceFile, mSourceEncoding, true))
			{
				using (StreamWriter ds = new StreamWriter(destFile, false, mDestinationEncoding))
				{
					tempRes = CoreTransform(fs, ds, sourceType, destType, method);
					ds.Close();
				}
				
				fs.Close();
			}


			return tempRes;
	}

		private int CoreTransformAsync(string sourceFile, string destFile, Type sourceType, Type destType, MethodInfo method)
		{
			FileHelperAsyncEngine sourceEngine = new FileHelperAsyncEngine(sourceType);
			FileHelperAsyncEngine destEngine = new FileHelperAsyncEngine(destType);

			sourceEngine.Encoding = mSourceEncoding;
			destEngine.Encoding = mDestinationEncoding;

			sourceEngine.BeginReadFile(sourceFile);
			destEngine.BeginWriteFile(destFile);

#if ! GENERICS
			foreach (object record in sourceEngine)
			{
				destEngine.WriteNext(CoreTransformOneRecord(record, method));
			}
#else
			foreach (Source record in sourceEngine)
			{
				destEngine.WriteNext(CoreTransformOneRecord(record, method));
			}
#endif
			
			sourceEngine.Close();
			destEngine.Close();

			return sourceEngine.TotalRecords;
		}

#if ! GENERICS
		private static object CoreTransformOneRecord(object record, MethodInfo method)
		{
			return method.Invoke(record, mEmptyArray);
		}
#else
		private static Destination CoreTransformOneRecord(Source record, MethodInfo method)
		{
			return (Destination) method.Invoke(record, mEmptyArray);
		}
#endif

		#endregion

		#region "  Properties  "


#if NET_2_0
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
#endif
        MethodInfo mConvert1to2 = null;
		//MethodInfo mConvert2to1 = null;

		/// <summary>The source record Type.</summary>
		public Type SourceType
		{
			get { return mSourceType; }
		}

		/// <summary>The destination record Type.</summary>
		public Type DestinationType
		{
			get { return mDestinationType; }
		}

		/// <summary>The Encoding of the Source File.</summary>
		public Encoding SourceEncoding
		{
			get { return mSourceEncoding; }
			set { mSourceEncoding = value; }
		}

		/// <summary>The Encoding of the Destination File.</summary>
		public Encoding DestinationEncoding
		{
			get { return mDestinationEncoding; }
			set { mDestinationEncoding = value; }
		}


		#endregion

		#region "  Helper Methods  "

		private void ValidateRecordTypes()
		{
			mConvert1to2 = GetTransformMethod(SourceType, DestinationType);
			//			mConvert2to1 = GetTransformMethod(DestinationType, SourceType);

			//			if (mConvert2to1 == null)
			//				throw new BadUsageException("You must define a method in the class " + RecordType2.Name + " with the attribute [TransfortToRecord(typeof(" + RecordType2.Name + "))]");
		}

		private MethodInfo GetTransformMethod(Type sourceType, Type destType)
		{
			MethodInfo res = null;
			
			MethodInfo[] methods = sourceType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
			foreach (MethodInfo m in methods)
			{
				if (m.IsDefined(typeof (TransformToRecordAttribute), false))
				{
					TransformToRecordAttribute ta = (TransformToRecordAttribute) m.GetCustomAttributes(typeof (TransformToRecordAttribute), false)[0];
					if (ta.TargetType == destType)
					{
						if (m.ReturnType != destType)
							throw new BadUsageException("The method " + m.Name + " must return an object of type " + destType.Name + " (not " + m.ReturnType.Name + ")");
						else if (m.GetParameters().Length > 0)
							throw new BadUsageException("The method " + m.Name + " must have not parameters");
						else
							res = m;

						break;
					}
				}
			}

			return res;
		}

		#endregion

	}
}

//#endif
