/////////////////////////////////////////////////////////////////////////////
//
// Backport from wxWidgets-3.0-rc1
//
/////////////////////////////////////////////////////////////////////////////
// Name:        src/common/valnum.cpp
// Purpose:     Numeric validator classes implementation
// Author:      Vadim Zeitlin based on the submission of Fulvio Senore
// Created:     2010-11-06
// Copyright:   (c) 2010 wxWidgets team
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// ============================================================================
// Declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------


#include "valnum.h"

// For compilers that support precompilation, includes "wx.h".
#include <wx/wxprec.h>

#include <wx/setup.h> // for wxUSE_* macros

#include "AudacityMessageBox.h"
#include "Internat.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#if wxUSE_VALIDATORS && wxUSE_TEXTCTRL

#ifndef WX_PRECOMP
    #include <wx/textctrl.h>
    #include <wx/combobox.h>
#endif

#include <wx/clipbrd.h>
#include <wx/dataobj.h>

#include "numformatter.h"

// ============================================================================
// NumValidatorBase implementation
// ============================================================================

BEGIN_EVENT_TABLE(NumValidatorBase, wxValidator)
   EVT_CHAR(NumValidatorBase::OnChar)
   EVT_TEXT_PASTE(wxID_ANY, NumValidatorBase::OnPaste)
   EVT_KILL_FOCUS(NumValidatorBase::OnKillFocus)
END_EVENT_TABLE()

int NumValidatorBase::GetFormatFlags() const
{
   int flags = NumberFormatter::Style_None;
   if ( m_style & NumValidatorStyle::THOUSANDS_SEPARATOR )
      flags |= NumberFormatter::Style_WithThousandsSep;
   if ( m_style & NumValidatorStyle::NO_TRAILING_ZEROES )
      flags |= NumberFormatter::Style_NoTrailingZeroes;
   if ( m_style & NumValidatorStyle::ONE_TRAILING_ZERO )
      flags |= NumberFormatter::Style_OneTrailingZero;
   if ( m_style & NumValidatorStyle::TWO_TRAILING_ZEROES )
      flags |= NumberFormatter::Style_TwoTrailingZeroes;
   if ( m_style & NumValidatorStyle::THREE_TRAILING_ZEROES )
      flags |= NumberFormatter::Style_ThreeTrailingZeroes;

   return flags;
}

wxTextEntry *NumValidatorBase::GetTextEntry() const
{
#if wxUSE_TEXTCTRL
   if ( wxTextCtrl *text = wxDynamicCast(m_validatorWindow, wxTextCtrl) )
      return text;
#endif // wxUSE_TEXTCTRL

#if wxUSE_COMBOBOX
    if ( wxComboBox *combo = wxDynamicCast(m_validatorWindow, wxComboBox) )
        return combo;
#endif // wxUSE_COMBOBOX

   wxFAIL_MSG(wxT("Can only be used with wxTextCtrl or wxComboBox"));

   return NULL;
}

bool NumValidatorBase::Validate(wxWindow *parent)
{
   // If window is disabled, simply return
   if ( !m_validatorWindow->IsEnabled() )
      return true;

   TranslatableString errmsg;
   bool res = DoValidateNumber(&errmsg);

   if ( !res )
   {
      AudacityMessageBox(
         errmsg,
         XO("Validation error"),
         wxOK | wxICON_ERROR,
         parent);
      wxTextEntry *te = GetTextEntry();
      if ( te )
      {
         te->SelectAll();
         m_validatorWindow->SetFocus();
      }
      return false;
   }

   return true;
}

void
NumValidatorBase::GetCurrentValueAndInsertionPoint(wxString& val,
                                                   int& pos) const
{
   wxTextEntry * const control = GetTextEntry();
   if ( !control )
      return;

   val = control->GetValue();
   pos = control->GetInsertionPoint();

   long selFrom, selTo;
   control->GetSelection(&selFrom, &selTo);

   const long selLen = selTo - selFrom;
   if ( selLen )
   {
      // Remove selected text because pressing a key would make it disappear.
      val.erase(selFrom, selLen);

      // And adjust the insertion point to have correct position in the NEW
      // string.
      if ( pos > selFrom )
      {
         if ( pos >= selTo )
            pos -= selLen;
         else
            pos = selFrom;
      }
   }
}

bool NumValidatorBase::IsMinusOk(const wxString& val, int pos) const
{
   // Minus is only ever accepted in the beginning of the string.
   if ( pos != 0 )
      return false;

   // And then only if there is no existing minus sign there.
   if ( !val.empty() && val[0] == '-' )
      return false;

   return true;
}

void NumValidatorBase::OnChar(wxKeyEvent& event)
{
   // By default we just validate this key so don't prevent the normal
   // handling from taking place.
   event.Skip();

   if ( !m_validatorWindow )
      return;

#if wxUSE_UNICODE
   const int ch = event.GetUnicodeKey();
   const int c = event.GetKeyCode();
   if ( c > WXK_START )
   {
      // It's a character without any Unicode equivalent at all, e.g. cursor
      // arrow or function key, we never filter those.
      return;
   }
#else // !wxUSE_UNICODE
   const int ch = event.GetKeyCode();
   const int c = ch;
   if ( ch > WXK_DELETE )
   {
      // Not a character neither.
      return;
   }
#endif // wxUSE_UNICODE/!wxUSE_UNICODE

   // Space is an allowed thousands separator. But we don't allow user to type
   // it. We will add it at formatting time in OnKillFocus().
   if ( c < WXK_SPACE || c == WXK_DELETE )
   {
      // Allow ASCII control characters and Delete.
      return;
   }

   // Check if this character is allowed in the current state.
   wxString val;
   int pos;
   GetCurrentValueAndInsertionPoint(val, pos);

   if ( !IsCharOk(val, pos, ch) )
   {
      if ( !wxValidator::IsSilent() )
         wxBell();

      // Do not skip the event in this case, stop handling it here.
      event.Skip(false);
   }
}

void NumValidatorBase::OnPaste(wxClipboardTextEvent& event)
{
   event.Skip(false);

   wxTextEntry * const control = GetTextEntry();
   if ( !control )
   {
      return;
   }

   wxClipboardLocker cb;
//    if (!wxClipboard::Get()->IsSupported(wxDataFormat(wxDF_UNICODETEXT)))
   if (!wxClipboard::Get()->IsSupported(wxDF_UNICODETEXT))
   {
      return;
   }

   wxTextDataObject data;
   if (!wxClipboard::Get()->GetData( data ))
   {
      return;
   }

   wxString toPaste = data.GetText();
   wxString val;
   int pos;
   GetCurrentValueAndInsertionPoint(val, pos);

   for (size_t i = 0, cnt = toPaste.length(); i < cnt; i++)
   {
      const wxChar ch = toPaste[i];

      // Check if this character is allowed in the current state.
      if ( IsCharOk(val, pos, ch) )
      {
         val = GetValueAfterInsertingChar(val, pos++, ch);
      }
      else if ( !wxValidator::IsSilent() )
      {
               wxBell();
      }
   }

   // When we change the control value below, its "modified" status is reset
   // so we need to explicitly keep it marked as modified if it was so in the
   // first place.
   //
   // Notice that only wxTextCtrl (and not wxTextEntry) has
   // IsModified()/MarkDirty() methods hence the need for dynamic cast.
   wxTextCtrl * const text = wxDynamicCast(m_validatorWindow, wxTextCtrl);
   const bool wasModified = text ? text->IsModified() : false;

   // Use SetValue because effect still needs EVT_TEXT (bug 1357)
   control->SetValue(NormalizeString(val));

   if ( wasModified )
   {
      text->MarkDirty();
   }
}

void NumValidatorBase::OnKillFocus(wxFocusEvent& event)
{
   wxTextEntry * const control = GetTextEntry();
   if ( !control )
      return;

   // When we change the control value below, its "modified" status is reset
   // so we need to explicitly keep it marked as modified if it was so in the
   // first place.
   //
   // Notice that only wxTextCtrl (and not wxTextEntry) has
   // IsModified()/MarkDirty() methods hence the need for dynamic cast.
   wxTextCtrl * const text = wxDynamicCast(m_validatorWindow, wxTextCtrl);
   const bool wasModified = text ? text->IsModified() : false;

   control->ChangeValue(NormalizeString(control->GetValue()));

   if ( wasModified )
      text->MarkDirty();

   event.Skip();

//    Validate(text);
}

// ============================================================================
// IntegerValidatorBase implementation
// ============================================================================

wxString IntegerValidatorBase::ToString(LongestValueType value) const
{
   return NumberFormatter::ToString(value, GetFormatFlags());
}

bool
IntegerValidatorBase::FromString(const wxString& s, LongestValueType *value)
{
   return NumberFormatter::FromString(s, value);
}

bool
IntegerValidatorBase::IsCharOk(const wxString& val, int pos, wxChar ch) const
{
   // We may accept minus sign if we can represent negative numbers at all.
   if ( ch == '-' )
   {
      // Notice that entering '-' can make our value invalid, for example if
      // we're limited to -5..15 range and the current value is 12, then the
      // NEW value would be (invalid) -12. We consider it better to let the
      // user do this because perhaps he is going to press Delete key next to
      // make it -2 and forcing him to DELETE 1 first would be unnatural.
      //
      // TODO: It would be nice to indicate that the current control contents
      //       is invalid (if it's indeed going to be the case) once
      //       wxValidator supports doing this non-intrusively.
      return m_min < 0 && IsMinusOk(val, pos);
   }

   // A separator is accepted if the locale allow it, the other chars must be digits
   if ( ch < '0' || ch > '9' )
   {
      wxChar thousands;
      if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousands) )
      {
//            if (ch != thousands)
               return false;
      }
      else
      {
         return false;
      }
   }

   return true;
}

bool IntegerValidatorBase::DoValidateNumber(TranslatableString * errMsg) const
{
   wxTextEntry * const control = GetTextEntry();
   if ( !control )
      return false;

   wxString s(control->GetValue());
   wxChar thousandsSep;
   if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousandsSep) )
      s.Replace(wxString(thousandsSep), wxString());

   if ( s.empty() )
   {
      // Is blank, but allowed. Stop here
      if ( HasFlag(NumValidatorStyle::ZERO_AS_BLANK) )
      {
         return true;
      }
      // We can't do any check with an empty string
      else
      {
         *errMsg = XO("Empty value");
         return false;
      }
   }

   // Can it be converted to a value?
   LongestValueType value = 0;
   bool res = FromString(s, &value);
   if ( !res )
      *errMsg = XO("Malformed number");
   else
   {
      res = IsInRange(value);
      if ( !res )
         *errMsg = XO("Not in range %d to %d")
            .Format( (int) m_min, (int) m_max );
   }

   return res;
}

// ============================================================================
// FloatingPointValidatorBase implementation
// ============================================================================

wxString FloatingPointValidatorBase::ToString(LongestValueType value) const
{
   return NumberFormatter::ToString(value, m_precision, GetFormatFlags());
}

bool
FloatingPointValidatorBase::FromString(const wxString& s,
                                       LongestValueType *value)
{
   return NumberFormatter::FromString(s, value);
}

bool
FloatingPointValidatorBase::IsCharOk(const wxString& val,
                                       int pos,
                                       wxChar ch) const
{
   if ( ch == '-' )
   {
      // We may accept minus sign if we can represent negative numbers at all.
      if ( pos == 0 )
         return m_min < 0 && IsMinusOk(val, pos);
      // or for the exponent definition
      else if ( val[pos-1] != 'e' && val[pos-1] != 'E' )
         return false;

      return true;
   }
   else if ( ch == '+' )
   {
      if ( pos == 0 )
         return m_max >= 0;
      else if ( val[pos-1] != 'e' && val[pos-1] != 'E' )
         return false;

      return true;
   }

   const wxChar separator = NumberFormatter::GetDecimalSeparator();
   if ( ch == separator )
   {
      if ( val.find(separator) != wxString::npos )
      {
         // There is already a decimal separator, can't insert another one.
         return false;
      }

      // Prepending a separator before the sign isn't allowed.
      if ( pos == 0 && !val.empty() && ( val[0] == '-' || val[0] == '+' ) )
         return false;

      // Otherwise always accept it, adding a decimal separator doesn't
      // change the number value and, in particular, can't make it invalid.
      // OTOH the checks below might not pass because strings like "." or
      // "-." are not valid numbers so parsing them would fail, hence we need
      // to treat it specially here.
      return true;
   }

   // Must be a digit, an exponent or a thousands separator.
   if( ( ch < '0' || ch > '9' ) && ch != 'E' && ch != 'e' )
   {
      wxChar thousands;
      if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousands) )
      {
//            if (ch != thousands)
               return false;
      }
      else
      {
         return false;
      }
   }

   // Check the number of decimal digits in the final string
   wxString str(val);
   str.insert(pos, ch);
   return ValidatePrecision(str);
}

bool FloatingPointValidatorBase::DoValidateNumber(TranslatableString * errMsg) const
{
   wxTextEntry * const control = GetTextEntry();
   if ( !control )
      return false;

   wxString s(control->GetValue());
   wxChar thousandsSep;
   if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousandsSep) )
      s.Replace(wxString(thousandsSep), wxString());

   if ( s.empty() )
   {
      if ( HasFlag(NumValidatorStyle::ZERO_AS_BLANK) )
         return true; //Is blank, but allowed. Stop here
      else
      {
         *errMsg = XO("Empty value");
         return false; //We can't do any checks with an empty string
      }
   }

   LongestValueType value = 0;
   bool res = FromString(s, &value); // Can it be converted to a value?
   if ( !res )
      *errMsg = XO("Value overflow");
   else
   {
      res = ValidatePrecision(s);
      if ( !res )
         *errMsg = XO("Too many decimal digits");
      else
      {
         res = IsInRange(value);
         if ( !res )
         {
            wxString strMin = wxString::Format(wxT("%f"), m_min);
            wxString strMax = wxString::Format(wxT("%f"), m_max);
            NumberFormatter::RemoveTrailingZeroes(strMin);
            NumberFormatter::RemoveTrailingZeroes(strMax);

            if (m_minSet && m_maxSet)
            {
               *errMsg = XO("Value not in range: %s to %s")
                  .Format( strMin, strMax );
            }
            else if (m_minSet)
            {
               *errMsg = XO("Value must not be less than %s").Format( strMin );
            }
            else if (m_maxSet)
            {
               *errMsg = XO("Value must not be greater than %s")
                  .Format( strMax );
            }
         }
      }
   }

   return res;
}

bool FloatingPointValidatorBase::ValidatePrecision(const wxString& s) const
{
   size_t posSep = s.find(NumberFormatter::GetDecimalSeparator());
   if ( posSep == wxString::npos )
      posSep = s.length();

   // If user typed exponent 'e' the number of decimal digits is not
   // important at all. But we must know that 'e' position.
   // PRL:  I can't find anything in lconv or std::numpunct that describes
   // alternatives to e.  So just use a plain string literal.  Don't trouble
   // with i18n.
   size_t posExp = s.Lower().Find("e");
   if ( posExp == wxString::npos )
      posExp = s.length();

   // Return true if number has no more decimal digits than allowed
   return ( (int)(posExp - posSep) - 1 <= (int)m_precision );
}

double RoundValue(int precision, double value)
{
   return Internat::CompatibleToDouble( Internat::ToString(value, precision) );
}

#endif // wxUSE_VALIDATORS && wxUSE_TEXTCTRL
