///////////////////////////////////////////////////////////////////////////////
// Name:        tests/controls/textentrytest.cpp
// Purpose:     TestEntryTestCase implementation
// Author:      Vadim Zeitlin
// Created:     2008-09-19 (extracted from textctrltest.cpp)
// Copyright:   (c) 2007, 2008 Vadim Zeitlin <vadim@wxwidgets.org>
///////////////////////////////////////////////////////////////////////////////

#include "testprec.h"

#ifndef WX_PRECOMP
    #include "wx/app.h"
    #include "wx/dialog.h"
    #include "wx/event.h"
    #include "wx/sizer.h"
    #include "wx/textctrl.h"
    #include "wx/textentry.h"
    #include "wx/timer.h"
    #include "wx/window.h"
#endif // WX_PRECOMP

#include "textentrytest.h"
#include "testableframe.h"

#include "wx/uiaction.h"

#include <memory>

void TextEntryTestCase::SetValue()
{
    wxTextEntry * const entry = GetTestEntry();

    CPPUNIT_ASSERT( entry->IsEmpty() );

    entry->SetValue("foo");
    CPPUNIT_ASSERT_EQUAL( "foo", entry->GetValue() );

    entry->SetValue("");
    CPPUNIT_ASSERT( entry->IsEmpty() );

    entry->SetValue("hi");
    CPPUNIT_ASSERT_EQUAL( "hi", entry->GetValue() );

    entry->SetValue("bye");
    CPPUNIT_ASSERT_EQUAL( "bye", entry->GetValue() );
}

void TextEntryTestCase::TextChangeEvents()
{
    EventCounter updated(GetTestWindow(), wxEVT_TEXT);

    wxTextEntry * const entry = GetTestEntry();

    // notice that SetValue() generates an event even if the text didn't change
    entry->SetValue("");
    CPPUNIT_ASSERT_EQUAL( 1, updated.GetCount() );
    updated.Clear();

    entry->SetValue("foo");
    CPPUNIT_ASSERT_EQUAL( 1, updated.GetCount() );
    updated.Clear();

    entry->SetValue("foo");
    CPPUNIT_ASSERT_EQUAL( 1, updated.GetCount() );
    updated.Clear();

    entry->SetValue("");
    CPPUNIT_ASSERT_EQUAL( 1, updated.GetCount() );
    updated.Clear();

    entry->ChangeValue("bar");
    CPPUNIT_ASSERT_EQUAL( 0, updated.GetCount() );

    entry->AppendText("bar");
    CPPUNIT_ASSERT_EQUAL( 1, updated.GetCount() );
    updated.Clear();

    entry->Replace(3, 6, "baz");
    CPPUNIT_ASSERT_EQUAL( 1, updated.GetCount() );
    updated.Clear();

    entry->Remove(0, 3);
    CPPUNIT_ASSERT_EQUAL( 1, updated.GetCount() );
    updated.Clear();

    entry->WriteText("foo");
    CPPUNIT_ASSERT_EQUAL( 1, updated.GetCount() );
    updated.Clear();

    entry->Clear();
    CPPUNIT_ASSERT_EQUAL( 1, updated.GetCount() );
    updated.Clear();

    entry->ChangeValue("");
    CPPUNIT_ASSERT_EQUAL( 0, updated.GetCount() );
    updated.Clear();

    entry->ChangeValue("non-empty");
    CPPUNIT_ASSERT_EQUAL( 0, updated.GetCount() );
    updated.Clear();

    entry->ChangeValue("");
    CPPUNIT_ASSERT_EQUAL( 0, updated.GetCount() );
    updated.Clear();
}

void TextEntryTestCase::CheckStringSelection(const char *sel)
{
    CPPUNIT_ASSERT_EQUAL( sel, GetTestEntry()->GetStringSelection() );
}

void TextEntryTestCase::AssertSelection(int from, int to, const char *sel)
{
    wxTextEntry * const entry = GetTestEntry();

    CPPUNIT_ASSERT( entry->HasSelection() );

    long fromReal,
         toReal;
    entry->GetSelection(&fromReal, &toReal);
    CPPUNIT_ASSERT_EQUAL( from, fromReal );
    CPPUNIT_ASSERT_EQUAL( to, toReal );

    CPPUNIT_ASSERT_EQUAL( from, entry->GetInsertionPoint() );

    CheckStringSelection(sel);
}

void TextEntryTestCase::Selection()
{
    wxTextEntry * const entry = GetTestEntry();

    entry->SetValue("0123456789");

    entry->SetSelection(2, 4);
    AssertSelection(2, 4, "23"); // not "234"!

    entry->SetSelection(3, -1);
    AssertSelection(3, 10, "3456789");

    entry->SelectAll();
    AssertSelection(0, 10, "0123456789");

    entry->SetSelection(0, 0);
    CPPUNIT_ASSERT( !entry->HasSelection() );
}

void TextEntryTestCase::InsertionPoint()
{
    wxTextEntry * const entry = GetTestEntry();

    CPPUNIT_ASSERT_EQUAL( 0, entry->GetLastPosition() );
    CPPUNIT_ASSERT_EQUAL( 0, entry->GetInsertionPoint() );

    entry->SetValue("0"); // should put the insertion point in front
    CPPUNIT_ASSERT_EQUAL( 1, entry->GetLastPosition() );
    CPPUNIT_ASSERT_EQUAL( 0, entry->GetInsertionPoint() );

    entry->AppendText("12"); // should update the insertion point position
    CPPUNIT_ASSERT_EQUAL( 3, entry->GetLastPosition() );
    CPPUNIT_ASSERT_EQUAL( 3, entry->GetInsertionPoint() );

    entry->SetInsertionPoint(1);
    CPPUNIT_ASSERT_EQUAL( 3, entry->GetLastPosition() );
    CPPUNIT_ASSERT_EQUAL( 1, entry->GetInsertionPoint() );

    entry->SetValue("012"); // shouldn't change the position if no real change
    CPPUNIT_ASSERT_EQUAL( 1, entry->GetInsertionPoint() );

    entry->ChangeValue("012"); // same as for SetValue()
    CPPUNIT_ASSERT_EQUAL( 1, entry->GetInsertionPoint() );

    entry->SetInsertionPointEnd();
    CPPUNIT_ASSERT_EQUAL( 3, entry->GetInsertionPoint() );

    entry->SetInsertionPoint(0);
    entry->WriteText("-"); // should move it after the written text
    CPPUNIT_ASSERT_EQUAL( 4, entry->GetLastPosition() );
    CPPUNIT_ASSERT_EQUAL( 1, entry->GetInsertionPoint() );

    entry->SetValue("something different"); // should still reset the caret
    CPPUNIT_ASSERT_EQUAL( 0, entry->GetInsertionPoint() );
}

void TextEntryTestCase::Replace()
{
    wxTextEntry * const entry = GetTestEntry();

    entry->SetValue("Hello replace!"
                    "0123456789012");
    entry->SetInsertionPoint(0);

    entry->Replace(6, 13, "changed");

    CPPUNIT_ASSERT_EQUAL("Hello changed!"
                         "0123456789012",
                         entry->GetValue());
    CPPUNIT_ASSERT_EQUAL(13, entry->GetInsertionPoint());

    entry->Replace(13, -1, "");
    CPPUNIT_ASSERT_EQUAL("Hello changed", entry->GetValue());
    CPPUNIT_ASSERT_EQUAL(13, entry->GetInsertionPoint());

    entry->Replace(0, 6, "Un");
    CPPUNIT_ASSERT_EQUAL("Unchanged", entry->GetValue());
    CPPUNIT_ASSERT_EQUAL(2, entry->GetInsertionPoint());
}

void TextEntryTestCase::WriteText()
{
    wxTextEntry * const entry = GetTestEntry();

    entry->SetValue("foo");
    entry->SetInsertionPoint(3);
    entry->WriteText("bar");
    CPPUNIT_ASSERT_EQUAL( "foobar", entry->GetValue() );

    entry->SetValue("foo");
    entry->SetInsertionPoint(0);
    entry->WriteText("bar");
    CPPUNIT_ASSERT_EQUAL( "barfoo", entry->GetValue() );

    entry->SetValue("abxxxhi");
    entry->SetSelection(2, 5);
    entry->WriteText("cdefg");
    CPPUNIT_ASSERT_EQUAL( "abcdefghi", entry->GetValue() );
    CPPUNIT_ASSERT_EQUAL( 7, entry->GetInsertionPoint() );
    CPPUNIT_ASSERT_EQUAL( false, entry->HasSelection() );
}

#if wxUSE_UIACTIONSIMULATOR

class TextEventHandler
{
public:
    explicit TextEventHandler(wxWindow* win)
        : m_win(win)
    {
        m_win->Bind(wxEVT_TEXT, &TextEventHandler::OnText, this);
    }

    ~TextEventHandler()
    {
        m_win->Unbind(wxEVT_TEXT, &TextEventHandler::OnText, this);
    }

    const wxString& GetLastString() const
    {
        return m_string;
    }

private:
    void OnText(wxCommandEvent& event)
    {
        m_string = event.GetString();
    }

    wxWindow* const m_win;

    wxString m_string;
};

void TextEntryTestCase::Editable()
{
    wxTextEntry * const entry = GetTestEntry();
    wxWindow * const window = GetTestWindow();

    EventCounter updated(window, wxEVT_TEXT);

    window->SetFocus();
    wxYield();

#ifdef __WXGTK__
    // For some reason, wxBitmapComboBox doesn't appear on the screen without
    // this (due to wxTLW size hacks perhaps?). It would be nice to avoid doing
    // this, but without this hack the test often (although not always) fails.
    wxMilliSleep(50);
#endif // __WGTK__

    // Check that we get the expected number of events.
    wxUIActionSimulator sim;
    sim.Text("abcdef");
    wxYield();

    CPPUNIT_ASSERT_EQUAL("abcdef", entry->GetValue());
    CPPUNIT_ASSERT_EQUAL(6, updated.GetCount());

    wxYield();

    // And that the event carries the right value.
    TextEventHandler handler(window);

    sim.Text("g");
    wxYield();

    CPPUNIT_ASSERT_EQUAL("abcdefg", handler.GetLastString());

    // ... even if we generate the event programmatically and whether it uses
    // the same value as the control has right now
    entry->SetValue("abcdefg");
    CPPUNIT_ASSERT_EQUAL("abcdefg", handler.GetLastString());

    // ... or not
    entry->SetValue("abcdef");
    CPPUNIT_ASSERT_EQUAL("abcdef", handler.GetLastString());

    // Check that making the control not editable does indeed prevent it from
    // being edited.
    updated.Clear();

    entry->SetEditable(false);
    sim.Text("gh");
    wxYield();

    CPPUNIT_ASSERT_EQUAL("abcdef", entry->GetValue());
    CPPUNIT_ASSERT_EQUAL(0, updated.GetCount());
}

#endif // wxUSE_UIACTIONSIMULATOR

void TextEntryTestCase::Hint()
{
    GetTestEntry()->SetHint("This is a hint");
    CPPUNIT_ASSERT_EQUAL("", GetTestEntry()->GetValue());
}

void TextEntryTestCase::CopyPaste()
{
#ifndef __WXOSX__
    wxTextEntry * const entry = GetTestEntry();

    entry->AppendText("sometext");
    entry->SelectAll();

    if(entry->CanCopy() && entry->CanPaste())
    {
        entry->Copy();
        entry->Clear();
        CPPUNIT_ASSERT(entry->IsEmpty());

        wxYield();

        entry->Paste();
        CPPUNIT_ASSERT_EQUAL("sometext", entry->GetValue());
    }
#endif
}

void TextEntryTestCase::UndoRedo()
{
    wxTextEntry * const entry = GetTestEntry();

    entry->AppendText("sometext");

    if(entry->CanUndo())
    {
        entry->Undo();
        CPPUNIT_ASSERT(entry->IsEmpty());

        if(entry->CanRedo())
        {
            entry->Redo();
            CPPUNIT_ASSERT_EQUAL("sometext", entry->GetValue());
        }
    }
}

#if wxUSE_UIACTIONSIMULATOR

namespace
{

enum ProcessEnter
{
    ProcessEnter_No,
    ProcessEnter_ButSkip,
    ProcessEnter_WithoutSkipping
};

class TestDialog : public wxDialog
{
public:
    explicit TestDialog(const TextLikeControlCreator& controlCreator,
                        ProcessEnter processEnter)
        : wxDialog(wxTheApp->GetTopWindow(), wxID_ANY, "Test dialog"),
          m_control
          (
              controlCreator.Create
              (
               this,
               processEnter == ProcessEnter_No ? 0 : wxTE_PROCESS_ENTER
              )
          ),
          m_processEnter(processEnter),
          m_gotEnter(false)
    {
        wxSizer* const sizer = new wxBoxSizer(wxVERTICAL);
        sizer->Add(m_control, wxSizerFlags().Expand());
        sizer->Add(CreateStdDialogButtonSizer(wxOK));
        SetSizerAndFit(sizer);

        CallAfter(&TestDialog::SimulateEnter);

        m_timer.Bind(wxEVT_TIMER, &TestDialog::OnTimeOut, this);
        m_timer.StartOnce(2000);
    }

    bool GotEnter() const { return m_gotEnter; }

private:
    void OnTextEnter(wxCommandEvent& e)
    {
        m_gotEnter = true;

        switch ( m_processEnter )
        {
            case ProcessEnter_No:
                FAIL("Shouldn't be getting wxEVT_TEXT_ENTER at all");
                break;

            case ProcessEnter_ButSkip:
                e.Skip();
                break;

            case ProcessEnter_WithoutSkipping:
                // Close the dialog with a different exit code than what
                // pressing the OK button would have generated.
                EndModal(wxID_APPLY);
                break;
        }
    }

    void OnText(wxCommandEvent& WXUNUSED(e))
    {
        // This should only happen for the multiline text controls.
        switch ( m_processEnter )
        {
            case ProcessEnter_No:
            case ProcessEnter_ButSkip:
                // We consider that the text succeeded, but in a different way,
                // so use a different ID to be able to distinguish between this
                // scenario and Enter activating the default button.
                EndModal(wxID_CLOSE);
                break;

            case ProcessEnter_WithoutSkipping:
                FAIL("Shouldn't be getting wxEVT_TEXT if handled");
                break;
        }
    }

    void OnTimeOut(wxTimerEvent&)
    {
        EndModal(wxID_CANCEL);
    }

    void SimulateEnter()
    {
        wxUIActionSimulator sim;

        // Calling SetFocus() is somehow not enough to give the focus to this
        // window when running this test with wxGTK, apparently because the
        // dialog itself needs to be raised to the front first, so simulate a
        // click doing this.
        sim.MouseMove(m_control->GetScreenPosition() + wxPoint(5, 5));
        wxYield();
        sim.MouseClick();
        wxYield();

        // Note that clicking it is still not enough to give it focus with
        // wxGTK either, so we still need to call SetFocus() nevertheless: but
        // now it works.
        m_control->SetFocus();

        sim.Char(WXK_RETURN);
    }

    wxControl* const m_control;
    const ProcessEnter m_processEnter;
    wxTimer m_timer;
    bool m_gotEnter;

    wxDECLARE_EVENT_TABLE();
};

// Note that we must use event table macros here instead of Bind() because
// binding wxEVT_TEXT_ENTER handler for a control without wxTE_PROCESS_ENTER
// style would fail with an assertion failure, due to wx helpfully complaining
// about it.
wxBEGIN_EVENT_TABLE(TestDialog, wxDialog)
    EVT_TEXT(wxID_ANY, TestDialog::OnText)
    EVT_TEXT_ENTER(wxID_ANY, TestDialog::OnTextEnter)
wxEND_EVENT_TABLE()

} // anonymous namespace

void TestProcessEnter(const TextLikeControlCreator& controlCreator)
{
    if ( !EnableUITests() )
    {
        WARN("Skipping wxTE_PROCESS_ENTER tests: wxUIActionSimulator use disabled");
        return;
    }

    SECTION("Without wxTE_PROCESS_ENTER")
    {
        TestDialog dlg(controlCreator, ProcessEnter_No);
        REQUIRE( dlg.ShowModal() == wxID_OK );
        CHECK( !dlg.GotEnter() );
    }

    SECTION("With wxTE_PROCESS_ENTER but skipping")
    {
        TestDialog dlgProcessEnter(controlCreator, ProcessEnter_ButSkip);
        REQUIRE( dlgProcessEnter.ShowModal() == wxID_OK );
        CHECK( dlgProcessEnter.GotEnter() );
    }

    SECTION("With wxTE_PROCESS_ENTER without skipping")
    {
        TestDialog dlgProcessEnter(controlCreator, ProcessEnter_WithoutSkipping);
        REQUIRE( dlgProcessEnter.ShowModal() == wxID_APPLY );
        CHECK( dlgProcessEnter.GotEnter() );
    }

    SECTION("Without wxTE_PROCESS_ENTER but with wxTE_MULTILINE")
    {
        std::unique_ptr<TextLikeControlCreator>
            multiLineCreator(controlCreator.CloneAsMultiLine());
        if ( !multiLineCreator )
            return;

        TestDialog dlg(*multiLineCreator, ProcessEnter_No);
        REQUIRE( dlg.ShowModal() == wxID_CLOSE );
        CHECK( !dlg.GotEnter() );
    }

    SECTION("With wxTE_PROCESS_ENTER and wxTE_MULTILINE but skipping")
    {
        std::unique_ptr<TextLikeControlCreator>
            multiLineCreator(controlCreator.CloneAsMultiLine());
        if ( !multiLineCreator )
            return;

        TestDialog dlg(*multiLineCreator, ProcessEnter_ButSkip);
        REQUIRE( dlg.ShowModal() == wxID_CLOSE );
        CHECK( dlg.GotEnter() );
    }

    SECTION("With wxTE_PROCESS_ENTER and wxTE_MULTILINE without skipping")
    {
        std::unique_ptr<TextLikeControlCreator>
            multiLineCreator(controlCreator.CloneAsMultiLine());
        if ( !multiLineCreator )
            return;

        TestDialog dlg(*multiLineCreator, ProcessEnter_WithoutSkipping);
        REQUIRE( dlg.ShowModal() == wxID_APPLY );
        CHECK( dlg.GotEnter() );
    }
}

#else // !wxUSE_UIACTIONSIMULATOR

void TestProcessEnter(const TextLikeControlCreator& WXUNUSED(controlCreator))
{
    WARN("Skipping wxTE_PROCESS_ENTER tests: wxUIActionSimulator not available");
}

#endif // wxUSE_UIACTIONSIMULATOR/!wxUSE_UIACTIONSIMULATOR
