//------------------------------------------------------------------------------
// <copyright file=”*.cs” company=”EhLib Team”>
//     Copyright (c) 2017 Dmitry V. Bolshakov   
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using System.Globalization;

namespace EhLib
{

  internal enum CalcState
  {
    First, Valid, Error
  };

  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Scope = "type")]
  [ToolboxItem(false)]
  public partial class CalculatorControl : UserControl
  {

    private const string N0Str = "0";
    private const string N1Str = "1";
    private const string N2Str = "2";
    private const string N3Str = "3";
    private const string N4Str = "4";
    private const string N5Str = "5";
    private const string N6Str = "6";
    private const string N7Str = "7";
    private const string N8Str = "8";
    private const string N9Str = "9";

    private const string EmptyString = "";
    private const string StringPoint = ".";
    private const string StringBackSpace = "<-";
    private const string StringEqualSign = "=";
    private const string StringOk = "Ok";


    private const string DivideByZeroText = "Cannot divide by zero";
    private const string InvalidOperationText = "Invalid operation";

    #region Privates
    private decimal _oldValue;
    private decimal _value;
    //private bool forceDecimalSeparator = false;
    private char binOperator = ' ';
    private bool arithSignJustPressed;
    private EventHandler okButtonPressed;
    private CalcState calcState = CalcState.First;
    private string errorText = EmptyString; //""
    private string valueText = N0Str; //"0"
    #endregion Privates

    #region constructor
    public CalculatorControl()
    {
      InitializeComponent();
    }
    #endregion constructor

    #region Properties
    public decimal Value
    {
      get
      {
        return _value;
      }
      set
      {
        if (value != this._value)
        {
          this._value = Normalize(value);
          UpdateTextFromValue();
          //UpdateForceDecimalSeparator();
          //calculatorBox1.Invalidate();
        }
      }
    }

    public string ValueAsString
    {
      get
      {
        if (calcState == CalcState.Error)
        {
          return errorText;
        }
        else
        {
          //string result = Value.ToString("#,0.####################");
          //if (ForceDecimalSeparator)
          //  result = result + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
          return valueText;
        }
      }

      internal set
      {
        if (value != this.valueText)
        {
          this.valueText = value;
          calculatorBox1.Invalidate();
        }
      }
    }

    internal char BinOperator
    {
      get
      {
        return binOperator;
      }
    }

    public bool ReadOnly
    { get; set; }

    internal string DecimalSeparator
    {
      get
      {
        return CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
      }
        
    }

    public event EventHandler OkButtonPressed
    {
      add
      {
        okButtonPressed += value;
      }

      remove
      {
        if (okButtonPressed != null)
          okButtonPressed -= value;
      }
    }
    #endregion Properties

    #region Methods
    private void CalculatorBox1_KeyPress(object sender, KeyPressEventArgs e)
    {
      if ( (e.KeyChar.ToString() == DecimalSeparator) &&
           !ValueHaveDecimalPart() )
      {
        ProcessKeyData(StringPoint);
      }
      else
      {
        switch (e.KeyChar)
        {
          case (char)Keys.D0:
          case (char)Keys.D1:
          case (char)Keys.D2:
          case (char)Keys.D3:
          case (char)Keys.D4:
          case (char)Keys.D5:
          case (char)Keys.D6:
          case (char)Keys.D7:
          case (char)Keys.D8:
          case (char)Keys.D9:

          case '+':
          case '-':
          case '/':
          case '*':
            {
              ProcessKeyData(e.KeyChar.ToString());
              break;
            }

          case (char)Keys.Back:
            {
              ProcessKeyData(StringBackSpace);
              break;
            }
        }
      }
    }

    private void CalculatorBox1_KeyDown(object sender, KeyEventArgs e)
    {
      switch (e.KeyCode)
      {
        case Keys.Delete:
          {
            Value = 0;
            break;
          }
        case Keys.Enter:
          {
            ProcessKeyData(beOk.Text);
            break;
          }
      }
    }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Windows.Forms.MessageBox.Show(System.String,System.String,System.Windows.Forms.MessageBoxButtons,System.Windows.Forms.MessageBoxIcon,System.Windows.Forms.MessageBoxDefaultButton)")]
    private void MakeCalculation()
    {
      switch (BinOperator)
      {
        case '+':
          {
            Value = _oldValue + Value;
            break;
          }
        case '-':
          {
            Value = _oldValue - Value;
            break;
          }
        case '/':
          {
            if (_value == 0)
              MessageBox.Show(DivideByZeroText,
                              InvalidOperationText, 
                              MessageBoxButtons.OK, 
                              MessageBoxIcon.Error, 
                              MessageBoxDefaultButton.Button1);
            else
              Value = _oldValue / Value;
            break;
          }
        case '*':
          {
            Value = _oldValue * Value;
            break;
          }
      }
      binOperator = ' ';
    }

    private void ProcessKeyData(string keyData)
    {
      switch (keyData)
      {
        case N0Str:
        case N1Str:
        case N2Str:
        case N3Str:
        case N4Str:
        case N5Str:
        case N6Str:
        case N7Str:
        case N8Str:
        case N9Str:
          {
            if (arithSignJustPressed)
            {
              _oldValue = Value;
              Value = 0;
            }
            CheckFirst();
            string s = ValueAsString + keyData;
            if (CheckTextValue(s))
            {
              if (ValueAsString.Contains(DecimalSeparator))
              {
                ValueAsString = s;
                UpdateValueFromText();
              }
              else
              {
                Value = decimal.Parse(s);
              }
            }
            break;
          }

        case "<-":
          {
            CheckFirst();
            string s = ValueAsString;
            s = s.Remove(s.Length - 1);
            if (String.IsNullOrEmpty(s))
              Value = 0;
            else
              ValueAsString = s;

            break;
          }

        case "+":
        case "-":
        case "/":
        case "*":
          {
            if (calcState != CalcState.Error)
            {
              if (!arithSignJustPressed)
                MakeCalculation();
              arithSignJustPressed = true;
              binOperator = keyData[0];
              calcState = CalcState.First;
            }
            break;
          }

        case "+/-":
          {
            if (calcState != CalcState.Error)
            {
              Value = -Value;
              calcState = CalcState.First;
            }
            break;
          }

        case "sqrt":
          {
            if (calcState != CalcState.Error)
            {
              calcState = CalcState.First;
              if (Value < 0)
                Error();
              else
                Value = (decimal)Math.Sqrt((double)Value);
            }
            break;
          }

        case "%":
          {
            Value = _oldValue / 100 * Value;
            calcState = CalcState.First;
            break;
          }

        case "1/x":
          {
            if (calcState != CalcState.Error)
            {
              calcState = CalcState.First;
              if (Value == 0)
                Error();
              else
                Value = 1 / Value;
            }
            break;
          }

        case ".":
          {
            CheckFirst();
            if (arithSignJustPressed)
            {
              _oldValue = Value;
              Value = 0;
            }
            string newText = ValueAsString + DecimalSeparator;
            if (CheckTextValue(newText))
              ValueAsString = newText;

            break;
          }

        case "=":
          {
            if (BinOperator != ' ')
            {
              if (arithSignJustPressed)
                _oldValue = Value;
              MakeCalculation();    
            }
            break;
          }

        case "Ok":
          {
            if (okButtonPressed != null)
              okButtonPressed(this, EventArgs.Empty);
            break;
          }

      }

      if (!("+-/*".Contains(keyData)))
        arithSignJustPressed = false;
      UpdateButtonsState();
    }

    private void Error()
    {
      calcState = CalcState.Error;
      errorText = "Error";
      calculatorBox1.Invalidate();
    }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Windows.Forms.Control.set_Text(System.String)")]
    private void UpdateButtonsState()
    {
      if ((_oldValue != 0) && ("+-/*".Contains(binOperator)))
        beOk.Text = StringEqualSign;
      else
        beOk.Text = StringOk;
    }

    private bool CheckTextValue(string s)
    {
      decimal v;
      return decimal.TryParse(s, out v);
    }

    private bool ValueHaveDecimalPart()
    {
      if ((Value - Math.Truncate(Value) != 0))
        return true;
      else
        return false;
    }

    private void UpdateTextFromValue()
    {
      ValueAsString = Value.ToString("#,0.####################");
    }

    private void UpdateValueFromText()
    {
      _value = Normalize(decimal.Parse(ValueAsString));
    }

    private void Button_Click(object sender, EventArgs e)
    {
      string keyData = ((Button) sender).Text;
      switch (keyData)
      {
        case "0":
        case "1":
        case "2":
        case "3":
        case "4":
        case "5":
        case "6":
        case "7":
        case "8":
        case "9":

        case "+":
        case "-":
        case "/":
        case "*":

        case "+/-":
        case ".":

        case "sqrt":
        case "%":
        case "1/x":

        case "<-":

        case "=":
        case "Ok":
          {
            ProcessKeyData(keyData);
            break;
          }

        case "C":
          {
            ClearAll();
            break;
          }
      }
    }

    private void ClearAll()
    {
      _oldValue = 0;
      Value = 0;
      binOperator = ' ';
      arithSignJustPressed = false;
      calculatorBox1.Invalidate();
    }

    private void CheckFirst()
    {
      if ((calcState == CalcState.First) || (calcState == CalcState.Error))
      {
        calcState = CalcState.Valid;
        Value = 0;
      }
    }

    public static decimal Normalize(decimal value)
    {
      return value / 1.000000000000000000000000000000000m;
    }
    #endregion Methods
  }

  [ToolboxItem(false)]
  public class CalculatorBox : Control
  {
    public CalculatorBox()
    {
    }

    protected override void OnPaint(PaintEventArgs e)
    {
      string text;
      Rectangle textRect;

      base.OnPaint(e);
      Font drawFont;
      bool drawFontDisplose = false;

      if (Focused)
      {
        drawFont = new Font(Font, FontStyle.Bold);
        drawFontDisplose = true;
      }
      else
        drawFont = Font;

      using (var sb = new SolidBrush(SystemColors.Window))
      {
        e.Graphics.FillRectangle(sb, ClientRectangle);
      }

      CalculatorControl parent = Parent as CalculatorControl;
      if (parent != null)
      {
        text = parent.ValueAsString;
        textRect = ClientRectangle;
        textRect.Inflate(-4, -2);
        EhLibUtils.DrawText(e.Graphics, text, drawFont, textRect, SystemColors.WindowText,
          HorizontalAlignment.Right, VerticalAlignment.Center, TextFormatFlagsEh.None);
      }

      if (drawFontDisplose)
        drawFont.Dispose();
    }

    protected override void OnGotFocus(EventArgs e)
    {
      base.OnGotFocus(e);
      Invalidate();
    }

    protected override void OnLostFocus(EventArgs e)
    {
      base.OnLostFocus(e);
      Invalidate();
    }

  }

}
