//-----------------------------------------------------------------------
//  This file is part of the Microsoft Code Samples.
// 
//  Copyright (C) Microsoft Corporation.  All rights reserved.
// 
//This source code is intended only as a supplement to Microsoft
//Development Tools and/or on-line documentation.  See these other
//materials for detailed information regarding Microsoft code samples.
// 
//THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//-----------------------------------------------------------------------

#region Using directives

using System;
using System.IO;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Collections.Generic;
using Microsoft.Samples.SqlServer;
using System.Text;
using System.Security;
#endregion

[assembly: AllowPartiallyTrustedCallers]

public class ShoppingCartService : Service
{
	enum CartStates 
	{
		Shopping,
		Processed,
		End,
		Error
	}

	private Dictionary<int, LineItem> m_shoppingCart;
	private Guid m_cgid;
	private bool m_isNew;

	private CartStates m_state;
	public override int State
	{
		get { return (int)m_state; }
	}

	public ShoppingCartService(SqlConnection conn)
		: base("ShoppingCartService", conn)
	{
		m_shoppingCart = new Dictionary<int, LineItem>();
		m_isNew = true;
		WaitforTimeout = TimeSpan.FromSeconds(10);
		AppLoaderProcName = "LoadState";
	}

	[BrokerMethod((int)CartStates.Shopping, "AddItem")]
	public void AddItem(
		Message msgReceived,
		SqlConnection connection,
		SqlTransaction transaction)
	{
		try
		{
			LineItem li = LineItem.Deserialize(msgReceived);
			LineItem prevLi;
			if (m_shoppingCart.TryGetValue(li.ItemNumber, out prevLi))
			{
				li.Quantity += prevLi.Quantity;
			}
			m_shoppingCart[li.ItemNumber] = li;
		}
		catch (InvalidMessageException ime)
		{
			Conversation conversation = msgReceived.Conversation;
			conversation.EndWithError(1001, ime.Message, connection, transaction);
		}
	}

	[BrokerMethod((int)CartStates.Shopping, "RemoveItem")]
	public void RemoveItem(
		Message msgReceived,
		SqlConnection connection,
		SqlTransaction transaction)
	{
		try
		{
			LineItem li = LineItem.Deserialize(msgReceived);
			m_shoppingCart.Remove(li.ItemNumber);
		}
		catch (InvalidMessageException ime)
		{
			Conversation conversation = msgReceived.Conversation;
			conversation.EndWithError(1001, ime.Message, connection, transaction);
		}
	}

	[BrokerMethod((int)CartStates.Shopping, "Confirm")]
	public void Confirm(
		Message msgReceived,
		SqlConnection connection,
		SqlTransaction transaction)
	{
		Conversation conversation = msgReceived.Conversation;

		Invoice invoice = new Invoice(m_shoppingCart);

		Message msgSend = new Message("Invoice", invoice.Serialize());
		conversation.Send(msgSend, connection, transaction);
		m_state = CartStates.Processed;
	}

	[BrokerMethod((int)CartStates.Processed, Message.EndDialogType)]
	public void CompleteTransaction(
		Message msgReceived,
		SqlConnection connection,
		SqlTransaction transaction)
	{
		msgReceived.Conversation.End(connection, transaction);
		m_state = CartStates.End;
	}

	[BrokerMethod(Message.ErrorType)]
	public void Error(
		Message msgReceived,
		SqlConnection connection,
		SqlTransaction transaction)
	{
		msgReceived.Conversation.End(connection, transaction);
		m_state = CartStates.Error;
	}

	public override bool LoadState(
		SqlDataReader reader, 
		SqlConnection connection, 
		SqlTransaction transaction)
	{
		if (!reader.Read())
			return false;
		m_cgid = reader.GetGuid(0);
		reader.NextResult();

		if (!reader.Read()) {
			m_state = CartStates.Shopping;
			m_isNew = true;
		}
		else {
			m_state = (CartStates) reader.GetInt32(0);
			m_isNew = false;
		}
		reader.NextResult();

		m_shoppingCart.Clear();
		while (reader.Read()) {
			LineItem li = new LineItem();
			li.ItemNumber = reader.GetInt32(0);
			li.Quantity = reader.GetInt32(1);
			li.UnitPrice = reader.GetDouble(2);
			li.Name = reader.GetString(3);
			m_shoppingCart.Add(li.ItemNumber, li);
		}
		reader.NextResult();
		return true;
	}

	public override void  SaveState(
		SqlConnection connection, 
		SqlTransaction transaction)
	{
		if (m_cgid == Guid.Empty)
			return;
		SqlCommand cmd = connection.CreateCommand();
		cmd.Transaction = transaction;
		if (m_isNew)
			cmd.CommandText = "INSERT INTO StateTable VALUES (@cgid, @state)";
		else 
			cmd.CommandText = "UPDATE StateTable SET state=@state WHERE cgid=@cgid";
		cmd.Parameters.Add("@cgid", SqlDbType.UniqueIdentifier).Value = m_cgid;
		cmd.Parameters.Add("@state", SqlDbType.Int).Value = State;
		cmd.ExecuteNonQuery();

		cmd = connection.CreateCommand();
		cmd.Transaction = transaction;
		cmd.CommandText = "DELETE FROM ShoppingList WHERE cgid=@cgid";
		cmd.Parameters.Add("@cgid", SqlDbType.UniqueIdentifier).Value = m_cgid;
		cmd.ExecuteNonQuery();

		cmd = connection.CreateCommand();
		cmd.Transaction = transaction;
		cmd.CommandText = "INSERT INTO ShoppingList VALUES (@cgid, @itemnumber, @quantity, @unitprice, @name)";
		cmd.Parameters.Add("@cgid",			SqlDbType.UniqueIdentifier).Value	= m_cgid;
		IDataParameter itemNumberParam	= cmd.Parameters.Add("@itemnumber", SqlDbType.Int);
		IDataParameter quantityParam	= cmd.Parameters.Add("@quantity", SqlDbType.Int);
		IDataParameter unitPriceParam	= cmd.Parameters.Add("@unitprice", SqlDbType.Float);
		IDataParameter nameParam		= cmd.Parameters.Add("@name", SqlDbType.VarChar, 255);
		foreach (int itemNumber in m_shoppingCart.Keys) {
			LineItem li = m_shoppingCart[itemNumber];
			itemNumberParam.Value	= li.ItemNumber;
			quantityParam.Value		= li.Quantity;
			unitPriceParam.Value	= li.UnitPrice;
			nameParam.Value			= li.Name;
			cmd.ExecuteNonQuery();
		}
	}

	public static void ServiceProc()
	{
		using (SqlConnection conn = new SqlConnection("context connection=true")) 
		{
			conn.Open();

			// Create a new ShoppingCartService on this in-proc connection.
			Service service = new ShoppingCartService(conn);

			// Receive one message at a time, i.e. use RECEIVE TOP(1) ...
			// This is required if we want to interleave database operations
			// such as 'SEND' while iterating a cursor over the result set
			// of the RECEIVE.
			service.FetchSize = 1;

			bool success = false;
		
			// Loop until you can exit successfully
			while (!success)
			{
				try
				{
					service.Run(true, conn, null);
					success = true;
				}
				catch (ServiceException svcex)
				{
					// Let us end the current dialog with the exception
					// wrapped up in the error message.
					svcex.CurrentConversation.EndWithError(2, svcex.Message,
						svcex.Connection, svcex.Transaction);
				}
			}
		}
	}
}