using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;

namespace Creditcall.ChipDna.Client
{
    internal class Client
    {
        #region default messages
        private const string AuthCodeInput = "\r\nPlease Enter Auth Code (Default=12345)";
        private const string VoiceReferralContinue = "\r\nVoice Referral Approved [True,False] (Default=True)";
        private const string VoiceReferralError = "ContinueVoiceReferral Errors";
        private const string VoiceReferralTelNumber = "\r\nVoice Referral Telephone Number: ";
        private const string VoiceReferralDefault = "True";
        private const string AuthCodeDefault = "12345";
        private const string DeferredAuthorizationContinue = "\r\nDeferred Authorization Approved [True,False] (Default=True)";
        private const string DeferredAuthorizationError = "ContinueDeferredAuthorization Errors";
        private const string DeferredAuthorizationDefault = "True";
        #endregion

        public TaskCompletionSource<Dictionary<string, string>> TransactionCompletionSource { get; set; }

        private volatile bool voiceReferralRequired;
        private volatile bool voiceReferralDone;
        private readonly bool saveReceipt;

        private volatile bool deferredAuthorizationRequested;
        private volatile bool deferredAuthorizationDone;

        private volatile bool transactionInProgress;
        private volatile bool getCardDetailsInProgress;

        private bool verboseLogEnabled;
        private readonly StringBuilder consoleCommands;

        private readonly ClientHelper chipDnaClientLib;

        private bool requestQueueRunCompletedEventSubscribed;

        private readonly ConfigFileParser settings;

        public Client(string identifier, string address, int port, string sslHost, bool receiptIsSaved, ConfigFileParser settings)
        {
            Console.OutputEncoding = Encoding.UTF8;

            this.settings = settings;
            saveReceipt = receiptIsSaved;
            consoleCommands = new StringBuilder();
            consoleCommands.Append("S \t Current Status\r\n");
            consoleCommands.Append("N \t Get Card Status\r\n");
            consoleCommands.Append("W \t Get Merchant Data\r\n");
            consoleCommands.Append("M \t Set Idle Message On Supported PED\r\n");
            consoleCommands.Append("R \t Process Refund By Reference\r\n");
            consoleCommands.Append("P \t Start Transaction\r\n");
            consoleCommands.Append("C \t Confirm Transaction\r\n");
            consoleCommands.Append("V \t Void Transaction\r\n");
            consoleCommands.Append("T \t Terminate Transaction\r\n");
            consoleCommands.Append("U \t Update Transaction\r\n");
            consoleCommands.Append("K \t Release Card\r\n");
            consoleCommands.Append("L \t Continue Transaction\r\n");
            consoleCommands.Append("F \t Continue Voice Referral\r\n");
            consoleCommands.Append("D \t Continue Deferred Authorization\r\n");
            consoleCommands.Append("J \t Continue Signature Verification\r\n");
            consoleCommands.Append("I \t Transaction Information\r\n");
            consoleCommands.Append("+ \t Toggle Client Verbose Debug\r\n");
            consoleCommands.Append("X \t Request TMS Update\r\n");
            consoleCommands.Append("G \t Get Card Details\r\n");
            consoleCommands.Append("Q \t Quit Client Application\r\n");
            consoleCommands.Append("B \t Open Pass Thru Session\r\n");
            consoleCommands.Append("E \t Close Pass Thru Session\r\n");
            consoleCommands.Append("H \t Send Pass Thru Command\r\n");
            consoleCommands.Append("O \t Connect And Configure\r\n");
            consoleCommands.Append("A \t Custom Command\r\n");
            consoleCommands.Append("Y \t Show/Hide the Request Queue Event response\r\n");
            consoleCommands.Append("Z \t Run the Request Queue\r\n");
            if (settings.ApiKey != null)
            {
                if (!string.IsNullOrEmpty(identifier))
                {
                    chipDnaClientLib = new ClientHelper(identifier, address, port, sslHost, settings.ApiKey);
                }
                else
                {
                    chipDnaClientLib = new ClientHelper(address, port, sslHost, settings.ApiKey);
                }
                
            }
            else
            {
                chipDnaClientLib = new ClientHelper(identifier, address, port, sslHost);
            }
            
            chipDnaClientLib.VoiceReferralEvent += ChipDnaClientLibOnVoiceReferral;
            chipDnaClientLib.DeferredAuthorizationEvent += ChipDnaClientLibOnDeferredAuthorization;
            chipDnaClientLib.TransactionFinishedEvent += ChipDnaClientLibOnTransactionFinished;
            chipDnaClientLib.TransactionUpdateEvent += ChipDnaClientLibOnTransactionUpdate;
            chipDnaClientLib.CardNotificationEvent += ChipDnaClientLibOnCardNotification;
            chipDnaClientLib.SignatureVerificationRequestedEvent += ChipDnaClientLibOnSignatureVerificationRequested;
            chipDnaClientLib.CardDetailsEvent += ChipDnaClientLibOnCardDetails;
            chipDnaClientLib.TransactionPauseEvent += ChipDnaClientLibOnTransactionPause;
            chipDnaClientLib.PaymentDeviceAvailabilityChangeEvent += ChipDnaClientLibOnPaymentDeviceAvailabilityChangeEvent;
            chipDnaClientLib.TmsUpdateEvent += ChipDnaClientLibOnTmsUpdateEvent;
            chipDnaClientLib.ErrorEvent += ChipDnaClientLibOnErrorEvent;
            chipDnaClientLib.UpdateTransactionParametersFinishedEvent += ChipDnaClientLibOnUpdateTransactionParametersFinishedEvent;
            chipDnaClientLib.SendPassThruCommandResponseEvent += ChipDnaClientLibOnPassThruResponseEvent;
            chipDnaClientLib.OpenPassThruSessionResponseEvent += ChipDnaClientLibOnOpenPassThruSessionResponseEvent;
            chipDnaClientLib.ConnectAndConfigureEvent += ChipDnaClientLibOnConnectAndConfigureEvent;
            chipDnaClientLib.ConfigurationUpdateEvent += ChipDnaClientLibOnConfigurationUpdateEvent;
            chipDnaClientLib.DccRateInformationEvent += ChipDnaClientLibOnDccRateInformationEvent;
        }

        private ParameterSet GetExtraParams(string commandName)
        {
            List<ExtraParameter> extraParams = this.settings.GetExtraParameters(commandName);

            ParameterSet returnSet = new ParameterSet();
            foreach (ExtraParameter theParam in extraParams)
            {
                if (theParam.GetInput)
                {
                    AddUserInputToParameters(returnSet, theParam.Key, theParam.Description, theParam.DefaultValue);
                }
				else if (theParam.DefaultValue != null && theParam.DefaultValue.Equals(":EMPTY:"))
                {
                    returnSet.Add(theParam.Key, "");
                }
                else
                {
                    returnSet.Add(theParam.Key, theParam.DefaultValue);
                }
            }

            return returnSet;
        }

        private void PerformDeferredAuthorization()
        {
            var parameterSet = new ParameterSet();
            var input = GetUserInput(DeferredAuthorizationContinue);
            if (string.IsNullOrEmpty(input))
                input = DeferredAuthorizationDefault;

            parameterSet.Add(ParameterKeys.DeferredAuthorizationApproved, input);

            parameterSet.Add(GetExtraParams("ContinueDeferredAuthorization"));
            var response = chipDnaClientLib.ContinueDeferredAuthorization(parameterSet);

            string errors;
            response.GetValue(ParameterKeys.Errors, out errors);
            if (!string.IsNullOrEmpty(errors))
                Console.WriteLine(ErrorsString(DeferredAuthorizationError, errors));

            deferredAuthorizationDone = true;
            deferredAuthorizationRequested = false;
        }

        private void AddUserInputToParameters(ParameterSet set, string key, string description, string defaultVal)
        {
            StringBuilder message = new StringBuilder();
            message.Append("\r\nPlease enter ");
            message.Append(description);
            message.Append(" (");
            if (!string.IsNullOrEmpty(defaultVal))
            {
                message.Append("default= ");
                message.Append(defaultVal);
            }
            else
            {
                message.Append("Optional");
            }
            message.Append(")");
            var input = GetUserInput(message.ToString());
            input = string.IsNullOrEmpty(input) && !string.IsNullOrEmpty(defaultVal) && !defaultVal.Equals(":EMPTY:", StringComparison.CurrentCulture)? defaultVal : input;

            if (!string.IsNullOrEmpty(input) || defaultVal != null && defaultVal.Equals(":EMPTY:"))
            {
                set.Add(key, input?? "");
            }
        }


        public void PerformStartTransaction(string requestedAmount, string requestedTransactionType)
        {
            ResetTransaction();
            transactionInProgress = true;

            var parameters = new ParameterSet();

            var amount = requestedAmount;
            string amountType = string.Empty;
            var transactionType = GetType(requestedTransactionType, "Sale");
            var reference = string.Format("ChipDNA-{0}-{1}", transactionType, DateTime.Now.ToString("yyyyMMddHHmmss"));
            string batchReference = string.Empty;

            string panKeyEntry = string.Empty;

            if (!string.IsNullOrEmpty(panKeyEntry) && !panKeyEntry.Equals("false", StringComparison.InvariantCultureIgnoreCase)) 
            {
                parameters.Add(ParameterKeys.PanKeyEntry, panKeyEntry);

                if (!transactionType.Equals("Refund", StringComparison.InvariantCultureIgnoreCase)) //if the user chooses to perform a PanKeyEntry transaction and it's not a refund then prompt for address information
                {
                    AddUserInputToParameters(parameters, ParameterKeys.CardholderAddress, "address information ", null);
                    AddUserInputToParameters(parameters, ParameterKeys.CardholderZipcode, "Postcode/Zipcode", null);
                }
            }

            if (!string.IsNullOrEmpty(amount))
                parameters.Add(ParameterKeys.Amount, amount);

            if (!string.IsNullOrEmpty(amountType))
                parameters.Add(ParameterKeys.AmountType, amountType);

            parameters.Add(ParameterKeys.Reference, reference);
            parameters.Add(ParameterKeys.TransactionType, transactionType);

            if (transactionType.Equals("Sale", StringComparison.InvariantCultureIgnoreCase))
            {
                bool isFirstStore = false;
                parameters.Add(ParameterKeys.CredentialOnFileFirstStore, isFirstStore.ToString());

                if (isFirstStore)
                {
                    parameters.Add(ParameterKeys.CredentialOnFileReason, GetFirstStoreReason());
                }


                parameters.Add(ParameterKeys.TippingSupport, "Default");
            }

            if (!string.IsNullOrEmpty(batchReference))
            {
                parameters.Add(ParameterKeys.BatchReference, batchReference);
            }

            parameters.Add(GetExtraParams("StartTransaction"));
            Console.WriteLine(parameters.ToString());
            Response response = chipDnaClientLib.StartTransaction(parameters);

            string errors;
            if (response.GetValue(ParameterKeys.Errors, out errors) && !string.IsNullOrEmpty(errors))
            {
                var result = new Dictionary<string, string>
        {
            { "ERROR", errors },
            { "REFERENCE", reference },
            { "TRANSACTION_RESULT", "ERROR" }
        };

                if (TransactionCompletionSource != null && !TransactionCompletionSource.Task.IsCompleted)
                {
                    TransactionCompletionSource.TrySetResult(result);
                }

                Console.WriteLine(ErrorsString("StartTransaction Errors", errors));
                return;
            }
        }

        public void Run()
        {

            while (true)
            {
                string userInput;

                PrintConsoleCommands();

                userInput = Console.ReadLine();
                if (string.IsNullOrEmpty(userInput)) continue;
                userInput = userInput.ToUpper().Trim();

                if (userInput.Equals("Q"))
                {
                    chipDnaClientLib.Dispose();
                    break;
                }

                ComputeInput(userInput);
            }
        }

        private void PrintConsoleCommands()
        {
            if (transactionInProgress || getCardDetailsInProgress || deferredAuthorizationDone || voiceReferralDone) return;

        }

        private void ResetTransaction()
        {
            transactionInProgress = false;
            getCardDetailsInProgress = false;

            voiceReferralRequired = false;
            voiceReferralDone = false;
            deferredAuthorizationRequested = false;
            deferredAuthorizationDone = false;
        }

        private void ComputeInput(String input)
        {
            try
            {
                switch (input)
                {
                    case "M":
                        SetIdleMessage();
                        break;
                    case "R":
                        PerformRefundTransactionByReference();
                        break;
                    case "P":
                        PerformStartTransaction("250", "");
                        break;
                    case "C":
                        PerformConfirmTransaction();
                        break;
                    case "V":
                        PerformVoidTransaction();
                        break;
                    case "I":
                        PerformGetTransactionInformation();
                        break;
                    case "K":
                        PerformReleaseCard();
                        break;
                    case "L":
                        PerformContinueTransaction();
                        break;
                    case "U":
                        DoUpdateTransaction();
                        break;
                    case "T":
                        PerformTerminateTransaction();
                        break;
                    case "F":
                        PerformVoiceReferral();
                        break;
                    case "D":
                        PerformDeferredAuthorization();
                        break;
                    case "+":
                        verboseLogEnabled = !verboseLogEnabled;
                        Console.WriteLine("VerboseLogEnabled: {0}", verboseLogEnabled);
                        break;
                    case "S":
                        GetStatus();
                        break;
                    case "W":
                        GetMerchantData();
                        break;
                    case "N":
                        GetCardStatus();
                        break;
                    case "X":
                        RequestTmsUpdate();
                        break;
                    case "G":
                        PerformGetCardDetails();
                        break;
                    case "B":
                        OpenPassThruSession();
                        break;
                    case "E":
                        ClosePassThruSession();
                        break;
                    case "H":
                        SendPassThruCommand();
                        break;
                    case "O":
                        ConnectAndConfigure();
                        break;
                    case "A":
                        PerformCustomCommand();
                        break;
                    case "J":
                        PerformContinueSignatureVerification();
                        break;
                    case "Y":
                        ToggleRequestQueueRunCompletedEvent();
                        break;
                    case "Z":
                        RunRequestQueue();
                        break;
                    default:
                        return;
                }
            }
            catch (ClientException ex)
            {
                PrintErrorEvent(ex);
            }
        }

        private void PrintErrorEvent(Exception exception)
        {
            if (exception == null) return;

            var builder = new StringBuilder();
            builder.Append(exception.Message);

            var inner = exception.InnerException;
            while (inner != null && !string.IsNullOrEmpty(inner.Message))
            {
                builder.Append(inner.Message + "\r\n");
                inner = inner.InnerException;
            }

            if (verboseLogEnabled)
            {
                Console.WriteLine("ERROR: {0}:\r\n{1} ", builder, exception);
            }
            else
            {
                Console.WriteLine("ERROR: {0}", builder);
            }

        }

        private void RequestTmsUpdate()
        {
            var parameter = new ParameterSet();

            var requestType = GetUserInput("\r\nTMS Request Type [TmsConfiguration] (Default=TmsConfiguration)");
            if (!string.IsNullOrEmpty(requestType))
            {
                parameter.Add(ParameterKeys.TmsRequestType, requestType);
            }

            var updateType = GetUserInput("\r\nConfiguration Update Type [Partial,Full] (Default=Partial)");
            if (!string.IsNullOrEmpty(updateType))
            {
                parameter.Add(ParameterKeys.TmsUpdateType, updateType);
            }

            var configUpdateSchedule = GetUserInput("\r\nConfiguration Update Schedule [Immediate,MaintenanceTime] (Default=Immediate)");
            if (!string.IsNullOrEmpty(configUpdateSchedule))
            {
                parameter.Add(ParameterKeys.ConfigurationUpdateSchedule, configUpdateSchedule);
            }

            parameter.Add(GetExtraParams("RequestTmsUpdate"));
            var response = chipDnaClientLib.RequestTmsUpdate(parameter);
            string errors;
            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("RequestTmsConfigUpdate Errors", errors));
            }
        }

        private void SetIdleMessage()
        {
            var idleMessage = GetUserInput("\r\nPlease Enter Message For Idle Screen");
            var parameter = new ParameterSet();
            if (!string.IsNullOrEmpty(idleMessage))
            {
                parameter.Add(ParameterKeys.IdleDisplayMessage, idleMessage);
            }

            parameter.Add(GetExtraParams("SetIdleMessage"));
            var response = chipDnaClientLib.SetIdleMessage(parameter);
            string errors;
            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("SetIdleMessage Errors", errors));
            }
        }

        private void GetStatus()
        {
            Console.WriteLine(ParameterKeys.VersionInformation + "\n" + ParameterKeys.ChipDnaStatus + "\n" + ParameterKeys.PaymentDeviceStatus + "\n" + ParameterKeys.RequestQueueStatus + "\n" + ParameterKeys.TmsStatus + "\n" + ParameterKeys.PaymentPlatformStatus);

            var parameterSet = new ParameterSet();

            parameterSet.Add(GetExtraParams("GetStatus"));
            Response response = chipDnaClientLib.GetStatus(GetRequestedParameterSet(parameterSet)); 
            
            string errors;
            var serverStatus = (ChipDnaStatus) null;
            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("GetStatus Errors", errors));
            }
            else
            {
                 serverStatus = ChipDnaStatus.ParseFromResponse(response);
            }

            if (serverStatus != null)
            {
                Console.WriteLine(serverStatus);
            }
        }

        private void GetMerchantData()
        {
            Response response = chipDnaClientLib.GetMerchantData(new ParameterSet());

            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out var errors))
            {
                Console.WriteLine(ErrorsString("GetMerchantData Errors", errors));
            }

            var merchantData = MerchantData.ParseFromResponse(response);
            Console.WriteLine((merchantData.ToString()));
        }

        private void GetCardStatus()
        {
            var parameterSet = new ParameterSet();
            var input = GetUserInput("\r\nEnter CardNotificationEvents [Transactions,Always] (Optional)");

            if (!string.IsNullOrEmpty(input))
            {
                parameterSet.Add(ParameterKeys.CardNotificationEvents, input);
            }

            parameterSet.Add(GetExtraParams("GetCardStatus"));
            var response = chipDnaClientLib.GetCardStatus(parameterSet);

            string errors;
            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("GetStatus Errors", errors));
            }

            var cardStatus = new DeviceCardStatusDataObject(response);

            Console.WriteLine(cardStatus);

        }

        private void PerformVoiceReferral()
        {
            var parameterSet = new ParameterSet();
            var input = GetUserInput(VoiceReferralContinue);
            if (String.IsNullOrEmpty(input))
                input = VoiceReferralDefault;
            parameterSet.Add(ParameterKeys.VoiceReferralApproved, input);

            var authcode = GetUserInput(AuthCodeInput);
            if (string.IsNullOrEmpty(authcode))
                authcode = AuthCodeDefault;

            parameterSet.Add(ParameterKeys.AuthCode, authcode);

            parameterSet.Add(GetExtraParams("ContinueVoiceReferral"));
            var response = chipDnaClientLib.ContinueVoiceReferral(parameterSet);
            string errors;
            response.GetValue(ParameterKeys.Errors, out errors);
            if (!string.IsNullOrEmpty(errors))
                Console.WriteLine(ErrorsString(VoiceReferralError, errors));

            voiceReferralDone = true;
            voiceReferralRequired = false;
        }

        private static string ErrorsString(string errorsTitle, string errors)
        {
            if (errorsTitle == null)
                errorsTitle = "";
            if (errors == null)
                errors = "";
            var errorStringBuilder = new StringBuilder();
            errorStringBuilder.Append("\r\n");
            errorStringBuilder.AppendFormat(null, "Errors: [{0}]\t{1}", errorsTitle, errors);

            return errorStringBuilder.ToString();
        }

        private void PerformRefundTransactionByReference()
        {
            var defaultReference = string.Format("ChipDNA-{0}-{1}", "refundbyref", DateTime.Now.ToString("yyyyMMddHHmmss"));
            var amount = GetAmount(true);
            var reference = GetReference(true, defaultReference);
            var saleReference = GetUserInput("\r\nPlease Enter Sale Reference (Required)");

            var saleDateTime = GetUserInput("\r\nPlease Enter Sale Date And Time (Optional)");
            var parameterSet = new ParameterSet();
            if (!string.IsNullOrEmpty(amount))
            {
                parameterSet.Add(ParameterKeys.Amount, amount);
            }
            if (!string.IsNullOrEmpty(saleReference))
            {
                parameterSet.Add(ParameterKeys.SaleReference, saleReference);
            }
            if (!string.IsNullOrEmpty(reference))
            {
                parameterSet.Add(ParameterKeys.Reference, reference);
            }

            if (!string.IsNullOrEmpty(saleDateTime))
            {
                parameterSet.Add(ParameterKeys.SaleDateTime, saleDateTime);
            }
            parameterSet.Add(GetExtraParams("LinkedRefundTransaction"));
            var response = chipDnaClientLib.LinkedRefundTransaction(parameterSet);
            string transactionResult;
            if (response.GetValue(ParameterKeys.TransactionResult, out transactionResult))
            {

                Console.WriteLine("Refund By Reference Result: {0}", transactionResult);
            }
            string transactionId;
            if (response.GetValue(ParameterKeys.TransactionId, out transactionId))
            {
                Console.WriteLine("Transaction ID: {0}", transactionId);
            }
            string receipt;
            if (response.GetValue(ParameterKeys.ReceiptDataMerchant, out receipt))
            {
                PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt));

            }
            if (response.GetValue(ParameterKeys.ReceiptDataCardholder, out receipt))
            {
                PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt));

            }
            string errors;
            if (response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("RefundTransactionByReference Errors", errors));
            }
            string errorDescription;
            if (response.GetValue(ParameterKeys.ErrorDescription, out errorDescription))
            {
                Console.WriteLine("ErrorDescription: {0}", errorDescription);
            }
        }

        public void PerformConfirmTransaction()
        {
            var reference = GetReference(true, "");
            var amount = GetAmount(false);
            var gratuity = GetGratuity(false);
            var closeTransaction = GetCloseTransaction(false);

            var parameters = new ParameterSet();
            if (!string.IsNullOrEmpty(reference))
            {
                parameters.Add(ParameterKeys.Reference, reference);
            }
            if (!String.IsNullOrEmpty(amount))
            {
                parameters.Add(ParameterKeys.Amount, amount);
            }
            if (!String.IsNullOrEmpty(gratuity))
            {
                parameters.Add(ParameterKeys.Gratuity, gratuity);
            }
            if (!String.IsNullOrEmpty(closeTransaction))
            {
                parameters.Add(ParameterKeys.CloseTransaction, closeTransaction);
            }

            parameters.Add(GetExtraParams("ConfirmTransaction"));
            var response = chipDnaClientLib.ConfirmTransaction(parameters);
            string receipt, result;
            if (response.GetValue(ParameterKeys.TransactionResult, out result))
            {
                Console.WriteLine("Confirmed Transaction Result: {0}", result);
            }
            if (response.GetValue(ParameterKeys.ReceiptDataMerchant, out receipt))
            {
                PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt));
            }
            if (response.GetValue(ParameterKeys.ReceiptDataCardholder, out receipt))
            {
                PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt));
            }
            string errors;
            if (response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("ConfirmTransaction Errors", errors));
            }

            string errorDescription;
            if (response.GetValue(ParameterKeys.ErrorDescription, out errorDescription))
            {
                Console.WriteLine("ErrorDescription: {0}", errorDescription);
            }
        }

        private void DoUpdateTransaction()
        {
            var reference = GetReference(false, "");
            var amount = GetAmount(false);

            var paramSet = new ParameterSet();
            if (ShouldUpdateBatchReference())
            {
                var batchReference = GetBatchReference(false);
                paramSet.Add(ParameterKeys.BatchReference, batchReference);
            }
            if (!string.IsNullOrEmpty(reference))
            {
                paramSet.Add(ParameterKeys.Reference, reference);
            }
            if (!string.IsNullOrEmpty(amount))
            {
                paramSet.Add(ParameterKeys.Amount, amount);
            }

            paramSet.Add(GetExtraParams("UpdateTransactionParameters"));
            var response = chipDnaClientLib.UpdateTransactionParameters(paramSet);
            string errors;
            if (response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("UpdateTransaction Errors", errors));
            }
        }

        private void PerformReleaseCard()
        {
            var response = chipDnaClientLib.ReleaseCard(GetExtraParams("ReleaseCard"));
            string errors;
            if (response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("ReleaseCard Errors", errors));
            }
        }

        private void PerformTerminateTransaction()
        {
            var terminateReason = GetUserInput("\r\nPlease Enter Terminate Reason (Optional)");
            var displayMessage = GetUserInput("\r\nPlease Enter Display Message (Optional)");

            var parameters = new ParameterSet();

            if (!string.IsNullOrEmpty(displayMessage))
            {
                parameters.Add(ParameterKeys.TerminateDisplayMessage, displayMessage);
            }

            if (!string.IsNullOrEmpty(terminateReason))
            {
                parameters.Add(ParameterKeys.TerminateReason, terminateReason);
            }

            parameters.Add(GetExtraParams("TerminateTransaction"));
            var response = chipDnaClientLib.TerminateTransaction(parameters);

            string errors;
            if (response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("TerminateTransaction Errors", errors));
            }
        }

        private void PerformVoidTransaction()
        {
            var reference = GetReference(true, "");
            var voidReason = GetUserInput("\r\nPlease Enter Void Reason (Optional)");
            var parameters = new ParameterSet();
            if (!string.IsNullOrEmpty(reference))
            {
                parameters.Add(ParameterKeys.Reference, reference);
            }
            if (!String.IsNullOrEmpty(voidReason))
            {
                parameters.Add(ParameterKeys.VoidReason, voidReason);
            }

            parameters.Add(GetExtraParams("VoidTransaction"));
            var response = chipDnaClientLib.VoidTransaction(parameters);
            string receipt, result;
            if (response.GetValue(ParameterKeys.TransactionResult, out result))
            {
                Console.WriteLine("Void Transaction Result: {0}", result);
            }
            if (response.GetValue(ParameterKeys.ReceiptDataMerchant, out receipt))
            {
                PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt));
            }
            if (response.GetValue(ParameterKeys.ReceiptDataCardholder, out receipt))
            {
                PrintReceiptData(ReceiptData.GetReceiptDataFromXml(receipt));
            }
            string errors;
            if (response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("VoidTransaction Errors", errors));
            }
            string errorDescription;
            if (response.GetValue(ParameterKeys.ErrorDescription, out errorDescription))
            {
                Console.WriteLine("ErrorDescription: {0}", errorDescription);
            }
        }

        private void PerformGetTransactionInformation()
        {
            var referenceValue = GetReference(true, "");

            var parameters = new ParameterSet();
            if (!string.IsNullOrEmpty(referenceValue))
            {
                parameters.Add(ParameterKeys.Reference, referenceValue);
            }

            parameters.Add(GetExtraParams("GetTransactionInformation"));
            var response = chipDnaClientLib.GetTransactionInformation(parameters);

            var responseParameter = response.ToList();

            responseParameter.ForEach(parameter =>
            {
                if (parameter.Key == ParameterKeys.Errors)
                {
                    Console.WriteLine(ErrorsString("GetTransactionInformation Errors", parameter.Value));
                }
                else
                {
                    Console.WriteLine("{0} {1}", parameter.Key, parameter.Value);
                }
            });
        }

        private static string GetUserInput(string prompt)
        {
            Console.Write("\r\n{0}:", prompt);
            string input = Console.ReadLine();
            return input;
        }

        private void PerformContinueTransaction()
        {
            var forceDecline = GetUserInput("\r\nForce Decline [True,False] (Default=False)");
            var continueParams = new ParameterSet();
            if (!string.IsNullOrEmpty(forceDecline))
            {
                continueParams.Add(ParameterKeys.ForceDecline, forceDecline);
            }
            var amount = GetAmount(false);
            var reference = GetReference(false, "");

            if (ShouldUpdateBatchReference())
            {
                var batchReference = GetBatchReference(false);
                continueParams.Add(ParameterKeys.BatchReference, batchReference);
            }

            if (!string.IsNullOrEmpty(amount))
            {
                continueParams.Add(ParameterKeys.Amount, amount);
            }

            if (!string.IsNullOrEmpty(reference))
            {
                continueParams.Add(ParameterKeys.Reference, reference);
            }

            continueParams.Add(GetExtraParams("ContinueTransaction"));
            Response response = chipDnaClientLib.ContinueTransaction(continueParams);
            string errors;
            response.GetValue(ParameterKeys.Errors, out errors);
            Console.WriteLine(!string.IsNullOrEmpty(errors) ? ErrorsString("ContinuePausedTransaction Errors", errors) : "");
        }

        private void PerformContinueSignatureVerification()
        {
            var parameters = new ParameterSet();
            parameters.Add(GetExtraParams("PerformContinueSignatureVerification"));

            var result = GetUserInput("\r\nVerify Signature? [True,False]");

            if (!string.IsNullOrEmpty(result))
            {
                parameters.Add(ParameterKeys.SignatureVerificationResult, result);
            }

            var response = chipDnaClientLib.ContinueSignatureVerification(parameters);
            string errors;
            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("ContinueSignatureVerification Errors", errors));
            }
        }

        private void PerformGetCardDetails()
        {
            ResetTransaction();
            getCardDetailsInProgress = true;

            var response = chipDnaClientLib.GetCardDetails(GetExtraParams("GetCardDetails"));
            string errors;
            if (response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("GetCardDetails Errors", errors));
            }
        }

        private void PerformCustomCommand()
        {

            var parameters = new ParameterSet();
            parameters.Add(GetExtraParams("CustomCommand"));
            var response = chipDnaClientLib.CustomCommand(parameters);
            string errors;
            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("CustomCommand Errors", errors));
            }
        }

        private void ToggleRequestQueueRunCompletedEvent()
        {
            requestQueueRunCompletedEventSubscribed = !requestQueueRunCompletedEventSubscribed;
            Console.WriteLine("{0} the RequestQueueRunCompletedEvent.", requestQueueRunCompletedEventSubscribed ? "Subscribed to" : "Unsubscribed from");
            if (requestQueueRunCompletedEventSubscribed)
                chipDnaClientLib.RequestQueueRunCompletedEvent += ChipDnaClientLibOnRequestQueueRunCompletedEvent;
            else
                chipDnaClientLib.RequestQueueRunCompletedEvent -= ChipDnaClientLibOnRequestQueueRunCompletedEvent;
        }

        private void RunRequestQueue()
        {
            var parameters = new ParameterSet();
            var requestQueueType = GetUserInput("\r\nRun failed transactions (Pending/Failed/PendingAndFailed)? Default=[Pending]");
            
            if (!string.IsNullOrEmpty(requestQueueType))
                parameters.Add(ParameterKeys.RequestQueueType, requestQueueType);

            if (Enum.TryParse(requestQueueType, true, out RequestQueueTypes requestQueueTypeEnum))
            {
                if (new[] { RequestQueueTypes.Failed, RequestQueueTypes.PendingAndFailed }.Contains(requestQueueTypeEnum))
                {
                    var reRunFailedTransactionsFromDate = GetUserInput("\r\nFrom when should the failed transactions be run (yyyyMMddHHmmss)?");
                    parameters.Add(ParameterKeys.RunFailedTransactionsFromDate, reRunFailedTransactionsFromDate);
                }
            }

            var response = chipDnaClientLib.RunRequestQueue(parameters);
            var responseParameter = response.ToList();

            if (response.GetValue(ParameterKeys.Errors, out var errors))
            {
                Console.WriteLine(ErrorsString("RunRequestQueue Errors", errors));
            }
        }


        private void ConnectAndConfigure()
        {

            var response = chipDnaClientLib.ConnectAndConfigure(null);
            string errors;
            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("ConnectAndConfigure Errors", errors));
            }
        }
        
        private void OpenPassThruSession()
        {
            var deviceId = GetUserInput("\r\nEnter payment device ID");
            var parameters = new ParameterSet();
            if (!string.IsNullOrEmpty(deviceId))
            {
                parameters.Add(ParameterKeys.PaymentDeviceIdentifier, deviceId);
            }

            var response = chipDnaClientLib.OpenPassThruSession(parameters);

            string errors;
            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("OpenPassThruSession Errors", errors));
            }
        }

        private void ClosePassThruSession()
        {
            var deviceId = GetUserInput("\r\nEnter payment device ID");
            var parameters = new ParameterSet();
            if (!string.IsNullOrEmpty(deviceId))
            {
                parameters.Add(ParameterKeys.PaymentDeviceIdentifier, deviceId);
            }

            var response = chipDnaClientLib.ClosePassThruSession(parameters);

            string errors;
            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("ClosePassThruSession Errors", errors));
            }
        }

        private void SendPassThruCommand()
        {
            var deviceId = GetUserInput("\r\nEnter payment device ID");
            var parameters = new ParameterSet();
            if (!string.IsNullOrEmpty(deviceId))
            {
                parameters.Add(ParameterKeys.PaymentDeviceIdentifier, deviceId);
            }

            var commandData = GetUserInput("\r\nEnter command (hex string)");
            if (!string.IsNullOrEmpty(commandData))
            {
                parameters.Add(ParameterKeys.Data, commandData);
            }

            var response = chipDnaClientLib.SendPassThruCommand(parameters);

            string errors;
            if (response.ContainsKey(ParameterKeys.Errors) && response.GetValue(ParameterKeys.Errors, out errors))
            {
                Console.WriteLine(ErrorsString("SendPassThruCommand Errors", errors));
            }
        }

        private string NewLine()
        {
#if !WindowsCE
            return Environment.NewLine;
#else
            return "\r\n";
#endif
        }

        private void PrintReceiptData(ReceiptData receiptData)
        {
            if (receiptData == null) return;
            var receiptDataBuilder = new StringBuilder();
            receiptDataBuilder.Append("Receipt Data:");
            var receiptDataForPrinting = new StringBuilder();

            var entries = new List<ReceiptData.ReceiptEntry>(receiptData.ReceiptEntries);
            entries.Sort((entry1, entry2) =>
            {
                if (entry1.Priority > entry2.Priority)
                {
                    return 1;
                }

                if (entry1.Priority < entry2.Priority)
                {
                    return -1;
                }

                return 0;
            });
            
            var recipientEntry = entries.FirstOrDefault(e => e.ReceiptEntryId.Equals("Recipient") && e.Value == "CARDHOLDER COPY");
            bool isCustomerCopy = (recipientEntry != null) ? true : false;

            List<string> printData = new List<string>(entries.Count);
            foreach (var receiptEntry in entries)
            {
                if (isCustomerCopy && receiptEntry.ReceiptEntryId.Equals("SignatureLineLabel")) continue;

                /*
                 * MerchantStreetAddress, MerchantCityStateZip and MerchantPhoneNumber
                 * are not populated by ChipDNA and must be populated by the integration,
                 * when they are present in ReceiptData.
                 * The same also applies to MerchandiseDescription
                 */
                if (receiptEntry.ReceiptEntryId.Equals("MerchantStreetAddress") &&
                    !string.IsNullOrEmpty(settings.MerchantStreetAddress))
                {
                    //e.g. 123 Western Boulevard
                    receiptEntry.Value = settings.MerchantStreetAddress;
                }
                else if (receiptEntry.ReceiptEntryId.Equals("MerchantCityStateZip") &&
                         !string.IsNullOrEmpty(settings.MerchantCityStateZip))
                {
                    //e.g. Chicago, IL, 60007 
                    receiptEntry.Value = settings.MerchantCityStateZip;
                }
                else if (receiptEntry.ReceiptEntryId.Equals("MerchantPhoneNumber") &&
                         !string.IsNullOrEmpty(settings.MerchantPhoneNumber))
                {
                    //e.g. (555) 555-1234
                    receiptEntry.Value = settings.MerchantPhoneNumber;
                } 
                else if (receiptEntry.ReceiptEntryId.Equals("MerchandiseDescription") &&
                      !string.IsNullOrEmpty(settings.MerchandiseDescription))
                {
                    //e.g. Food and drinks
                    receiptEntry.Value = settings.MerchandiseDescription;
                }

                receiptDataBuilder.AppendFormat(null,
                    "\t{0}=> {1}{2}\r\n",
                    string.Format("[{0} - {1}]", receiptEntry.ReceiptEntryId, receiptEntry.ReceiptItemType).PadRight(40, '-'),
                    string.IsNullOrEmpty(receiptEntry.Label) ? "" : String.Format("{0}: ", receiptEntry.Label),
                    receiptEntry.Value);

                string label = string.IsNullOrEmpty(receiptEntry.Label) ? String.Empty : receiptEntry.Label + ": ";

                printData.Add(string.Format("{0}{1}", label, receiptEntry.Value));

                if (receiptEntry.ReceiptEntryId.Equals("SignatureLineLabel", StringComparison.InvariantCultureIgnoreCase))
                {
                    printData.Add(string.Empty.PadRight(30, '_'));
                }
            }


            if (saveReceipt)
            {
                try
                {
                    string currentPath = Directory.GetCurrentDirectory();

                    var receiptDir = Path.Combine(currentPath, @"Receipts\");
                    var receiptFilename = string.Format("ReceiptData.{0}.txt", DateTime.Now.ToString("yyyyMMddHHmmss"));

                    if (!Directory.Exists(receiptDir))
                    {
                        Directory.CreateDirectory(receiptDir);
                    }

                    var receiptFilePath = Path.Combine(receiptDir, Path.GetFileName(receiptFilename));
                    var str = string.Join(NewLine(), printData.ToArray());
                    str += Environment.NewLine; //To separate Merchant Copy from Cardholder Copy
                    using (StreamWriter sw = new StreamWriter(receiptFilePath, append: true, Encoding.UTF8))
                    {
                        sw.WriteLine(str, Encoding.UTF8);
                    }
                    Console.WriteLine("Receipt File: {0}", receiptFilePath);

                }
                catch (Exception ex)
                {
                    PrintErrorEvent(new Exception("Error Writing Receipt to File.", ex));
                }
            }

            Console.WriteLine("{0}{1}{2}{1}{0}", string.Empty.PadRight(80, '='), "\r\n", receiptDataBuilder);
        }

        private string GetAmount(bool isRequired)
        {
            return GetUserInput(string.Format("\r\nPlease Enter Amount [eg 250 => £2.50] ({0})", (isRequired) ? "Required" : "Optional"));
        }

        private bool GetFirstStore()
        {
            Boolean.TryParse(GetUserInput(string.Format("\r\nCredential On File First Store Transaction? [True,False] (Optional)")), out bool firstStore);
            return firstStore;
        }

        private string GetFirstStoreReason()
        {
            return GetUserInput(string.Format("\r\nCredential On File Transaction Reason? (Optional)"));
        }

        private ParameterSet GetRequestedParameterSet(ParameterSet parameterSet)
        {
            var input = GetUserInput(string.Format(
                "\r\nPlease Enter Requested Parameters (Comma Separated) [eg CHIPDNA_STATUS,TMS_STATUS] (Default = Returns all parameters)"));
            if (input != null) 
            {
                string[] keys = input.Split(',');
                foreach (string par in keys)
                {
                    if (!parameterSet.ContainsKey(par) && par != "")
                    {
                        parameterSet.Add(par, par);
                    }
                }
            }
            return parameterSet;
        }

        private string GetAmountType(bool isRequired)
        {
            return GetUserInput(string.Format("\r\nActual or Estimated Amount? ({0}) [Actual]", isRequired ? "Required" : "Optional"));
        }

        private string GetGratuity(bool isRequired)
        {
            return GetUserInput(string.Format("\r\nPlease Enter Gratuity [eg 250 => £2.50] ({0} - End Of Day tipping)", (isRequired) ? "Required" : "Optional"));
        }

        private string GetCloseTransaction(bool isRequired)
        {
            return GetUserInput(string.Format("\r\nClose Transaction [True,False] (Default = True) ({0} - End Of Day tipping)", (isRequired) ? "Required" : "Optional"));
        }

        private string getDefaultInput(string defaultFormat)
        {
            if (string.IsNullOrEmpty(defaultFormat))
            {
                return "";
            }
            return string.Format("[{0}]", defaultFormat);
        }

        private String GetType(string requestedTransactionType, string defaultType)
        {
            if (string.IsNullOrEmpty(requestedTransactionType))
            {
                if (string.IsNullOrEmpty(defaultType))
                    return "";
                return defaultType;
            }
            return requestedTransactionType;
        }

        private String GetTippingMethod(bool isRequired, string defaultMethod)
        {
            var type = GetUserInput(string.Format("\r\nPlease Enter Tipping Method ({0}) {1}", (isRequired) ? "Required" : "Optional", getDefaultInput(defaultMethod)));
            if (string.IsNullOrEmpty(type))
            {
                if (string.IsNullOrEmpty(defaultMethod))
                    return "";
                return defaultMethod;
            }
            return type;
        }

        private String GetReference(bool isRequired, string defaultReference)
        {
            var reference = GetUserInput(string.Format("\r\nPlease Enter Reference ({0}) {1}", (isRequired) ? "Required" : "Optional", getDefaultInput(defaultReference)));
            if (string.IsNullOrEmpty(reference))
            {
                if (!string.IsNullOrEmpty(defaultReference))
                {
                    reference = defaultReference;
                }
                else
                {
                    return "";
                }
            }
            return reference;
        }
        
        private static bool ShouldUpdateBatchReference()
        {
            string answer = GetUserInput("Update Batch Reference [True,False] (Default = False)");
            if (string.IsNullOrEmpty(answer))
                return false;
            return answer.Substring(0, 1).ToUpper().Equals("T", StringComparison.InvariantCultureIgnoreCase);
        }

        private static string GetBatchReference(bool displayOptional)
        {
            return GetUserInput(string.Format("\r\nPlease Enter Batch Reference ({0})", (displayOptional) ? "Optional" : "Required"));
        }

        private void ChipDnaClientLibOnVoiceReferral(object sender, EventParameters voiceReferralEventArgs)
        {
            // Console.WriteLine("Voice Referral Event Parameters: {0}", voiceReferralEventArgs);

            var processSource = "";

            var telephoneNumber = "";

            foreach (var parameter in voiceReferralEventArgs.ToList())
            {
                if (ParameterKeys.VoiceReferralVerificationSource.Equals(parameter.Key))
                {
                    try
                    {
                        processSource = parameter.Value;
                    }
                    catch (Exception ex)
                    {
                        PrintErrorEvent(new Exception("Error Parsing Voice Referral Source.", ex));
                    }
                }

                if (ParameterKeys.VoiceReferralTelephoneNumber.Equals(parameter.Key))
                {
                    telephoneNumber = parameter.Value;
                }
            }

            if (processSource.Equals("Pos"))
            {
                Console.WriteLine("Voice Referral Event Parameters: {0}{1} *Waiting For Voice Referral Continue Command --> Press 'F' To Continue*",
                    voiceReferralEventArgs, "\r\n");
                voiceReferralRequired = true;
            }

        }

        private void ChipDnaClientLibOnDeferredAuthorization(object sender, EventParameters deferredAuthorizationEventArgs)
        {
            var pinBlockPresent = false;

            foreach (var parameter in deferredAuthorizationEventArgs.ToList())
            {
                if (ParameterKeys.PinBlockPresent.Equals(parameter.Key))
                {
                    try
                    {
                        pinBlockPresent = bool.Parse(parameter.Value);
                    }
                    catch (Exception ex)
                    {
                        PrintErrorEvent(new Exception("Error Parsing Pin Block Present.", ex));
                    }
                }
            }

            Console.WriteLine("Deferred Authorization Event Parameters: {0}{1} *Waiting for Deferred Authorization Continue Command --> Press 'D' To Continue*",
                deferredAuthorizationEventArgs, "\r\n");
            deferredAuthorizationRequested = true;
        }

        private void ChipDnaClientLibOnTransactionFinished(object sender, EventParameters transactionFinishedEventArgs)
        {
            var result = new Dictionary<string, string>();
            var transactionFinishedStringBuilder = new StringBuilder();
            transactionFinishedStringBuilder.Append("Transaction Finished Event Parameters: ");
            string reference = null;
            bool signatureRequired = false;

            if (transactionFinishedEventArgs != null)
            {
                ExtractParametersForLogging(transactionFinishedEventArgs,
                    out StringBuilder strBuilder,
                    out ReceiptData receiptDataMerchant,
                    out ReceiptData receiptDataCardholder);

                transactionFinishedStringBuilder.Append(strBuilder);

                if (transactionFinishedEventArgs.GetValue(ParameterKeys.SignatureVerificationRequired, out string signatureVerification))
                {
                    try
                    {
                        signatureRequired = Convert.ToBoolean(signatureVerification);
                    }
                    catch (FormatException)
                    {
                        Console.WriteLine("Formatting Error With Signature Required");
                    }
                }

                if (transactionFinishedEventArgs.GetValue(ParameterKeys.Reference, out string referenceValue))
                {
                    reference = referenceValue;
                }

                Console.WriteLine(transactionFinishedStringBuilder);

                if (signatureRequired)
                {
                    Console.WriteLine("\n  Signature Verification Required For [ref={0}]\n", reference);
                }

                if (transactionFinishedEventArgs.GetValue(ParameterKeys.ErrorDescription, out string errorDescription))
                {
                    Console.WriteLine("ErrorDescription: {0}", errorDescription);
                }

                PrintReceiptData(receiptDataMerchant);
                PrintReceiptData(receiptDataCardholder);

                // 🔥 SET THE RESULT FOR HTTP LISTENER
                foreach (var param in transactionFinishedEventArgs.ToList())
                {
                    string value = param.Key.Equals(ParameterKeys.CardHashCollection)
                        ? FormatCardHashCollection(CardHash.ParseCardHashFromXml(param.Value))
                        : param.Value;

                    result[param.Key] = value;
                }
                if (TransactionCompletionSource != null && !TransactionCompletionSource.Task.IsCompleted)
                {
                    TransactionCompletionSource.SetResult(result);
                }
            }

            ResetTransaction();
            PrintConsoleCommands();
        }



        private void ChipDnaClientLibOnSignatureVerificationRequested(object sender, EventParameters signatureVerificationRequestedEventArgs)
        {
            var transactionFinishedStringBuilder = new StringBuilder();
            transactionFinishedStringBuilder.Append("Signature Verification Requested Event Parameters: ");
            string reference = null;
            bool signatureRequired = false;

            if (signatureVerificationRequestedEventArgs != null)
            {
                ExtractParametersForLogging(signatureVerificationRequestedEventArgs, out StringBuilder strBuilder, out ReceiptData receiptDataMerchant, out ReceiptData receiptDataCardholder);

                if (strBuilder.Length == 0)
                {
                    transactionFinishedStringBuilder.Append("[There are no set parameters for this event]");
                }
                else
                {
                    transactionFinishedStringBuilder.Append(strBuilder);
                }

                string signatureVerification;
                if (signatureVerificationRequestedEventArgs.GetValue(ParameterKeys.SignatureVerificationRequired, out signatureVerification))
                {
                    try
                    {
                        signatureRequired = Convert.ToBoolean(signatureVerification);
                    }
                    catch (FormatException) { Console.WriteLine("Formating Error With Signature Required"); }
                }
                string referenceValue;
                if (signatureVerificationRequestedEventArgs.GetValue(ParameterKeys.Reference, out referenceValue))
                {

                    reference = referenceValue;
                }

                Console.WriteLine(transactionFinishedStringBuilder);

                if (signatureRequired)
                {
                    Console.WriteLine("\n  Signature Verification Required For [ref={0}]\n", reference);
                }
                string errorDescription;
                if (signatureVerificationRequestedEventArgs.GetValue(ParameterKeys.ErrorDescription, out errorDescription))
                {
                    Console.WriteLine("ErrorDescription: {0}", errorDescription);
                }

                PrintReceiptData(receiptDataMerchant);
                PrintReceiptData(receiptDataCardholder);

                Console.WriteLine("*Waiting for Continue Signature Verification Command --> Press 'J' To Continue*");
            }
        }

        private void ChipDnaClientLibOnTransactionPause(object sender, EventParameters transactionPauseEventArgs)
        {
            Console.WriteLine("Transaction Pause Event Parameters: {0}{1} *Waiting For Continue Command --> Press 'L' To Continue*",
                transactionPauseEventArgs, "\n");
        }

        private void ChipDnaClientLibOnTransactionUpdate(object sender, EventParameters transactionUpdateEventArgs)
        {
            Console.WriteLine("Transaction Update Event Parameters: {0}", transactionUpdateEventArgs);
        }

        private void ChipDnaClientLibOnCardNotification(object sender, EventParameters cardNotificationEventArgs)
        {
            Console.WriteLine("Card Notification Event Parameters: {0}", cardNotificationEventArgs);
        }

        private void ChipDnaClientLibOnCardDetails(object sender, EventParameters cardDetailsEventArgs)
        {
            var stringBuilder = new StringBuilder();
            foreach (var param in cardDetailsEventArgs.ToList())
            {
                var value = (param.Key.Equals(ParameterKeys.CardHashCollection)) ? FormatCardHashCollection(CardHash.ParseCardHashFromXml(param.Value)) : param.Value;
                stringBuilder.Append(string.Format("[{0}, {1}]", param.Key, value));
            }
            Console.WriteLine("Card Details Event Parameters: {0}", stringBuilder);

            getCardDetailsInProgress = false;
            PrintConsoleCommands();
        }

        private void ChipDnaClientLibOnUpdateTransactionParametersFinishedEvent(object sender, EventParameters updateTransactionUpdateFinishedEvent)
        {
            Console.WriteLine("Update Transaction Parameters Finished Event: {0}", updateTransactionUpdateFinishedEvent);
        }

        private void ChipDnaClientLibOnPaymentDeviceAvailabilityChangeEvent(object sender, EventParameters connectionEventArgs)
        {
            var stringBuilder = new StringBuilder();
            connectionEventArgs.ToList().ForEach(param =>
            {
                if (param.Key.Equals(ParameterKeys.AvailabilityErrorInformation, StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrEmpty(param.Value))
                {
                    var paymentErrorList = ChipDnaStatus.ParseFromAvailabilityErrorInformation(param.Value);
                    stringBuilder.Append(string.Format("[{0}, ", param.Key));
                    foreach (var error in paymentErrorList)
                    {
                        stringBuilder.Append(error);
                    }
                    stringBuilder.Append("]");
                }
                else
                {
                    stringBuilder.Append(string.Format("[{0}]", param.ToString()));
                }
            });
            Console.WriteLine("{0}: {1}", "Payment Device Availability Change Event", stringBuilder);
        }

        private void ChipDnaClientLibOnTmsUpdateEvent(object sender, EventParameters tmsUpdateEventParameters)
        {
            Console.WriteLine("Tms Update Event Parameters: {0}", tmsUpdateEventParameters);
        }

        private void ChipDnaClientLibOnPassThruResponseEvent(object sender, EventParameters passThruResponseEventParameters)
        {
            Console.WriteLine("Pass Thru Response Event Parameters: {0}", passThruResponseEventParameters);
        }

        private void ChipDnaClientLibOnOpenPassThruSessionResponseEvent(object sender, EventParameters openPassThruSessionResponseEventParameters)
        {
            Console.WriteLine("Open Pass Thru Session Response Event Parameters: {0}", openPassThruSessionResponseEventParameters);
        }

        private void ChipDnaClientLibOnDccRateInformationEvent(object sender, EventParameters dccRateInformationEventParameters)
        {
            Console.WriteLine("Dcc Rate Information Event Parameters: {0}", dccRateInformationEventParameters);
        }

        private void ChipDnaClientLibOnErrorEvent(object sender, ClientHelperErrorEventArgs errorEventArgs)
        {
            PrintErrorEvent(errorEventArgs.RaisedException);
        }

        private void ChipDnaClientLibOnConnectAndConfigureEvent(object sender, EventParameters connectAndConfigureEventParameters)
        {
            Console.WriteLine("Connect And Configure Event Parameters: {0}", connectAndConfigureEventParameters);
        }

        private void ChipDnaClientLibOnConfigurationUpdateEvent(object sender, EventParameters configurationUpdateEventParameters)
        {
            Console.WriteLine("Configuration Update Event Parameters: {0}", configurationUpdateEventParameters);
        }

        private void ChipDnaClientLibOnRequestQueueRunCompletedEvent(object sender, EventParameters configurationUpdateEventParameters)
        {
            var requestQueueRunCompletedObject = RequestQueueRunCompletedObject.ParseFromResponse(configurationUpdateEventParameters);
            Console.WriteLine(requestQueueRunCompletedObject.ToString());
        }

        private string FormatCardHashCollection(List<CardHash> cardHashes)
        {
            var stringBuilder = new StringBuilder();
            cardHashes.ForEach(hash => stringBuilder.Append(hash));
            return stringBuilder.ToString();
        }

        private void ExtractParametersForLogging(EventParameters eventParameter, out StringBuilder strBuilder, out ReceiptData receiptDataMerchant, out ReceiptData receiptDataCardholder)
        {
            strBuilder = new StringBuilder();
            receiptDataMerchant = null;
            receiptDataCardholder = null;

            foreach (Parameter param in eventParameter.ToList())
            {
                if (param.Key.Equals(ParameterKeys.ReceiptDataMerchant))
                {
                    receiptDataMerchant = ReceiptData.GetReceiptDataFromXml(param.Value);
                } else if (param.Key.Equals(ParameterKeys.ReceiptDataCardholder))
                {
                    receiptDataCardholder = ReceiptData.GetReceiptDataFromXml(param.Value);
                }
                else
                {
                    var value = (param.Key.Equals(ParameterKeys.CardHashCollection)) ? FormatCardHashCollection(CardHash.ParseCardHashFromXml(param.Value)) : param.Value;
                    strBuilder.AppendFormat(null, " [{0}={1}] ", param.Key, value);
                }
            }
        }
    }
}
