//
//  kjots
//
//  Copyright (C) 1997 Christoph Neerfeld
//  Copyright (C) 2002, 2003 Aaron J. Seigo
//  Copyright (C) 2003 Stanislav Kljuhhin
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//

#include <tqpainter.h>
#include <tqpaintdevicemetrics.h>
#include <tqcursor.h>

#include <tdepopupmenu.h>
#include <kfind.h>
#include <kfinddialog.h>
#include <kreplace.h>
#include <kreplacedialog.h>
#include <kstringhandler.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kopenwith.h>
#include <kprinter.h>

#include "kjotsedit.h"
#include "kjotsentry.h"

//----------------------------------------------------------------------
// MYMULTIEDIT
//----------------------------------------------------------------------
KJotsEdit::KJotsEdit (TQWidget* parent, const char* name)
    : KTextEdit(parent, name)
    , m_entry(nullptr)
    , m_find(nullptr)
    , m_findDialog(nullptr)
    , m_replace(nullptr)
    , m_replaceDialog(nullptr)
{
    // no rich text until printing and other such issues are worked out
    setTextFormat(TQt::PlainText);
    setWordWrap(TQTextEdit::WidgetWidth);
    setLinkUnderline(true);
    web_menu = new TDEPopupMenu(this);;
    web_menu->insertItem(i18n("Open URL"), this, TQ_SLOT(openUrl()) );
}

KJotsEdit::~KJotsEdit()
{
    delete m_find;
    delete m_replace;
}

void KJotsEdit::mousePressEvent( TQMouseEvent *e )
{
    if(e->button() == TQt::RightButton &&
            hasSelectedText())
    {
        KURL url(selectedText());

        if(url.isValid())
        {
            web_menu->popup(TQCursor::pos());
            return;
        }
    }

    KTextEdit::mousePressEvent(e);
}

void KJotsEdit::openUrl()
{
    if (hasSelectedText())
    {
        KURL url(selectedText());
        if(url.isValid())
        {
            new KRun(url);
        }
    }
}

void KJotsEdit::print(TQString title)
{
    KPrinter printer;
    printer.setDocName(title);
    printer.setFullPage(false);
    printer.setCreator("KJots");

    if (printer.setup(this))
    {
        TQFont printFont = font();
        TQPainter painter( &printer );
        TQPaintDeviceMetrics metrics( &printer );
        int y = 0;
        int maxWidth = metrics.width();
        int maxHeight = metrics.height();
        TQString currentParagraph;

        for (int paragraphCount = 0; paragraphCount < paragraphs(); ++paragraphCount )
        {
            currentParagraph = text(paragraphCount);
            TQRect r = painter.boundingRect(0, y, maxWidth, maxHeight,
                    TQPainter::ExpandTabs | TQPainter::WordBreak,
                    currentParagraph);

            if ((y + r.height()) > maxHeight)
            {
                printer.newPage();
                y = 0;
            }

            painter.drawText(0, y, maxWidth, maxHeight - y,
                    TQPainter::ExpandTabs | TQPainter::WordBreak,
                    currentParagraph);
            y += r.height();
        }
        painter.end();
    }
}

void KJotsEdit::slotFindHighlight(const TQString& text, int matchIndex, int matchLength)
{
    // tqDebug("KJotsEdit::slotFindHighlight");

    Q_UNUSED(text);

    const bool inSelection = m_find->options() & KFindDialog::SelectedText;

    // Ensure we are offsetting the selection for the first line of the selection
    // so we don't highlight the wrong text.
    if (inSelection && (m_findCursor.paragraph == m_selectionStart.paragraph))
    {
	setSelection(m_findCursor.paragraph, m_selectionStart.paragraphIndex + matchIndex, m_findCursor.paragraph,
		     m_selectionStart.paragraphIndex + matchIndex + matchLength);
    }
    else
    {
	setSelection(m_findCursor.paragraph, matchIndex, m_findCursor.paragraph, matchIndex + matchLength);
    }
}

void KJotsEdit::slotReplaceHighlight(const TQString& text, int matchIndex, int matchLength)
{
    // tqDebug("KJotsEdit::slotReplaceHighlight");

    Q_UNUSED(text);

    setSelection(m_replaceCursor.paragraph, matchIndex, m_replaceCursor.paragraph, matchIndex + matchLength);
}

void KJotsEdit::replace()
{
    // tqDebug("KJotsEdit::replace");

    if (length() == 0)
    {
        return;
    }

    if (m_replaceDialog)
    {
        m_replaceDialog->setActiveWindow();
    }
    else
    {
        m_replaceDialog = new KReplaceDialog(this, "m_replaceDialog", KReplaceDialog::PromptOnReplace);
        connect(m_replaceDialog, TQ_SIGNAL(okClicked()), this, TQ_SLOT(slotDoReplace()));
    }

    if (hasSelectedText())
    {
        m_replaceDialog->setHasSelection(true);
        m_replaceDialog->setOptions(m_replaceDialog->options() | KReplaceDialog::SelectedText);
    }
    else
    {
        m_replaceDialog->setHasSelection(false);
        m_replaceDialog->setOptions(m_replaceDialog->options() & ~KReplaceDialog::SelectedText);
    }

    m_replaceDialog->show();
}

void KJotsEdit::slotDoReplace()
{
    // tqDebug("KJotsEdit::slotDoReplace");

    /* Performing a new replacement, so we need to remove the previous
     * KReplace, set up a new one and the 'replaceNext' functionality. */

    delete m_replace;
    m_replace = nullptr;

    if (!m_replaceDialog)
    {
        // tqDebug("KJotsEdit::slotDoReplace: no replaceDialog");
        return;
    }

    m_replace = new KReplace(m_replaceDialog->pattern(), m_replaceDialog->replacement(), m_replaceDialog->options());

    if ((m_replace->options() & KReplaceDialog::SelectedText))
    {
        const bool backwards = m_replace->options() & KFindDialog::FindBackwards;

        getSelection(&m_selectionStart.paragraph, &m_selectionStart.paragraphIndex, &m_selectionEnd.paragraph,
                     &m_selectionEnd.paragraphIndex);
        m_replaceCursor.paragraph = backwards ? m_selectionEnd.paragraph : m_selectionStart.paragraph;
        m_replaceCursor.paragraphIndex = backwards ? m_selectionEnd.paragraphIndex : m_selectionStart.paragraphIndex;
    }
    else
    {
        setupCursor(&m_replaceCursor, m_replace);
    }

    connect(m_replace, TQ_SIGNAL(highlight(const TQString&, int, int)), this,
            TQ_SLOT(slotReplaceHighlight(const TQString&, int, int)));
    connect(m_replace, TQ_SIGNAL(findNext()), this, TQ_SLOT(slotReplaceNext()));
    connect(m_replace, TQ_SIGNAL(replace(const TQString&, int, int, int)), this,
            TQ_SLOT(slotReplace(const TQString&, int, int, int)));

    m_replaceDialog->close();
    slotReplaceNext();
}

void KJotsEdit::slotReplace(const TQString& replaceText, int replacementIndex, int replacementLength, int matchedLength)
{
    // tqDebug("KJotsEdit::slotReplace");

    // Ensure the selection only matches the replacement.
    // Prevents an issue where all the text is selected before replacing (since we remove the selection below).
    setSelection(m_replaceCursor.paragraph, replacementIndex, m_replaceCursor.paragraph,
                 replacementIndex + matchedLength);

    const auto replacement = replaceText.mid(replacementIndex, replacementLength);
    removeSelectedText();
    insertAt(replacement, m_replaceCursor.paragraph, replacementIndex);
    setModified(true);
}

void KJotsEdit::slotReplaceNext()
{
    // tqDebug("KJotsEdit::slotReplaceNext");

    if (!m_replace)
    {
        return;
    }

    const bool backwards = (m_replace->options() & KReplaceDialog::FindBackwards);
    const bool useSelection = (m_replace->options() & KReplaceDialog::SelectedText);

    if (m_replace->needData())
    {
        if (useSelection && backwards)
        {
            m_replace->setData(text(m_selectionEnd.paragraph), m_selectionEnd.paragraphIndex);
        }
        else if (useSelection)
        {
            m_replace->setData(text(m_selectionStart.paragraph), m_selectionStart.paragraphIndex);
        }
        else
        {
            m_replace->setData(text(m_replaceCursor.paragraph), m_replaceCursor.paragraphIndex);
        }
    }

    KReplace::Result res = KReplace::NoMatch;

    do
    {
        res = m_replace->replace();
        if (res == KReplace::Match)
        {
            return;
        }

        m_replaceCursor.paragraph += (backwards ? -1 : 1);
        m_replaceCursor.paragraphIndex = (backwards ? paragraphLength(m_replaceCursor.paragraph) : 0);
        m_replace->setData(text(m_replaceCursor.paragraph), m_replaceCursor.paragraphIndex);

        if (useSelection && m_replaceCursor.paragraph > m_selectionEnd.paragraph)
        {
            break;
        }

        if (useSelection && backwards && m_replaceCursor.paragraph < m_selectionStart.paragraph)
        {
            break;
        }
    } while (backwards ? (m_replaceCursor.paragraph >= 0) : (m_replaceCursor.paragraph < paragraphs()));

    Q_ASSERT(res != KReplace::Match);

    if ((m_replace->options() & KReplaceDialog::FromCursor) && m_replace->shouldRestart(true))
    {
        // tqDebug("KJotsEdit::slotReplaceNext restarting");
        m_replaceCursor.paragraph = backwards ? (paragraphs() - 1) : 0;
        m_replaceCursor.paragraphIndex = backwards ? (paragraphLength(m_replaceCursor.paragraph)) : 0;
        m_replace->setData(text(m_replaceCursor.paragraph), m_replaceCursor.paragraphIndex);
        m_replace->resetCounts();
        slotReplaceNext();
        return;
    }

    m_replace->displayFinalDialog();
    m_replace->disconnect(this, TQ_SLOT(slotReplaceHighlight(const TQString&, int, int)));
    m_replace->deleteLater();
    m_replace = nullptr;
}

void KJotsEdit::find()
{
    // tqDebug("KJotsEdit::find");

    if (length() == 0)
    {
        return;
    }

    if (m_findDialog)
    {
        m_findDialog->setActiveWindow();
    }
    else
    {
        m_findDialog = new KFindDialog(this, "m_findDialog");
        connect(m_findDialog, TQ_SIGNAL(okClicked()), this, TQ_SLOT(slotDoFind()));
    }

    if (hasSelectedText())
    {
        m_findDialog->setHasSelection(true);
        m_findDialog->setOptions(m_findDialog->options() | KFindDialog::SelectedText);
    }
    else
    {
        m_findDialog->setHasSelection(false);
        m_findDialog->setOptions(m_findDialog->options() & ~KFindDialog::SelectedText);
    }


    m_findDialog->show();
}

void KJotsEdit::findNext()
{
    // tqDebug("KJotsEdit::findNext");

    if (!m_find)
    {
        find();
        return;
    }

    const bool backwards = (m_find->options() & KFindDialog::FindBackwards);
    const bool fromCursor = (m_find->options() & KFindDialog::FromCursor);
    const bool inSelection = (m_find->options() & KFindDialog::SelectedText);

    if (m_find->needData())
    {
	if (inSelection)
	{
	    if (m_selectionStart.paragraph == m_selectionEnd.paragraph)
	    {
		// Same line, ensure we only inlcude the selection.
		auto selectionLength = m_selectionEnd.paragraphIndex - m_selectionStart.paragraphIndex;
		auto data = text(m_findCursor.paragraph).mid(m_selectionStart.paragraphIndex, selectionLength);
		m_find->setData(data);
	    }
	    else if (backwards)
	    {
		m_findCursor.paragraph = m_selectionEnd.paragraph;
		m_findCursor.paragraphIndex = -1;
		m_find->setData(text(m_findCursor.paragraph).left(m_selectionEnd.paragraphIndex));
	    }
	    else
	    {
		m_findCursor.paragraph = m_selectionStart.paragraph;
		m_findCursor.paragraphIndex = 0;
		auto offset = (paragraphLength(m_findCursor.paragraph)) - m_selectionStart.paragraphIndex+1;
		m_find->setData(text(m_findCursor.paragraph).right(offset), m_findCursor.paragraphIndex);
	    }
	}
        else
        {
            m_find->setData(text(m_findCursor.paragraph), m_findCursor.paragraphIndex);
        }
    }

    KFind::Result res = KFind::NoMatch;

    do
    {
        res = m_find->find();
        if (res == KFind::Match)
        {
            return;
        }

        m_findCursor.paragraph += (backwards ? -1 : 1);
	m_findCursor.paragraphIndex = -1; // SOL or EOL depending on `backwards`.

	if (m_findCursor.paragraph == m_selectionStart.paragraph)
	{
	    auto offset = (paragraphLength(m_findCursor.paragraph)) - m_selectionStart.paragraphIndex+1;
	    m_find->setData(text(m_findCursor.paragraph).right(offset), m_findCursor.paragraphIndex);
	}
	else if (m_findCursor.paragraph == m_selectionEnd.paragraph)
	{
	    m_find->setData(text(m_findCursor.paragraph).left(m_selectionEnd.paragraphIndex), m_findCursor.paragraphIndex);
	}
	else
	{
	    m_findCursor.paragraphIndex = -1;
	    m_find->setData(text(m_findCursor.paragraph), m_findCursor.paragraphIndex);
	}

        if (inSelection && backwards && m_findCursor.paragraph < m_selectionStart.paragraph)
        {
            break;
        }

        if (inSelection && m_findCursor.paragraph > m_selectionEnd.paragraph)
        {
            break;
        }
    } while (backwards ? (m_findCursor.paragraph >= 0) : (m_findCursor.paragraph < paragraphs()));

    Q_ASSERT(res != KFind::Match);

    // If there were no matches, and we were checking from the start of the text,
    // then we do not want to prompt to search again, since we already know there
    // is nothing.

    if (m_find->numMatches() == 0 && !fromCursor)
    {
        KMessageBox::sorry(this,
                           i18n("Search string '%1' was not found!").arg(KStringHandler::csqueeze(m_find->pattern())));
        // Reset the cursor in case more text is added between calls to findNext()
        m_findCursor = m_selectionStart;
        m_find->setData(text(m_selectionStart.paragraph)
                                .mid(m_selectionStart.paragraphIndex,
                                     m_selectionEnd.paragraphIndex - m_selectionStart.paragraphIndex));
        return;
    }

    if (m_find->shouldRestart(/* forceAsking */ true, /* showNumMatches */ false))
    {
        if (inSelection)
        {
            if (m_selectionStart.paragraph == m_selectionEnd.paragraph)
            {
                m_findCursor.paragraph = m_selectionStart.paragraph;
                m_findCursor.paragraphIndex = m_selectionStart.paragraphIndex;
                auto selectionLength = m_selectionEnd.paragraphIndex - m_selectionStart.paragraphIndex;
                auto data = text(m_findCursor.paragraph).mid(m_findCursor.paragraphIndex, selectionLength);
                m_find->setData(data);
            }
	    else if (backwards)
	    {
		m_findCursor = m_selectionEnd;
		m_find->setData(text(m_findCursor.paragraph).left(m_findCursor.paragraphIndex));
	    }
            else
            {
                m_findCursor.paragraph = m_selectionStart.paragraph;
		m_findCursor.paragraphIndex = -1;
		auto offset = (paragraphLength(m_findCursor.paragraph)) - m_selectionStart.paragraphIndex+1;
		m_find->setData(text(m_findCursor.paragraph).right(offset), m_findCursor.paragraphIndex);
            }
        }
        else
        {
            m_findCursor.paragraph = backwards ? (paragraphs() - 1) : 0;
            m_findCursor.paragraphIndex = backwards ? (paragraphLength(m_findCursor.paragraph)) : 0;
            m_find->setData(text(m_findCursor.paragraph), m_findCursor.paragraphIndex);
        }

        m_find->resetCounts();
        findNext();
    }
}

void KJotsEdit::findPrev()
{
    if (!m_find)
    {
        find();
        return;
    }

    m_find->setOptions(m_find->options() ^ KFindDialog::FindBackwards);
    findNext();

    // Check as pressing 'stop' will delete m_find.
    if (m_find)
    {
        m_find->setOptions(m_find->options() ^ KFindDialog::FindBackwards);
    }
}

void KJotsEdit::slotDoFind()
{
    // tqDebug("KJotsEdit::slotDoFind");

    /* Performing a new search, ensure the previous search is invalidated. */

    delete m_find;
    m_find = nullptr;

    if (!m_findDialog)
    {
        // tqDebug("KJotsEdit::slotDoFind: find dialog not set up");
        return;
    }

    if (m_findDialog->pattern().isEmpty())
    {
        // tqDebug("KJotsEdit::slotDoFind: empty pattern.");
        return;
    }

    // tqDebug("findDialog->pattern = %s", m_findDialog->pattern().local8Bit().data());

    m_find = new KFind(m_findDialog->pattern(), m_findDialog->options(), this);

    if (m_find->options() & KFindDialog::SelectedText)
    {
        const bool backwards = m_find->options() & KFindDialog::FindBackwards;

        getSelection(&m_selectionStart.paragraph, &m_selectionStart.paragraphIndex, &m_selectionEnd.paragraph,
                     &m_selectionEnd.paragraphIndex);
        m_findCursor.paragraph = backwards ? m_selectionEnd.paragraph : m_selectionStart.paragraph;
        m_findCursor.paragraphIndex = backwards ? m_selectionEnd.paragraphIndex : m_selectionStart.paragraphIndex;
    }
    else
    {
        setupCursor(&m_findCursor, m_find);
        // Reset selection so slotFindHighlight works correctly.
        m_selectionStart = {0, 0};
        m_selectionEnd = {0, 0};
    }

    connect(m_find, TQ_SIGNAL(highlight(const TQString&, int, int)), this,
            TQ_SLOT(slotFindHighlight(const TQString&, int, int)));
    connect(m_find, TQ_SIGNAL(findNext()), this, TQ_SLOT(findNext()));

    m_findDialog->close();
    m_find->closeFindNextDialog();

    findNext();
}

void KJotsEdit::setEntry (KJotsPage *entry)
{
    //tell the old entry to take a hike
    if ( m_entry )
    {
        m_entry->setEditor(0);
    }

    //load up the new entry (assuming there is one)
    if ( entry )
    {
        m_entry = entry;
        setText(entry->body());
        removeSelection();
        repaint();
        setEnabled(true);
        setFocus();
        entry->setEditor(this);
    } else {
        clear();
    }

    m_entry = entry;

    // Reset the find & replace dialog for the new entry.
    delete m_find;
    delete m_replace;
    m_find = nullptr;
    m_replace = nullptr;
}

void KJotsEdit::setupCursor(KJotsEdit::CursorPosition* cursor, const KFind* find)
{
    if (!cursor)
    {
        tqWarning("WARNING: Attempting to setup a NULL cursor: %s (%d)", __FILE__, __LINE__);
        return;
    }

    cursor->paragraph = 0;
    cursor->paragraphIndex = 0;

    if (find->options() & KFindDialog::FromCursor)
    {
        getCursorPosition(&cursor->paragraph, &cursor->paragraphIndex);
    }
    else if (find->options() & KFindDialog::FindBackwards)
    {
        cursor->paragraph = paragraphs();
        cursor->paragraphIndex = paragraphLength(cursor->paragraph);
    }
}

#include "kjotsedit.moc"
/* ex: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab: */
