//
// Mono.Data.Tds.Protocol.TdsBulkCopy.cs
//
// Author:
//   Nagappan A (anagappan@novell.com)
//
// Copyright (C) 2007 Novell Inc

//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;

namespace Mono.Data.Tds.Protocol {
        public class TdsBulkCopy
	{
		#region Fields

		Tds tds;
		#endregion

		#region Constructors

		public TdsBulkCopy (Tds tds)
		{
			this.tds = tds;
		}

		#endregion

		#region Methods
		public bool SendColumnMetaData (string colMetaData)
		{
			tds.Comm.StartPacket (TdsPacketType.Query);
			tds.Comm.Append (colMetaData);
			tds.ExecBulkCopyMetaData (30, false);
			return true;
		}

		public bool BulkCopyStart (TdsMetaParameterCollection parameters)
		{
			tds.Comm.StartPacket (TdsPacketType.Bulk);
			tds.Comm.Append ((byte) TdsPacketSubType.ColumnMetadata);
			short count = 0;
			foreach (TdsMetaParameter param in parameters) {
				if (param.Value != null)
					continue;
				count++;
			}
			tds.Comm.Append (count);
			if (parameters != null) {
				foreach (TdsMetaParameter param in parameters) {
					if (param.Value != null)
						continue;
					tds.Comm.Append ((short) 0x00);

					if (param.IsNullable) {
						// fNullable = true
						// usUpdateable = Unused/Unkown
						tds.Comm.Append ((short) 0x09);
					} else {
						// usUpdateable = Unused/Unkown
						tds.Comm.Append ((short) 0x08);
					}

					WriteParameterInfo (param);
					tds.Comm.Append ((byte) param.ParameterName.Length);
					tds.Comm.Append (param.ParameterName);
				}
			}
			return true;
		}

		public bool BulkCopyData (object o, bool isNewRow, int size, TdsMetaParameter parameter)
		{
			// First append a new row byte if needed
			if (isNewRow)
				tds.Comm.Append ((byte) TdsPacketSubType.Row);

			// Push the null value if that is what was supplied
			if (o == null || o == DBNull.Value) {
				if (parameter.IsAnyVarCharMax) {
					// So max varchar and nvarchar needs to contain all F's as a long value.  Seems crazy
					// but oh well
					tds.Comm.Append(System.Convert.ToInt64("0xFFFFFFFFFFFFFFFF", 16));
				} else if (parameter.IsTextType) {
					tds.Comm.Append((byte)0XFF);
					tds.Comm.Append((byte)0XFF);
				}
				else
					tds.Comm.Append ((byte)0);
				return true;
			}

			// Now we must put the size in if it is a VariableType
			// The length of the size field varies based on what type it is
			parameter.CalculateIsVariableType();
			if (parameter.IsVariableSizeType) {
				//int size = parameter.GetActualSize();
				if (parameter.IsAnyVarCharMax) {
					// So max varchar and nvarchar needs to contain the long value as well as size is specified as int
					tds.Comm.Append(System.Convert.ToInt64("0xFFFFFFFFFFFFFFFE", 16));
					tds.Comm.Append ((int) size);
				}
				else if (o.GetType() == typeof(string))
					tds.Comm.Append ((short) size);
				else
					tds.Comm.Append ((byte) size);
			}

			// There are a few special cases for bulk insert that we will handle ourself
			// Otherwise we can just pass the value down to the generic Append Object function
			if (parameter.IsNonUnicodeText)
				tds.Comm.AppendNonUnicode ((string)o);
			else if (parameter.IsMoneyType)
				tds.Comm.AppendMoney ((decimal)o, size);
			else if (parameter.IsDateTimeType)
				tds.Comm.Append((DateTime)o, size);
			else if (parameter.IsDecimalType)
				tds.Comm.AppendDecimal((decimal)o, size, parameter.Scale);
			else
				tds.Comm.Append (o);

			// For some reason max varchar and nvarchar values need to have 4 bytes of 0 appended
			if (parameter.IsAnyVarCharMax)
				tds.Comm.Append ((int)0);
			return true;
		}

		public bool BulkCopyEnd ()
		{
			tds.Comm.Append ((byte) TdsPacketSubType.Done);

			// So the TDS spec calls for a Status (ushort), CurCmd (ushort) and DoneRowCount (long)
			// all of which are 0.
			// However it looks like MS .net is only sending 8 bytes not sure which parts they are leaving
			// out but we are going with the TDS spec size
			tds.Comm.Append ((short) 0x00);
			tds.Comm.Append ((short) 0x00);
			tds.Comm.Append ((long) 0x00);

			tds.ExecBulkCopy (30, false);
			return true;
		}

		private void WriteParameterInfo (TdsMetaParameter param)
		{
			TdsColumnType colType = param.GetMetaType ();

			int size = 0;
			if (param.Size == 0)
				size = param.GetActualSize ();
			else
				size = param.Size;

			/*
			 * If column type is SqlDbType.NVarChar the size of parameter is multiplied by 2
			 * FIXME: Need to check for other types
			 */
			if (colType == TdsColumnType.BigNVarChar)
				size <<= 1;

			// Total hack for varchar(max) and nvarchar(max)
			// They are coming back as Text and not the correct values
			// based on the size we can determine what the correct type is
			// We need the size to come out to 0xFFFF on the wire.
			if (param.IsVarNVarCharMax)
				colType = TdsColumnType.BigNVarChar;
			else if (param.IsVarCharMax)
				colType = TdsColumnType.BigVarChar;

			tds.Comm.Append ((byte)colType); // type

			param.CalculateIsVariableType();

			if (param.IsAnyVarCharMax) {
				tds.Comm.Append ((byte)0xFF);
				tds.Comm.Append ((byte)0xFF);
			} else if (tds.IsLargeType (colType))
				tds.Comm.Append ((short)size); // Parameter size passed in SqlParameter
			else if (tds.IsBlobType (colType))
				tds.Comm.Append (size); // Parameter size passed in SqlParameter
			else if (param.IsVariableSizeType)
				tds.Comm.Append ((byte)size);

			// Precision and Scale are non-zero for only decimal/numeric
			if ( param.TypeName == "decimal" || param.TypeName == "numeric") {
				tds.Comm.Append ((param.Precision!=0)?param.Precision:(byte)29);
				tds.Comm.Append (param.Scale);
			}

			// Documentation is basically 0 on these 5 bytes.  But in a nutshell it seems during a bulk insert
			// these are required for text types.
			if (param.IsTextType) {
				tds.Comm.Append ((byte)0x09);
				tds.Comm.Append ((byte)0x04);
				tds.Comm.Append ((byte)0xd0);
				tds.Comm.Append ((byte)0x00);
				tds.Comm.Append ((byte)0x34);
			}
		}
		#endregion
	}
}
