using System;
using System.Globalization;
using System.Collections.Generic;

using CommonHelpers;

namespace ConsoleCalculator {

//**************************************************************************************************
static partial class Program {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/// <summary>
/// Calculation sequence routine.
/// </summary>
static void PerformCalculation()
{
   int operationIdx = -1;
   decimal accumResult, operand = 0;
   string operandStr = null;
   bool backwardMovement = false;

   var resultChain = new List<decimal>();

   // Calling operand input routine (primary operand):
   if (!InputOperand( _InputPrimaryOperand_Message_, ConsoleColor.Green,
                      ref operandStr, out accumResult ))
      return;
   resultChain.Add( accumResult );
   CON.PrintSplitter(true,true);

   for (;;)
   {
      operandStr = null;

      // Operation input dialog:
      CON.OutputArrowAlignedInputPrompt(
         _PressOperationKey_Message_, _MaxInputCaptionLength_,
         CON.ArrowedCaptionDashChar );
   ACCEPT_OPERATION_KEY:
      int opIdx =
         AcceptOperationKey();
      if (opIdx<0)
      {
         CON.Print();
         if (CON.PerformDialog( true,
            "Confirm calculation termination:",
            new string[]{ "Reset calculation" },
            new ConsoleKey[]{ ConsoleKey.Enter },
            "Continue calculation"
         ) == 0 )
            return;
         CON.Print();
         continue;
      }
      int
         opCount = Enum.GetValues( typeof(CalculationOperation) ).Length;
      bool
         repeatLastOperation = opIdx==opCount,
         oneOperationBack = opIdx==opCount+1;
      if ( repeatLastOperation && operationIdx<0 ||
           oneOperationBack && resultChain.Count<=1 )
      {
         HSound.PlayDisabled();
         goto ACCEPT_OPERATION_KEY;
      }
      if (oneOperationBack)
      {
         CON.PrintExpressive(_OperationCaptions_[opIdx]);
         goto PROCEED_RESULT;
      }
      if (!repeatLastOperation)
         operationIdx = opIdx;
      CON.PrintExpressive(_OperationCaptions_[operationIdx]);

      bool binaryOperation =
         (CalculationOperation)operationIdx != CalculationOperation.Negate;

      if (binaryOperation && repeatLastOperation)
      {
         CON.OutputArrowAlignedInputPrompt(
            _InputOperand_Message_, _MaxInputCaptionLength_, '-' );
         CON.Print(
            operand.ToString("G",NumberFormatInfo.InvariantInfo) );
         goto PERFORM_OPERATION;
      }

   INPUT_OPERAND:
      // Calling operand input routine (next operand):
      if ( binaryOperation &&
           !InputOperand(_InputOperand_Message_,ref operandStr,out operand) )
      {
         PrintRedefineLastOperation();
         operationIdx = -1;
         continue;
      }

   PERFORM_OPERATION:
      // Performing operation (unary or binary):
      try
      {
         switch ((CalculationOperation)operationIdx)
         {
            case CalculationOperation.Negate  : accumResult =- accumResult;   break;
            case CalculationOperation.Add     : accumResult += operand;       break;
            case CalculationOperation.Subtract: accumResult -= operand;       break;
            case CalculationOperation.Multiply: accumResult *= operand;       break;
            case CalculationOperation.Divide  : accumResult /= operand;       break;
            case CalculationOperation.Power:
               accumResult = (decimal)Math.Pow( (double)accumResult, (double)operand );
               break;
            default:
               throw HAssertionFailure.DEAD_BRANCH();
         }
      }
      catch (ArithmeticException e)
      {
         var errorMessage = string.Format(
            "Can't perform the operation: {0}",
            e.Message );
         if (!binaryOperation || repeatLastOperation)
         {
            ReportErrorRedefineOperation( errorMessage );
            operationIdx = -1;
            continue;
         }
         ReportErrorRedefineOperand( errorMessage );
         goto INPUT_OPERAND;
      }

   PROCEED_RESULT:
      // Proceeding result:
      if (oneOperationBack)
      {
         resultChain.RemoveAt( resultChain.Count-1 );
         accumResult = resultChain[ resultChain.Count-1 ];
         if (operationIdx!=-1)
            CON.PrintShallowSplitter(true,true);
         operationIdx = -1;
         backwardMovement = true;
         HSound.Play( Sounds.Back );
      }
      else
      {
         if (backwardMovement)
            CON.PrintShallowSplitter(true,true);
         resultChain.Add( accumResult );
         backwardMovement = false;
         HSound.PlaySuccess();
      }

      // Displaying successful result:
      CON.OutputArrowAlignedCaption(
         string.Format("Result #{0}",resultChain.Count-1),
         CON.SuccessColor, _MaxInputCaptionLength_,
         '-', null, ConsoleColor.DarkGray );
      CON.PrintExclamation(accumResult);

      if (oneOperationBack && resultChain.Count==1)
         CON.PrintSplitter(true);

      CON.Print();
   } // calculation
}

//==================================================================================================

/// <summary>
/// Simplified operand input routine (default caption color).
/// </summary>
static bool InputOperand(string caption,[HOptional] ref string strOperand,out decimal operand)
{
   return InputOperand(caption,CON.InputCaptionColor,ref strOperand,out operand);
}

/// <summary>
/// Operand input routine (decimal number).
/// </summary>
static bool InputOperand( string caption, ConsoleColor? captionColor,
                          [HOptional] ref string operandString, out decimal operand )
{
LOOP:
   // Performing string input:
   if (!CON.InputStringModify( caption, captionColor, _MaxInputCaptionLength_,
                               ref operandString, HConsoleInputOptions.RequireNonEmpty,
                               ValidateInput ))
   {
      operand = 0; return false;
   }

   // Parsing the string as decimal value (culture invariant):
   HParseValueException e;

   if (!HDecimal.TryParseInvariant(operandString,out operand,out e))
   {
      ReportErrorRedefineOperand( e );
      goto LOOP;
   }

   return true;
}

static bool ValidateInput(string inputStr)
{
   foreach (char ch in inputStr)
      if (!(HOps.IsBetween(ch,'0','9') || HOps.IsOneOf(ch,'-','.')))
         return false;
   return true;
}

//--------------------------------------------------------------------------------------------------

/// <summary>
/// Operation input routine.
/// </summary>
static int AcceptOperationKey()
{
   int idx = CON.AcceptInputKey
   (
      HArray.Append( _OperationKeyArray_     , ConsoleKey.Escape   ),
      HArray.Append( _OperationModifierArray_, (ConsoleModifiers)0 )
   );
   if (idx==_OperationModifierArray_.Length)
      return -1;
   return
      idx % _MainOperationKeyArray_.Length;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // Program
//**************************************************************************************************

} // ConsoleCalculator
