/*
SPDX-FileCopyrightText: 2021 Hamed Masafi <hamed.masfi@gmail.com>

SPDX-License-Identifier: GPL-3.0-or-later
*/

#pragma once

#include "lcs.h"
#include "results.h"

#include <functional>

namespace Diff
{

int LIBKOMMITDIFF_EXPORT getRight(int baseLeft, const QList<LcsResult>::iterator &it);

template<typename T>
MergeResult<T> diff3(const QList<T> &base, const QList<T> &local, const QList<T> &remote, const DiffOptions<T> &opts = {})
{
    std::function<bool(const T &, const T &)> equals = [&opts](const T &s1, const T &s2) -> bool {
        return opts.equals(s1, s2);
    };
    using namespace std::placeholders;
    QList<LcsResult> withLocal = longestCommonSubsequence(base, local, equals);
    QList<LcsResult> withRemote = longestCommonSubsequence(base, remote, equals);

    QList<LcsResult>::iterator itLocal = withLocal.begin();
    QList<LcsResult>::iterator itRemote = withRemote.begin();

    auto indexLocal{0};
    auto indexRemote{0};
    int indexBase{};

    QList<MergeSegment2 *> result;

    while (itLocal != withLocal.end() && itRemote != withRemote.end()) {
        if (itLocal->leftStart == itRemote->leftStart) {
        } else if (itLocal->leftStart < itRemote->leftStart) {
            if (itRemote->leftStart > itLocal->leftEnd) {
                ++itLocal;
                continue;
            }
        } else if (itRemote->leftStart < itLocal->leftStart) {
            if (itLocal->leftStart > itRemote->leftEnd) {
                ++itRemote;
                continue;
            }
        }
        auto commonStart = std::max(itLocal->leftStart, itRemote->leftStart);
        auto commonEnd = std::min(itLocal->leftEnd, itRemote->leftEnd);

        auto seg = new MergeSegment2; // TODO: check memory leak
        seg->baseStart = commonStart;
        seg->baseSize = seg->localSize = seg->remoteSize = commonEnd - commonStart + 1;
        seg->localStart = indexLocal;
        seg->remoteStart = indexRemote;
        seg->type = MergeSegment2::Type::Unchanged;

        if (commonStart > indexBase || itRemote->rightStart > indexRemote || itLocal->rightStart > indexLocal) {
            auto segChanged = new MergeSegment2;
            segChanged->baseStart = indexBase;
            segChanged->baseSize = commonStart - indexBase;

            auto localEnd = getRight(commonStart - 1, itLocal);
            auto remoteEnd = getRight(commonStart - 1, itRemote);

            segChanged->localStart = indexLocal;
            segChanged->localSize = localEnd - segChanged->localStart + 1;

            segChanged->remoteStart = indexRemote;
            segChanged->remoteSize = remoteEnd - segChanged->remoteStart + 1;

            if (segChanged->remoteSize && segChanged->localSize)
                segChanged->type = MergeSegment2::Type::ChangedOnBoth;
            else if (segChanged->localSize)
                segChanged->type = MergeSegment2::Type::OnlyOnLocal;
            else if (segChanged->remoteSize)
                segChanged->type = MergeSegment2::Type::OnlyOnRemote;
            else
                segChanged->type = MergeSegment2::Type::Unchanged;

            if (Q_UNLIKELY(segChanged->type != MergeSegment2::Type::Unchanged))
                result << segChanged;
            else
                qDebug() << "Invalid segment detected";

            indexLocal += segChanged->localSize;
            indexRemote += segChanged->remoteSize;
        }

        seg->localStart = getRight(commonStart, itLocal);
        seg->remoteStart = getRight(commonStart, itRemote);
        seg->type = MergeSegment2::Type::Unchanged;

        result << seg;

        indexLocal = seg->localStart + seg->localSize;
        indexRemote = seg->remoteStart + seg->remoteSize;
        indexBase = commonEnd + 1;

        if (itLocal->leftEnd < indexBase)
            ++itLocal;

        if (itRemote->leftEnd < indexBase)
            ++itRemote;
    }

    if (itLocal != withLocal.end() || itRemote != withRemote.end()) {
        auto segChanged = new MergeSegment2;
        segChanged->baseStart = indexBase;
        segChanged->baseSize = base.size() - indexBase;

        segChanged->localStart = indexLocal + 1;
        segChanged->localSize = local.size() - indexLocal;

        segChanged->remoteStart = indexRemote + 1;
        segChanged->remoteSize = remote.size() - indexRemote;

        segChanged->type = MergeSegment2::Type::ChangedOnBoth;

        result << segChanged;
    }

    MergeResult<QString> ret;
    ret.base = base;
    ret.local = local;
    ret.remote = remote;
    ret.segments = result;
    return ret;
}

MergeResult<Text> LIBKOMMITDIFF_EXPORT diff3String(const QString &base, const QString &local, const QString &remote, const DiffOptions<QString> &opts = {});
}
