/**
 * UGENE - Integrated Bioinformatics Tools.
 * Copyright (C) 2008-2023 UniPro <ugene@unipro.ru>
 * http://ugene.net
 *
 * 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 "MuscleParallel.h"
#include "MuscleWorkPool.h"

#define TRACE 0

namespace U2 {

struct Range {
    Range()
        : m_uBestColLeft(0), m_uBestColRight(0) {
    }

    unsigned m_uBestColLeft;
    unsigned m_uBestColRight;
};

static void ListVertSavings(unsigned uColCount, unsigned uAnchorColCount, const Range* Ranges, unsigned uRangeCount) {
    MuscleContext* ctx = getMuscleContext();
    if (!ctx->params.g_bVerbose || !ctx->params.g_bAnchors)
        return;
    double dTotalArea = uColCount * uColCount;
    double dArea = 0.0;
    for (unsigned i = 0; i < uRangeCount; ++i) {
        unsigned uLength = Ranges[i].m_uBestColRight - Ranges[i].m_uBestColLeft;
        dArea += uLength * uLength;
    }
    double dPct = (dTotalArea - dArea) * 100.0 / dTotalArea;
    Log("Anchor columns found       %u\n", uAnchorColCount);
    Log("DP area saved by anchors   %-4.1f%%\n", dPct);
}

static void ColsToRanges(const unsigned BestCols[], unsigned uBestColCount, unsigned uColCount, Range Ranges[]) {
    // N best columns produces N+1 vertical blocks.
    const unsigned uRangeCount = uBestColCount + 1;
    for (unsigned uIndex = 0; uIndex < uRangeCount; ++uIndex) {
        unsigned uBestColLeft = 0;
        if (uIndex > 0)
            uBestColLeft = BestCols[uIndex - 1];

        unsigned uBestColRight = uColCount;
        if (uIndex < uBestColCount)
            uBestColRight = BestCols[uIndex];

        Ranges[uIndex].m_uBestColLeft = uBestColLeft;
        Ranges[uIndex].m_uBestColRight = uBestColRight;
    }
}

// Return true if any changes made
bool RefineTask::RefineVertP(MSA* msaIn, unsigned uIters) {
    bool bAnyChanges = false;
    const unsigned uColCountIn = msaIn->GetColCount();
    const unsigned uSeqCountIn = msaIn->GetSeqCount();

    if (uColCountIn < 3 || uSeqCountIn < 3)
        return false;

    unsigned* AnchorCols = new unsigned[uColCountIn];
    unsigned uAnchorColCount;
    SetMSAWeightsMuscle(*msaIn);
    FindAnchorCols(*msaIn, AnchorCols, &uAnchorColCount);

    const unsigned uRangeCount = uAnchorColCount + 1;
    Range* Ranges = new Range[uRangeCount];

    ColsToRanges(AnchorCols, uAnchorColCount, uColCountIn, Ranges);
    ListVertSavings(uColCountIn, uAnchorColCount, Ranges, uRangeCount);

    delete[] AnchorCols;

    MSA msaOut;
    msaOut.SetSize(uSeqCountIn, 0);

    for (unsigned uSeqIndex = 0; uSeqIndex < uSeqCountIn; ++uSeqIndex) {
        const char* ptrName = msaIn->GetSeqName(uSeqIndex);
        unsigned uId = msaIn->GetSeqId(uSeqIndex);
        msaOut.SetSeqName(uSeqIndex, ptrName);
        msaOut.SetSeqId(uSeqIndex, uId);
    }

    MuscleContext* ctx = getMuscleContext();
    workpool->uRangeCount = uRangeCount;

    for (unsigned uRangeIndex = 0; uRangeIndex < uRangeCount && !ctx->isCanceled(); ++uRangeIndex) {
        workpool->uRangeIndex = uRangeIndex;
        MSA msaRange;
        const Range& r = Ranges[uRangeIndex];

        const unsigned uFromColIndex = r.m_uBestColLeft;
        const unsigned uRangeColCount = r.m_uBestColRight - uFromColIndex;

        if (0 == uRangeColCount)
            continue;
        else if (1 == uRangeColCount) {
            MSAFromColRange(*msaIn, uFromColIndex, 1, msaRange);
            MSAAppend(msaOut, msaRange);
            continue;
        }
        MSAFromColRange(*msaIn, uFromColIndex, uRangeColCount, msaRange);
        bool& bLockLeft = workpool->bLockLeft;
        bool& bLockRight = workpool->bLockRight;
        bLockLeft = (0 != uRangeIndex);
        bLockRight = (uRangeCount - 1 != uRangeIndex);
        bool bAnyChangesThisBlock = RefineHorizP(&msaRange, uIters, bLockLeft, bLockRight);
        bAnyChanges = (bAnyChanges || bAnyChangesThisBlock);

        MSAAppend(msaOut, msaRange);
    }

    delete[] Ranges;
    if (ctx->isCanceled()) {
        throw MuscleException("Canceled");
    }

#if DEBUG
    // Sanity check
    AssertMSAEqIgnoreCaseAndGaps(*msaIn, msaOut);
#endif

    if (bAnyChanges) {
        msaIn->Copy(msaOut);
    }
    return bAnyChanges;
}

}  // namespace U2
