/*  This file is part of the Kate project.
 *
 *  Copyright (C) 2010 Christoph Cullmann <cullmann@kde.org>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 */

#include "katetexthistory.h"
#include "katetextbuffer.h"

namespace Kate
{

TextHistory::TextHistory(TextBuffer &buffer)
    : m_buffer(buffer)
    , m_lastSavedRevision(-1)
    , m_firstHistoryEntryRevision(0)
{
    // just call clear to init
    clear();
}

TextHistory::~TextHistory()
{
}

qint64 TextHistory::revision() const
{
    // just output last revisions of buffer
    return m_buffer.revision();
}

void TextHistory::clear()
{
    // reset last saved revision
    m_lastSavedRevision = -1;

    // remove all history entries and add no-change dummy for first revision
    m_historyEntries.clear();
    m_historyEntries.push_back(Entry());

    // first entry will again belong to first revision
    m_firstHistoryEntryRevision = 0;
}

void TextHistory::setLastSavedRevision()
{
    // current revision was successful saved
    m_lastSavedRevision = revision();
}

void TextHistory::wrapLine(const KTextEditor::Cursor &position)
{
    // create and add new entry
    Entry entry;
    entry.type = Entry::WrapLine;
    entry.line = position.line();
    entry.column = position.column();
    addEntry(entry);
}

void TextHistory::unwrapLine(int line, int oldLineLength)
{
    // create and add new entry
    Entry entry;
    entry.type = Entry::UnwrapLine;
    entry.line = line;
    entry.column = 0;
    entry.oldLineLength = oldLineLength;
    addEntry(entry);
}

void TextHistory::insertText(const KTextEditor::Cursor &position, int length, int oldLineLength)
{
    // create and add new entry
    Entry entry;
    entry.type = Entry::InsertText;
    entry.line = position.line();
    entry.column = position.column();
    entry.length = length;
    entry.oldLineLength = oldLineLength;
    addEntry(entry);
}

void TextHistory::removeText(const KTextEditor::Range &range, int oldLineLength)
{
    // create and add new entry
    Entry entry;
    entry.type = Entry::RemoveText;
    entry.line = range.start().line();
    entry.column = range.start().column();
    entry.length = range.end().column() - range.start().column();
    entry.oldLineLength = oldLineLength;
    addEntry(entry);
}

void TextHistory::addEntry(const Entry &entry)
{
    /**
     * history should never be empty
     */
    Q_ASSERT(!m_historyEntries.empty());

    /**
     * simple efficient check: if we only have one entry, and the entry is not referenced
     * just replace it with the new one and adjust the revision
     */
    if ((m_historyEntries.size() == 1) && !m_historyEntries.first().referenceCounter) {
        /**
         * remember new revision for first element, it is the revision we get after this change
         */
        m_firstHistoryEntryRevision = revision() + 1;

        /**
         * remember edit
         */
        m_historyEntries.first() = entry;

        /**
         * be done...
         */
        return;
    }

    /**
     * ok, we have more than one entry or the entry is referenced, just add up new entries
     */
    m_historyEntries.push_back(entry);
}

void TextHistory::lockRevision(qint64 revision)
{
    /**
     * some invariants must hold
     */
    Q_ASSERT(!m_historyEntries.empty());
    Q_ASSERT(revision >= m_firstHistoryEntryRevision);
    Q_ASSERT(revision < (m_firstHistoryEntryRevision + m_historyEntries.size()));

    /**
     * increment revision reference counter
     */
    Entry &entry = m_historyEntries[revision - m_firstHistoryEntryRevision];
    ++entry.referenceCounter;
}

void TextHistory::unlockRevision(qint64 revision)
{
    /**
     * some invariants must hold
     */
    Q_ASSERT(!m_historyEntries.empty());
    Q_ASSERT(revision >= m_firstHistoryEntryRevision);
    Q_ASSERT(revision < (m_firstHistoryEntryRevision + m_historyEntries.size()));

    /**
     * decrement revision reference counter
     */
    Entry &entry = m_historyEntries[revision - m_firstHistoryEntryRevision];
    Q_ASSERT(entry.referenceCounter);
    --entry.referenceCounter;

    /**
     * clean up no longer used revisions...
     */
    if (!entry.referenceCounter) {
        /**
         * search for now unused stuff
         */
        int unreferencedEdits = 0;
        for (int i = 0; i + 1 < m_historyEntries.size(); ++i) {
            if (m_historyEntries.at(i).referenceCounter) {
                break;
            }

            // remember deleted count
            ++unreferencedEdits;
        }

        /**
         * remove unrefed from the list now
         */
        if (unreferencedEdits > 0) {
            // remove stuff from history
            m_historyEntries.erase(m_historyEntries.begin(), m_historyEntries.begin() + unreferencedEdits);

            // patch first entry revision
            m_firstHistoryEntryRevision += unreferencedEdits;
        }
    }
}

void TextHistory::Entry::transformCursor(int &cursorLine, int &cursorColumn, bool moveOnInsert) const
{
    /**
     * simple stuff, sort out generic things
     */

    /**
     * no change, if this change is in line behind cursor
     */
    if (line > cursorLine) {
        return;
    }

    /**
     * handle all history types
     */
    switch (type) {
    /**
     * Wrap a line
     */
    case WrapLine:
        /**
         * we wrap this line
         */
        if (cursorLine == line) {
            /**
             * skip cursors with too small column
             */
            if (cursorColumn <= column) {
                if (cursorColumn < column || !moveOnInsert) {
                    return;
                }
            }

            /**
             * adjust column
             */
            cursorColumn =  cursorColumn - column;
        }

        /**
         * always increment cursor line
         */
        cursorLine +=  1;
        return;

    /**
     * Unwrap a line
     */
    case UnwrapLine:
        /**
         * we unwrap this line, adjust column
         */
        if (cursorLine == line) {
            cursorColumn +=  oldLineLength;
        }

        /**
         * decrease cursor line
         */
        cursorLine -= 1;
        return;

    /**
     * Insert text
     */
    case InsertText:
        /**
         * only interesting, if same line
         */
        if (cursorLine != line) {
            return;
        }

        // skip cursors with too small column
        if (cursorColumn <= column)
            if (cursorColumn < column || !moveOnInsert) {
                return;
            }

        // patch column of cursor
        if (cursorColumn <= oldLineLength) {
            cursorColumn += length;
        }

        // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
        else if (cursorColumn < oldLineLength + length) {
            cursorColumn =  oldLineLength + length;
        }

        return;

    /**
     * Remove text
     */
    case RemoveText:
        /**
         * only interesting, if same line
         */
        if (cursorLine != line) {
            return;
        }

        // skip cursors with too small column
        if (cursorColumn <= column) {
            return;
        }

        // patch column of cursor
        if (cursorColumn <= column + length) {
            cursorColumn =  column;
        } else {
            cursorColumn -=  length;
        }

        return;

    /**
     * nothing
     */
    default:
        return;
    }
}

void TextHistory::Entry::reverseTransformCursor(int &cursorLine, int &cursorColumn, bool moveOnInsert) const
{
    /**
     * handle all history types
     */
    switch (type) {
    /**
     * Wrap a line
     */
    case WrapLine:
        /**
         * ignore this line
         */
        if (cursorLine <= line) {
            return;
        }

        /**
         * next line is unwrapped
         */
        if (cursorLine == line + 1) {
            /**
             * adjust column
             */
            cursorColumn = cursorColumn + column;
        }

        /**
         * always decrement cursor line
         */
        cursorLine -=  1;
        return;

    /**
     * Unwrap a line
     */
    case UnwrapLine:
        /**
         * ignore lines before unwrapped one
         */
        if (cursorLine < line - 1) {
            return;
        }

        /**
         * we unwrap this line, try to adjust cursor column if needed
         */
        if (cursorLine == line - 1) {
            /**
             * skip cursors with to small columns
             */
            if (cursorColumn <= oldLineLength) {
                if (cursorColumn < oldLineLength || !moveOnInsert) {
                    return;
                }
            }

            cursorColumn -= oldLineLength;
        }

        /**
         * increase cursor line
         */
        cursorLine += 1;
        return;

    /**
     * Insert text
     */
    case InsertText:
        /**
         * only interesting, if same line
         */
        if (cursorLine != line) {
            return;
        }

        // skip cursors with too small column
        if (cursorColumn <= column) {
            return;
        }

        // patch column of cursor
        if (cursorColumn - length < column) {
            cursorColumn = column;
        } else {
            cursorColumn -= length;
        }

        return;

    /**
     * Remove text
     */
    case RemoveText:
        /**
         * only interesting, if same line
         */
        if (cursorLine != line) {
            return;
        }

        // skip cursors with too small column
        if (cursorColumn <= column)
            if (cursorColumn < column || !moveOnInsert) {
                return;
            }

        // patch column of cursor
        if (cursorColumn <= oldLineLength) {
            cursorColumn += length;
        }

        // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
        else if (cursorColumn < oldLineLength + length) {
            cursorColumn =  oldLineLength + length;
        }
        return;

    /**
     * nothing
     */
    default:
        return;
    }
}

void TextHistory::transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision)
{
    /**
     * -1 special meaning for from/toRevision
     */
    if (fromRevision == -1) {
        fromRevision = revision();
    }

    if (toRevision == -1) {
        toRevision = revision();
    }

    /**
     * shortcut, same revision
     */
    if (fromRevision == toRevision) {
        return;
    }

    /**
     * some invariants must hold
     */
    Q_ASSERT(!m_historyEntries.empty());
    Q_ASSERT(fromRevision != toRevision);
    Q_ASSERT(fromRevision >= m_firstHistoryEntryRevision);
    Q_ASSERT(fromRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
    Q_ASSERT(toRevision >= m_firstHistoryEntryRevision);
    Q_ASSERT(toRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));

    /**
     * transform cursor
     */
    bool moveOnInsert = insertBehavior == KTextEditor::MovingCursor::MoveOnInsert;

    /**
     * forward or reverse transform?
     */
    if (toRevision > fromRevision) {
        for (int rev = fromRevision - m_firstHistoryEntryRevision + 1; rev <= (toRevision - m_firstHistoryEntryRevision); ++rev) {
            const Entry &entry = m_historyEntries.at(rev);
            entry.transformCursor(line, column, moveOnInsert);
        }
    } else {
        for (int rev = fromRevision - m_firstHistoryEntryRevision; rev >= (toRevision - m_firstHistoryEntryRevision + 1); --rev) {
            const Entry &entry = m_historyEntries.at(rev);
            entry.reverseTransformCursor(line, column, moveOnInsert);
        }
    }
}

void TextHistory::transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision)
{
    /**
     * invalidate on empty?
     */
    bool invalidateIfEmpty = emptyBehavior == KTextEditor::MovingRange::InvalidateIfEmpty;
    if (invalidateIfEmpty && range.end() <= range.start()) {
        range = KTextEditor::Range::invalid();
        return;
    }

    /**
     * -1 special meaning for from/toRevision
     */
    if (fromRevision == -1) {
        fromRevision = revision();
    }

    if (toRevision == -1) {
        toRevision = revision();
    }

    /**
     * shortcut, same revision
     */
    if (fromRevision == toRevision) {
        return;
    }

    /**
     * some invariants must hold
     */
    Q_ASSERT(!m_historyEntries.empty());
    Q_ASSERT(fromRevision != toRevision);
    Q_ASSERT(fromRevision >= m_firstHistoryEntryRevision);
    Q_ASSERT(fromRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
    Q_ASSERT(toRevision >= m_firstHistoryEntryRevision);
    Q_ASSERT(toRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));

    /**
     * transform cursors
     */

    // first: copy cursors, without range association
    int startLine = range.start().line(), startColumn = range.start().column(), endLine = range.end().line(), endColumn = range.end().column();

    bool moveOnInsertStart = !(insertBehaviors & KTextEditor::MovingRange::ExpandLeft);
    bool moveOnInsertEnd = (insertBehaviors & KTextEditor::MovingRange::ExpandRight);

    /**
     * forward or reverse transform?
     */
    if (toRevision > fromRevision) {
        for (int rev = fromRevision - m_firstHistoryEntryRevision + 1; rev <= (toRevision - m_firstHistoryEntryRevision); ++rev) {
            const Entry &entry = m_historyEntries.at(rev);

            entry.transformCursor(startLine, startColumn, moveOnInsertStart);

            entry.transformCursor(endLine, endColumn, moveOnInsertEnd);

            // got empty?
            if (endLine < startLine || (endLine == startLine && endColumn <= startColumn)) {
                if (invalidateIfEmpty) {
                    range = KTextEditor::Range::invalid();
                    return;
                } else {
                    // else normalize them
                    endLine = startLine;
                    endColumn = startColumn;
                }
            }
        }
    } else {
        for (int rev = fromRevision - m_firstHistoryEntryRevision; rev >= (toRevision - m_firstHistoryEntryRevision + 1); --rev) {
            const Entry &entry = m_historyEntries.at(rev);

            entry.reverseTransformCursor(startLine, startColumn, moveOnInsertStart);

            entry.reverseTransformCursor(endLine, endColumn, moveOnInsertEnd);

            // got empty?
            if (endLine < startLine || (endLine == startLine && endColumn <= startColumn)) {
                if (invalidateIfEmpty) {
                    range = KTextEditor::Range::invalid();
                    return;
                } else {
                    // else normalize them
                    endLine = startLine;
                    endColumn = startColumn;
                }
            }
        }
    }

    // now, copy cursors back
    range.setRange(KTextEditor::Cursor(startLine, startColumn), KTextEditor::Cursor(endLine, endColumn));
}

}
