/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */

/*
    Rosegarden
    A sequencer and musical notation editor.
    Copyright 2000-2026 the Rosegarden development team.
    See the AUTHORS file for more details.

    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.  See the file
    COPYING included with this distribution for more information.
*/

#define RG_MODULE_STRING "[SegmentPerformanceHelper]"
#define RG_NO_DEBUG_PRINT

#include "SegmentPerformanceHelper.h"

#include "BaseProperties.h"

#include "misc/Debug.h"


namespace Rosegarden
{


SegmentPerformanceHelper::IteratorVector
SegmentPerformanceHelper::getTiedNotes(Segment::iterator i)
{
    IteratorVector c;
    c.push_back(i);

    Event *e = *i;
    if (!e->isa(Note::EventType)) return c;
    Segment::iterator j(i);

    bool tiedBack = false, tiedForward = false;
    e->get<Bool>(BaseProperties::TIED_BACKWARD, tiedBack);
    e->get<Bool>(BaseProperties::TIED_FORWARD, tiedForward);

    timeT d = e->getNotationDuration();
    timeT t = e->getNotationAbsoluteTime();

    if (!e->has(BaseProperties::PITCH)) return c;
    int pitch = e->get<Int>(BaseProperties::PITCH);

    bool valid = false;

    if (tiedBack) {
        // #1171463: If we can find no preceding TIED_FORWARD event,
        // then we remove this property

        while (j != begin()) {

            --j;
            if (!(*j)->isa(Note::EventType)) continue;
            e = *j; // can reuse e because this branch always returns

            timeT t2 = e->getNotationAbsoluteTime() + e->getNotationDuration();
            if (t2 < t) break;

            if (t2 > t || !e->has(BaseProperties::PITCH) ||
                e->get<Int>(BaseProperties::PITCH) != pitch) continue;

            bool prevTiedForward = false;
            if (!e->get<Bool>(BaseProperties::TIED_FORWARD, prevTiedForward) ||
                !prevTiedForward) break;

            valid = true;
            break;
        }

        if (valid) {
            return IteratorVector();
        } else {
            (*i)->unset(BaseProperties::TIED_BACKWARD);
            return c;
        }
    }
    else if (!tiedForward) return c;

    for (;;) {
        while (++j != end() && !(*j)->isa(Note::EventType)) {
            // explicit braces around empty while statement to quiet warning
        };
        if (j == end()) return c;

        e = *j;

        timeT t2 = e->getNotationAbsoluteTime();

        if (t2 > t + d) break;
        else if (t2 < t + d || !e->has(BaseProperties::PITCH) ||
                 e->get<Int>(BaseProperties::PITCH) != pitch) continue;

        if (!e->get<Bool>(BaseProperties::TIED_BACKWARD, tiedBack) ||
            !tiedBack) break;

        d += e->getNotationDuration();
        c.push_back(j);
        valid = true;

        if (!e->get<Bool>(BaseProperties::TIED_FORWARD, tiedForward) ||
            !tiedForward) return c;
    }

    if (!valid) {
        // Related to #1171463: If we can find no following
        // TIED_BACKWARD event, then we remove this property
        (*i)->unset(BaseProperties::TIED_FORWARD);
    }

    return c;
}


bool
SegmentPerformanceHelper::getGraceAndHostNotes(Segment::iterator i,
                                               IteratorVector &graceNotes,
                                               IteratorVector &hostNotes,
                                               bool &isHostNote)
{
    if (i == end() || !(*i)->isa(Note::EventType)) return false;

    Segment::iterator j = i;
    Segment::iterator firstGraceNote = i;
    Segment::iterator firstHostNote = i;

    if ((*i)->has(BaseProperties::IS_GRACE_NOTE)  &&
        (*i)->get<Bool>(BaseProperties::IS_GRACE_NOTE)) {

        // i is a grace note.  Find the first host note following it

        while (++j != end()) {
            if ((*j)->getNotationAbsoluteTime() >
                (*i)->getNotationAbsoluteTime()) break;
            if ((*j)->getSubOrdering() < 0) continue;
            if ((*j)->isa(Note::EventType)) {
                firstHostNote = j;
                break;
            }
        }

        if (firstHostNote == i) {
            RG_DEBUG << "getGraceAndHostNotes(): REMARK: Grace note at " << (*i)->getAbsoluteTime() << " has no host note";
            return false;
        }
    } else {

        // i is a host note, but we need to ensure we have the first
        // one, not just any one

        j = i;

        while (j != begin()) {
            --j;
            if ((*j)->getNotationAbsoluteTime() <
                (*i)->getNotationAbsoluteTime()) break;
            if ((*j)->getSubOrdering() <
                (*i)->getSubOrdering()) break;
            if ((*j)->isa(Note::EventType)) {
                firstHostNote = j;
                break;
            }
        }
    }

    // firstHostNote now points to the first host note, which is
    // either the first non-grace note after i (if i was a grace note)
    // or the first note with the same time and subordering as i (if i
    // was not a grace note).

    if ((*firstHostNote)->getSubOrdering() < 0) {
        RG_WARNING << "getGraceAndHostNotes(): WARNING: Note at " << (*firstHostNote)->getAbsoluteTime() << " has subordering " << (*i)->getSubOrdering() << " but is not a grace note";
        return false;
    }

    j = firstHostNote;

    while (j != begin()) {
        --j;
        if ((*j)->getNotationAbsoluteTime() <
            (*firstHostNote)->getNotationAbsoluteTime()) break;
        if ((*j)->getSubOrdering() >= 0) continue;
        if (!(*j)->isa(Note::EventType)) continue;
        if (!(*j)->has(BaseProperties::IS_GRACE_NOTE)  ||
            !(*j)->get<Bool>(BaseProperties::IS_GRACE_NOTE)) {
            RG_WARNING << "getGraceAndHostNotes(): WARNING: Note at " << (*j)->getAbsoluteTime() << " (in trackback) has subordering " << (*j)->getSubOrdering() << " but is not a grace note";
            break;
        }
        firstGraceNote = j;
    }

    if (firstGraceNote == firstHostNote) {
        RG_DEBUG << "getGraceAndHostNotes(): REMARK: Note at " << (*firstHostNote)->getAbsoluteTime() << " has no grace notes";
        return false;
    }

    j = firstGraceNote;

    // push all of the grace notes, and notes with the same time as
    // the first host note, onto the container

    isHostNote = false;

    while (j != end()) {
        if ((*j)->isa(Note::EventType)) {
            if ((*j)->getSubOrdering() < 0) {
                if ((*j)->has(BaseProperties::IS_GRACE_NOTE)  &&
                    (*j)->get<Bool>(BaseProperties::IS_GRACE_NOTE)) {
                    graceNotes.push_back(j);
                }
            } else {
                hostNotes.push_back(j);
                if (j == i) isHostNote = true;
            }
        }
        if ((*j)->getNotationAbsoluteTime() >
            (*firstHostNote)->getNotationAbsoluteTime()) break;
        ++j;
    }

    return true;
}


timeT
SegmentPerformanceHelper::getSoundingAbsoluteTime(Segment::iterator i)
{
    timeT t = 0;

    timeT durationUnused;

    //RG_DEBUG << "getSoundingAbsoluteTime() at " << (*i)->getAbsoluteTime();

    if ((*i)->has(BaseProperties::IS_GRACE_NOTE)) {
        //RG_DEBUG << "it's a grace note";
        if (getGraceNoteTimeAndDuration(i, t, durationUnused))
            return t;
    }

    if ((*i)->has(BaseProperties::MAY_HAVE_GRACE_NOTES)) {
        //RG_DEBUG << "it's a candidate host note";
        if (getGraceNoteTimeAndDuration(i, t, durationUnused))
            return t;
    }

    return (*i)->getAbsoluteTime();
}

timeT
SegmentPerformanceHelper::getSoundingDuration(Segment::iterator i)
{
    timeT d = 0;

    timeT discard;

    //RG_DEBUG << "getSoundingDuration() at " << (*i)->getAbsoluteTime();

    if ((*i)->has(BaseProperties::IS_GRACE_NOTE)) {
        //RG_DEBUG << "it's a grace note";
        if (getGraceNoteTimeAndDuration(i, discard, d)) return d;
    }
    if ((*i)->has(BaseProperties::MAY_HAVE_GRACE_NOTES)) {
        //RG_DEBUG << "it's a candidate host note";
        if (getGraceNoteTimeAndDuration(i, discard, d)) return d;
    }

    if ((*i)->has(BaseProperties::TIED_BACKWARD)) {

        // Formerly we just returned d in this case, but now we check
        // with getTiedNotes so as to remove any bogus backward ties
        // that have no corresponding forward tie.  Unfortunately this
        // is quite a bit slower.

        //!!! optimize. at least we should add a marker property to
        //anything we've already processed from this helper this time
        //around.

        IteratorVector c(getTiedNotes(i));

        if (c.empty()) { // the tie back is valid
            return 0;
        }
    }

    if (!(*i)->has(BaseProperties::TIED_FORWARD) || !(*i)->isa(Note::EventType)) {

        d = (*i)->getDuration();

    } else {

        // tied forward but not back

        IteratorVector c(getTiedNotes(i));

        for (IteratorVector::iterator ci = c.begin();
             ci != c.end(); ++ci) {
            // cppcheck-suppress useStlAlgorithm
            d += (**ci)->getDuration();
        }
    }

    return d;
}


// In theory we can do better with tuplets, because real time has
// finer precision than timeT time.  With a timeT resolution of 960ppq
// however the difference is probably not audible

/* unused
RealTime
SegmentPerformanceHelper::getRealAbsoluteTime(iterator i)
{
    return segment().getComposition()->getElapsedRealTime
        (getSoundingAbsoluteTime(i));
}
*/

// In theory we can do better with tuplets, because real time has
// finer precision than timeT time.  With a timeT resolution of 960ppq
// however the difference is probably not audible
//
// (If we did want to do this, it'd help to have abstime->realtime
// conversion methods that accept double args in Composition)

/* unused
RealTime
SegmentPerformanceHelper::getRealSoundingDuration(iterator i)
{
    timeT t0 = getSoundingAbsoluteTime(i);
    timeT t1 = t0 + getSoundingDuration(i);

    if (t1 > segment().getEndMarkerTime()) {
        t1 = segment().getEndMarkerTime();
    }

    return segment().getComposition()->getRealTimeDifference(t0, t1);
}
*/

bool
SegmentPerformanceHelper::getGraceNoteTimeAndDuration(
        Segment::iterator eventIter, timeT &t, timeT &d)
{
    // [This code currently assumes appoggiatura.  Acciaccatura later.]

    // For our present purposes, we will assume that grace notes start
    // at the same time as their host note was intended to, and
    // "steal" a proportion of the duration of their host note.  This
    // causes the host note to start later, and be shorter, by that
    // same proportion.

    // If a host note has more than one (consecutive) grace note, they
    // should take a single cut from the grace note and divide it
    // amongst themselves.

    // To begin with we will set the proportion to 1/4, but we will
    // probably want it to be (a) something different [because I don't
    // really know what I'm doing], (b) adaptive [e.g. shorter host
    // note or more grace notes = longer proportion], (c)
    // configurable, or (d) all of the above.

    // Of course we also ought to be taking into account the notated
    // duration of the grace notes -- though in my working examples it
    // generally doesn't seem to be the case that we can always just
    // follow those.  I wonder if we can always use the grace notes'
    // notated duration if the ratio of grace note duration to host
    // note duration is less than some value?  Whatever we do, we
    // should be dividing the grace note duration up in proportion to
    // the durations of the grace notes, in situations where we have
    // more than one grace note consecutively of different durations;
    // that isn't handled at all here.

    if (eventIter == end()) return false;

    IteratorVector graceNotes, hostNotes;
    bool isHostNote;

    if (!getGraceAndHostNotes(eventIter, graceNotes, hostNotes, isHostNote)) {
        RG_DEBUG << "getGraceNoteTimeAndDuration(): REMARK: Note at " << (*eventIter)->getAbsoluteTime() << " is not a grace note, or has no grace notes";
        return false;
    }

    if (!isHostNote) {

        if (!(*eventIter)->has(BaseProperties::IS_GRACE_NOTE)  ||
            !(*eventIter)->get<Bool>(BaseProperties::IS_GRACE_NOTE)) {
            RG_WARNING << "getGraceNoteTimeAndDuration(): WARNING: Note at " << (*eventIter)->getAbsoluteTime() << " is neither grace nor host note, but was reported as suitable by getGraceAndHostNotes";
            return false;
        }
    }

    if (hostNotes.empty()) {
        RG_DEBUG << "getGraceNoteTimeAndDuration(): REMARK: Grace note at " << (*eventIter)->getAbsoluteTime() << " has no host note";
        return false;
    }

    if (graceNotes.empty()) {
        RG_DEBUG << "getGraceNoteTimeAndDuration(): REMARK: Note at " << (*eventIter)->getAbsoluteTime() << " has no grace notes";
        return false;
    }

    timeT hostNoteEarliestTime = 0;
    timeT hostNoteShortestDuration = 0;
    timeT hostNoteNotationDuration = 0;

    for (IteratorVector::iterator j = hostNotes.begin();
         j != hostNotes.end(); ++j) {

        if (j == hostNotes.begin() ||
            (**j)->getAbsoluteTime() < hostNoteEarliestTime) {
            hostNoteEarliestTime = (**j)->getAbsoluteTime();
        }
        if (j == hostNotes.begin() ||
            (**j)->getDuration() < hostNoteShortestDuration) {
            hostNoteShortestDuration = (**j)->getDuration();
        }
        if (j == hostNotes.begin() ||
            (**j)->getNotationDuration() > hostNoteNotationDuration) {
            hostNoteNotationDuration = (**j)->getNotationDuration();
        }
        (**j)->set<Bool>(BaseProperties::MAY_HAVE_GRACE_NOTES, true);
    }

    timeT graceNoteDuration = hostNoteNotationDuration / 4;
    if (graceNoteDuration > hostNoteShortestDuration / 2) {
        graceNoteDuration = hostNoteShortestDuration / 2;
    }

    if (isHostNote) {
        t = (*eventIter)->getAbsoluteTime() + graceNoteDuration;
        d = (*eventIter)->getDuration() - graceNoteDuration;
    } else {

        const timeT graceNoteTime = hostNoteEarliestTime;
        int count = 0, index = 0;
        bool found = false;
        int prevSubOrdering = 0;

        for (IteratorVector::iterator j = graceNotes.begin();
             j != graceNotes.end(); ++j) {

            bool newChord = false;

            if ((**j)->getSubOrdering() != prevSubOrdering) {
                newChord = true;
                prevSubOrdering = (**j)->getSubOrdering();
            }

            if (newChord) ++count;

            if (*j == eventIter) found = true;

            if (!found) {
                if (newChord) ++index;
            }
        }

        if (index == count) index = 0;
        if (count == 0) count = 1; // should not happen

        d = graceNoteDuration / count;
        t = graceNoteTime + d * index;
    }

    return true;
}


}
