/*
    SPDX-FileCopyrightText: 2003, 2008-2009 Friedrich W. H. Kossebau <kossebau@kde.org>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/

#include "bytearraytablecursor.hpp"

// lib
#include "bytearraytablelayout.hpp"
// Okteta core
#include <Okteta/ArrayChangeMetricsList>

namespace Okteta {

ByteArrayTableCursor::ByteArrayTableCursor(const ByteArrayTableLayout* layout)
    : mLayout(layout)
    , mIndex(layout->byteArrayOffset())
    , mCoord(layout->startCoord())
    , mBehind(false)
    , mAppendPosEnabled(false)
{
}

void ByteArrayTableCursor::setAppendPosEnabled(bool appendPosEnabled)
{
    if (mAppendPosEnabled == appendPosEnabled) {
        return;
    }

    mAppendPosEnabled = appendPosEnabled;
    // reposition Cursor
    if (realIndex() > mLayout->lastByteArrayOffset()
        && mCoord.pos() < mLayout->noOfBytesPerLine() - 1
        && mLayout->length() > 0) {
        if (mAppendPosEnabled) {
            ++mIndex;
            mCoord.goRight();
            mBehind = false;
        } else {
            --mIndex;
            mCoord.goLeft();
            mBehind = true;
        }
    }
}

void ByteArrayTableCursor::gotoPreviousByte()
{
    if (mBehind) {
        mBehind = false;
    } else if (mIndex > mLayout->byteArrayOffset()) {
        --mIndex;
        mCoord.goCLeft(mLayout->noOfBytesPerLine() - 1);
    }
}

void ByteArrayTableCursor::gotoPreviousByte(Size indexSteps)
{
    if (mBehind) {
        --indexSteps;
        mBehind = false;
    }
    const Address newIndex = mIndex - indexSteps;
    // would step before first position?
    const Address firstIndex = mLayout->byteArrayOffset();
    if (newIndex < firstIndex) {
        if (mIndex > firstIndex) {
            gotoStart();
        }
    } else {
        gotoIndex(newIndex);
    }
}

void ByteArrayTableCursor::gotoNextByte()
{
    const Address lastIndex = mLayout->lastByteArrayOffset();

    if (mIndex < lastIndex) {
        ++mIndex;
        mCoord.goCRight(mLayout->noOfBytesPerLine() - 1);
        mBehind = false;
    } else if (mIndex == lastIndex) {
        stepToEnd();
    }
}

void ByteArrayTableCursor::gotoNextByte(Size indexSteps)   // TODO: think about consistency with gotoNextByte!!!
{
    if (mBehind) {
        ++indexSteps;
        mBehind = false;
    }
    const Address newIndex = mIndex + indexSteps;
    // would step behind the end?
    if (newIndex > mLayout->lastByteArrayOffset()) {
        gotoEnd();
    } else {
        gotoIndex(newIndex);
    }
}

void ByteArrayTableCursor::gotoUp()
{
    // can we even go up?
    if (mCoord.isBelow(mLayout->startLine())) {
        mCoord.goUp();
        if (mCoord.isPriorInLineThan(mLayout->startCoord())) {
            mIndex = mLayout->byteArrayOffset();
            mCoord.setPos(mLayout->firstStartLinePosition());
            mBehind = false;
        } else {
            mIndex -= mLayout->noOfBytesPerLine();
            if (mBehind && !atLineEnd()) {
                ++mIndex;
                mCoord.goRight();
                mBehind = false;
            }
        }
    }
}

void ByteArrayTableCursor::gotoDown()
{
    if (mCoord.isAbove(mLayout->finalLine())) {
        mCoord.goDown();
        // behind End?
        if (mCoord.isLaterInLineThan(mLayout->finalCoord())) {
            gotoEnd();
        } else {
            mIndex += mLayout->noOfBytesPerLine();
        }
    }
}

void ByteArrayTableCursor::gotoLineStart()
{
    const Address oldIndex = mIndex;
    mIndex = mLayout->indexAtFirstLinePosition(mCoord.line());
    mCoord.goLeft(oldIndex - mIndex);
    mBehind = false;
}

void ByteArrayTableCursor::gotoLineEnd()
{
    if (mIndex <= mLayout->lastByteArrayOffset()) {
        const Address oldIndex = mIndex;
        mIndex = mLayout->indexAtLastLinePosition(mCoord.line());
        mCoord.goRight(mIndex - oldIndex);

        stepToEnd();
    }
}

void ByteArrayTableCursor::gotoStart()
{
    mIndex = mLayout->byteArrayOffset();
    mCoord = mLayout->startCoord();
    mBehind = false;
}

void ByteArrayTableCursor::gotoEnd()
{
    const Address lastIndex = mLayout->lastByteArrayOffset();
    if (lastIndex >= 0) {
        mIndex = lastIndex;
        mCoord = mLayout->finalCoord();

        stepToEnd();
    } else {
        gotoStart();
    }
}

void ByteArrayTableCursor::gotoCIndex(Address index)
{
    if (mLayout->length() > 0) {
        mIndex = mLayout->correctIndex(index);
        mCoord = mLayout->coordOfIndex(mIndex);
        mBehind = (index > mIndex);
    } else {
        gotoStart();
    }
}

void ByteArrayTableCursor::gotoCCoord(Coord coord)
{
    if (mLayout->length() > 0) {
        mCoord = mLayout->correctCoord(coord);
        mIndex = mLayout->indexAtCoord(mCoord);
        if (coord > mCoord) {
            stepToEnd();
        } else {
            mBehind = false;
        }
    } else {
        gotoStart();
    }
}

void ByteArrayTableCursor::stepToEnd()
{
    if (mAppendPosEnabled && (mCoord.pos() < mLayout->noOfBytesPerLine() - 1)) {
        ++mIndex;
        mCoord.goRight();
        mBehind = false;
    } else {
        mBehind = true;
    }
}

void ByteArrayTableCursor::gotoIndex(Address index)
{
    mIndex = index;
    mCoord = mLayout->coordOfIndex(mIndex);
    mBehind = false;
}

void ByteArrayTableCursor::gotoRealIndex()
{
    if (mBehind
        && (mAppendPosEnabled || (mIndex < mLayout->lastByteArrayOffset()))) {
        ++mIndex;
        mCoord.goCRight(mLayout->noOfBytesPerLine() - 1);
        mBehind = false;
    }
}

void ByteArrayTableCursor::gotoCoord(Coord coord)
{
    mIndex = mLayout->indexAtCoord(coord);
    mCoord = coord;
    mBehind = false;
}

void ByteArrayTableCursor::updateCoord()
{
    mCoord = mLayout->coordOfIndex(mIndex);
}

// page down should be: one page minus one line
// -> if in the very first line page down will put the cursor on the same page into the last line
void ByteArrayTableCursor::gotoPageUp()
{
    const LineSize noOfLinesPerPage = mLayout->noOfLinesPerPage();
    const Address newIndex = mIndex - noOfLinesPerPage * mLayout->noOfBytesPerLine();
    if (newIndex >= mLayout->byteArrayOffset()) {
        mIndex = newIndex;
        mCoord.goUp(noOfLinesPerPage);
        if (mBehind && !atLineEnd()) {
            ++mIndex;
            mCoord.goRight();
            mBehind = false;
        }
    } else {
        gotoStart();
    }
}

void ByteArrayTableCursor::gotoPageDown()
{
    const LineSize noOfLinesPerPage = mLayout->noOfLinesPerPage();
    const Address newIndex = mIndex + noOfLinesPerPage * mLayout->noOfBytesPerLine();
    if (newIndex <= mLayout->lastByteArrayOffset()) {
        mIndex = newIndex;
        mCoord.goDown(noOfLinesPerPage);
    } else {
        gotoEnd();
    }
}

Address ByteArrayTableCursor::validIndex() const
{
    return (mLayout->byteArrayOffset() <= mIndex && mIndex <= mLayout->lastByteArrayOffset()) ? mIndex : -1;
}

Address ByteArrayTableCursor::indexAtLineStart() const { return mLayout->indexAtFirstLinePosition(mCoord.line()); }
Address ByteArrayTableCursor::indexAtLineEnd()   const { return mLayout->indexAtLastLinePosition(mCoord.line()); }

bool ByteArrayTableCursor::atStart()     const { return mIndex == mLayout->byteArrayOffset(); }
bool ByteArrayTableCursor::atEnd()       const { return realIndex() == mLayout->lastByteArrayOffset() + 1; }
bool ByteArrayTableCursor::atAppendPos() const { return mIndex == mLayout->lastByteArrayOffset() + 1; }

bool ByteArrayTableCursor::atLineStart() const { return mLayout->atFirstLinePosition(mCoord); }
bool ByteArrayTableCursor::atLineEnd()   const { return mLayout->atLastLinePosition(mCoord); }

// TODO: oldLength is a hack, as DataLayout is already updated and used by e.g. gotoCIndex
void ByteArrayTableCursor::adaptToChanges(const ArrayChangeMetricsList& changeList, Size oldLength)
{
    for (const ArrayChangeMetrics& change : changeList) {
        // cursor affected?
        if (mIndex >= change.offset()) {
            switch (change.type())
            {
            case ArrayChangeMetrics::Replacement:
                oldLength += change.lengthChange();
                if (oldLength > 0) {
                    const Address newIndex =
                        // cursor behind the removed section?
                        (mIndex >= change.offset() + change.removeLength()) ? mIndex + change.lengthChange() :
                        // cursor at substituted section?
                        (mIndex < change.offset() + change.insertLength()) ?  mIndex :
                        // cursor at unsubstituted section
                                                                              change.offset() + change.insertLength();

                    // if the cursor gets behind, it will never get inside again.
                    if (newIndex >= oldLength) {
                        gotoEnd();
                        return;
                    }
                    mIndex = newIndex;
                }
                // if the cursor gets at the start, it will stay there
                else {
                    gotoStart();
                    return;
                }
                break;
            case ArrayChangeMetrics::Swapping:
                if (mIndex < change.secondStart()) {
                    mIndex += change.secondLength();
                } else if (mIndex <= change.secondEnd()) {
                    mIndex -= change.firstLength();
                }
                break;
            default:
                ;
            }
        }
    }

    const bool wasBehind = (mIndex >= oldLength);
    if (wasBehind) {
        mIndex = oldLength - 1;
    }
    updateCoord();
    if (wasBehind) {
        stepToEnd();
    }
}

}
