/* -*- 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.
*/

#ifndef RG_COMPOSITION_H
#define RG_COMPOSITION_H

#include "RealTime.h"
#include "base/Segment.h"
#include "Track.h"
#include "Configuration.h"
#include "XmlExportable.h"
#include "ColourMap.h"
#include "TriggerSegment.h"
#include "TimeSignature.h"
#include "Marker.h"

// Qt
#include <QString>
#include <QVariant>

// System
#include <utility>  // std::pair
#include <vector>
#include <set>
#include <map>
#include <string>
#include <list>


namespace Rosegarden
{


// We store tempo in quarter-notes per minute * 10^5 (hundred
// thousandths of a quarter-note per minute).  This means the maximum
// tempo in a 32-bit integer is about 21400 qpm.  We use a signed int
// for compatibility with the Event integer type -- but note that we
// use 0 (rather than -1) to indicate "tempo not set", by convention
// (though see usage of target tempo in e.g. addTempoAtTime).
// ??? I suspect keeping this in Composition.h introduces numerous unnecessary
//     dependencies on Composition.h.  Consider breaking this out into a
//     TempoT.h like TimeT.h.
typedef int tempoT;

class Quantizer;
class BasicQuantizer;
class NotationQuantizer;

class CompositionObserver;


/// A complete representation of a piece of music.
/**
 * It is a container for multiple Segment objects (m_segments), as well as
 * any associated non-Event data.
 *
 * The Composition owns the Segment objects it holds, and deletes them on
 * destruction.  See deleteSegment() and detachSegment().
 */
class ROSEGARDENPRIVATE_EXPORT Composition : public XmlExportable
{
    friend class Track; // to call notifyTrackChanged()
    friend class Segment; // to call notifySegmentRepeatChanged()

public:
    typedef SegmentMultiSet::iterator iterator;
    typedef SegmentMultiSet::const_iterator const_iterator;

    typedef std::vector<Segment *> SegmentVector;

    typedef std::map<TrackId, Track *> TrackMap;

    typedef std::vector<Marker *> MarkerVector;

    typedef std::set<TriggerSegmentRec *, TriggerSegmentCmp> TriggerSegmentSet;

    typedef std::set<TrackId> TrackIdSet;

    Composition();
    ~Composition() override;

private:
    Composition(const Composition &);
    Composition &operator=(const Composition &);
public:

    /**
     * Remove everything from the Composition.
     */
    void clear();

    /**
     * Return the absolute end time of the segment that ends last
     */
    timeT getDuration(bool withRepeats = false) const;
    void invalidateDurationCache() const;


    //////
    //
    // START AND END MARKERS

    timeT getStartMarker() const { return m_startMarker; }
    timeT getEndMarker() const { return m_endMarker; }
    bool autoExpandEnabled() const { return m_autoExpand; }

    void setStartMarker(const timeT &sM);
    void setEndMarker(const timeT &endMarker);
    void setAutoExpand(bool autoExpand) { m_autoExpand = autoExpand; }


    //////
    //
    //  INSTRUMENT & TRACK

    Track* getTrackById(TrackId track) const;

    bool haveTrack(TrackId track) const;

    /// Zero-based position on UI.
    Track* getTrackByPosition(int position) const;

    int getTrackPositionById(TrackId id) const; // -1 if not found

    TrackMap& getTracks() { return m_tracks; }

    const TrackMap& getTracks() const { return m_tracks; }

    // Reset id and position
    // unused
//    void resetTrackIdAndPosition(TrackId oldId, TrackId newId, int position);

    TrackId getMinTrackId() const;
    TrackId getMaxTrackId() const;

    const TrackIdSet &getRecordTracks() const { return m_recordTracks; }
    void setTrackRecording(TrackId trackId, bool recording);
    bool isTrackRecording(TrackId track) const;
    bool isInstrumentRecording(InstrumentId instrumentID) const;
    /// Update the list of tracks in record to match the actual tracks.
    void refreshRecordTracks();

    /**
     * rename: getSelectedTrackId()
     *
     * @see setSelectedTrack()
     */
    TrackId getSelectedTrack() const { return m_selectedTrackId; }

    InstrumentId getSelectedInstrumentId() const;

    /**
     * rename: setSelectedTrackId()
     *
     * @see getSelectedTrack()
     */
    void setSelectedTrack(TrackId trackId);

    /// Total number of tracks in the composition.
    unsigned int getNbTracks() const { return m_tracks.size(); }

    /**
     * Clear out the Track container
     */
    void clearTracks();

    /**
     * Insert a new Track.  The Composition takes over ownership of
     * the track object.
     */
    void addTrack(Track *track);

    /**
     * Delete a Track by index
     */
    // unused
//    void deleteTrack(TrackId track);

    /**
     * Detach a Track (revert ownership of the Track object to the
     * caller).
     */
    bool detachTrack(const Track *track);

    /**
     * Get the highest running track id (generated and kept
     * through addTrack)
     */
    TrackId getNewTrackId() const;

    bool hasTrack(InstrumentId) const;

    /**
     * Get the Instrument Id of a given segment.
     **/
    InstrumentId getInstrumentId(const Segment *segment) const {
        if (!segment)
            return NoInstrument;

        const Track *track = getTrackById(segment->getTrack());
        if (!track)
            return NoInstrument;

        return track->getInstrument();
    };

    /**
     * Get all segments that play on the same instrument segment s
     * plays on and start before t.
     */
    // unused SegmentMultiSet getInstrumentSegments(Segment *s, timeT t) const;

    //////
    //
    // MARKERS

    MarkerVector& getMarkers() { return m_markers; }
    const MarkerVector& getMarkers() const { return m_markers; }

    /**
     * Add a new Marker.  The Composition takes ownership of the
     * marker object.
     */
    void addMarker(Marker *marker);

    /**
     * Detach a Marker (revert ownership of the Marker object to the
     * caller).
     */
    bool detachMarker(const Marker *marker);

    // unused
//    bool isMarkerAtPosition(timeT time) const;

    void clearMarkers();

    Marker *findMarker(int markerID) const;


    //////
    //
    //  SEGMENT

    SegmentMultiSet& getSegments() { return m_segments; }
    const SegmentMultiSet& getSegments() const { return m_segments; }

    Segment* getSegmentByMarking(const QString& marking) const;

    unsigned int getNbSegments() const { return m_segments.size(); }

    /**
     * Add a new Segment and return an iterator pointing to it
     * The inserted Segment is owned by the Composition object
     */
    iterator addSegment(Segment*);

    /**
     * Delete the Segment pointed to by the specified iterator
     *
     * NOTE: The Segment is deleted from the Composition and
     * destroyed
     */
    void deleteSegment(iterator segmentIter);

    /**
     * Delete the Segment if it is part of the Composition
     * \return true if the Segment was found and deleted
     *
     * NOTE: The Segment is deleted from the composition and
     * destroyed
     */
    bool deleteSegment(const Segment*);

    /**
     * DO NOT USE THIS METHOD
     *
     * Set a Segment's start time while keeping the integrity of the
     * Composition multiset.
     *
     * The segment is removed and re-inserted from the composition
     * so the ordering is preserved.
     */
    void setSegmentStartTime(Segment*, timeT);

    /**
     * Test whether a Segment exists in this Composition.
     */
    bool contains(const Segment *);

    /**
     * Return an iterator pointing at the given Segment, or end()
     * if it does not exist in this Composition.
     */
    iterator findSegment(const Segment *);

    /**
     * Remove the Segment if it is part of the Composition,
     * but do not destroy it (passing it to addSegment again
     * would restore it correctly).
     * \return true if the Segment was found and removed
     *
     * NOTE: Many of the Segment methods will fail if the
     * Segment is not in a Composition.  You should not
     * expect to do anything meaningful with a Segment that
     * has been detached from the Composition in this way.
     */
    bool detachSegment(Segment*);

    /**
     * Add a new Segment which has been "weakly detached"
     *
     * Like addSegment(), but doesn't send the segmentAdded signal
     * nor updating refresh statuses
     */
    iterator weakAddSegment(Segment*);

    /**
     * Detach a segment which you're going to re-add (with weakAddSegment)
     * later.
     * Like detachSegment(), but without sending the segmentDeleted signal
     * nor updating refresh statuses.
     */
    bool weakDetachSegment(Segment*);

    /**
     * Get the largest number of segments that "overlap" at any one
     * time on the given track.  I have given this function a nice
     * long name to make it feel important.
     */
    int getMaxContemporaneousSegmentsOnTrack(TrackId track) const;

    /**
     * Retrieve a "vertical" index for this segment within its track.
     * Currently this is based on studying the way that segments on
     * the track overlap and returning the lowest integer such that no
     * prior starting segment that overlaps with this one would use
     * the same integer.  In future this could use proper voice
     * ordering.
     */
    int getSegmentVoiceIndex(const Segment *) const;

    /**
     * Add every segment in SegmentMultiSet
     */
    void addAllSegments(SegmentMultiSet segments);
    void addAllSegments(SegmentVector segments);

    /**
     * Detach every segment in SegmentMultiSet
     */
    void detachAllSegments(SegmentMultiSet segments);
    void detachAllSegments(SegmentVector segments);

    //////
    //
    //  TRIGGER SEGMENTS

    TriggerSegmentSet &getTriggerSegments() { return m_triggerSegments; }
    const TriggerSegmentSet &getTriggerSegments() const { return m_triggerSegments; }

    /**
     * Add a new trigger Segment with a given base pitch and base
     * velocity, and return its record.  If pitch or velocity is -1,
     * it will be taken from the first note event in the segment
     */
    TriggerSegmentRec *addTriggerSegment(Segment *, int pitch = -1, int velocity = -1);

    /**
     * Delete a trigger Segment.
     */
    // unused void deleteTriggerSegment(TriggerSegmentId);

    /**
     * Detach a trigger Segment from the Composition.
     */
    void detachTriggerSegment(TriggerSegmentId);

    /**
     * Delete all trigger Segments.
     */
    void clearTriggerSegments();

    /**
     * Return the TriggerSegmentId for the given Segment, or -1 if it is
     * not a trigger Segment.
     */
    int getTriggerSegmentId(const Segment *) const;

    /**
     * Return the Segment for a given TriggerSegmentId
     */
    Segment *getTriggerSegment(TriggerSegmentId);

    /**
     * Return the TriggerSegmentRec (with Segment, base pitch, base velocity,
     * references etc) for a given TriggerSegmentId
     */
    TriggerSegmentRec *getTriggerSegmentRec(TriggerSegmentId);

    /**
     * As above for a given Event, or nullptr if none.
     **/
    TriggerSegmentRec *getTriggerSegmentRec(Event* e);
    /**
     * Add a new trigger Segment with a given ID and base pitch and
     * velocity.  Fails and returns 0 if the ID is already in use.
     * This is intended for use from file load or from undo/redo.
     */
    TriggerSegmentRec *addTriggerSegment(Segment *, TriggerSegmentId,
                                         int pitch = -1, int velocity = -1);

    /**
     * Get the ID of the next trigger segment that will be inserted.
     */
    // unused TriggerSegmentId getNextTriggerSegmentId() const;

    /**
     * Specify the next trigger ID.  This is intended for use from file
     * load only.  Do not use this function unless you know what you're
     * doing.
     */
    void setNextTriggerSegmentId(TriggerSegmentId);

    /**
     * Update the trigger segment references for all trigger segments.
     * To be called after file load.
     */
    void updateTriggerSegmentReferences();

    /**
     * Clear refresh statuses of SegmentLinker after file load.
     */
    void resetLinkedSegmentRefreshStatuses();

    //////
    //
    //  BAR

    /**
     * Return the total number of bars in the composition
     */
    int getNbBars() const;

    /**
     * Return the number of the bar that starts at or contains time t.
     *
     * Will happily return computed bar numbers for times before
     * the start or beyond the real end of the composition.
     */
    int getBarNumber(timeT t) const;

    /**
     * Return the starting time of bar n
     */
    timeT getBarStart(int n) const {
        return getBarRange(n).first;
    }

    /**
     * Return the ending time of bar n
     */
    timeT getBarEnd(int n) const {
        return getBarRange(n).second;
    }

    /**
     * Return the time range of bar n.
     *
     * Will happily return theoretical timings for bars before the
     * start or beyond the end of composition (i.e. there is no
     * requirement that 0 <= n < getNbBars()).
     */
    std::pair<timeT, timeT> getBarRange(int n) const;

    /**
     * Return the starting time of the bar that contains time t
     */
    timeT getBarStartForTime(timeT t) const {
        return getBarRangeForTime(t).first;
    }

    /**
     * Return the ending time of the bar that contains time t
     */
    timeT getBarEndForTime(timeT t) const {
        return getBarRangeForTime(t).second;
    }

    /**
     * Return the starting and ending times of the bar that contains
     * time t.
     *
     * Will happily return theoretical timings for bars before the
     * start or beyond the end of composition.
     *
     * ??? typedef std::pair<timeT, timeT> BarRange;
     */
    std::pair<timeT, timeT> getBarRangeForTime(timeT t) const;


    //////
    //
    //  TIME SIGNATURE

    /**
     * Add the given time signature at the given time.  Returns the
     * resulting index of the time signature (suitable for passing
     * to removeTimeSignature, for example)
     */
    int addTimeSignature(timeT t, const TimeSignature& timeSig);

    /**
     * Return the time signature in effect at time t
     */
    TimeSignature getTimeSignatureAt(timeT t) const;

    /**
     * Return the time signature in effect at time t, and the time at
     * which it came into effect
     */
    timeT getTimeSignatureAt(timeT, TimeSignature &) const;

    /**
     * Return the time signature in effect in bar n.  Also sets
     * isNew to true if the time signature is a new one that did
     * not appear in the previous bar.
     */
    TimeSignature getTimeSignatureInBar(int barNo, bool &isNew) const;

    /**
     * Return the total number of time signature changes in the
     * composition.
     */
    int getTimeSignatureCount() const;

    /**
     * Return the index of the last time signature change before
     * or at the given time, in a range suitable for passing to
     * getTimeSignatureChange.  Return -1 if there has been no
     * time signature by this time.
     */
    int getTimeSignatureNumberAt(timeT t) const;

    /**
     * Return the absolute time of and time signature introduced
     * by time-signature change n.
     */
    std::pair<timeT, TimeSignature> getTimeSignatureChange(int n) const;

    /**
     * Remove time signature change event n from the composition.
     */
    void removeTimeSignature(int n);



    //////
    //
    //  TEMPO

    /**
     * Return the (approximate) number of quarters per minute for a
     * given tempo.
     */
    static double getTempoQpm(tempoT tempo) { return double(tempo) / 100000.0; }
    static tempoT getTempoForQpm(double qpm) { return tempoT(qpm * 100000 + 0.01); }

    /**
     * Return the tempo in effect at time t.  If a ramped tempo change
     * is in effect at the time, it will be properly interpolated and
     * a computed value returned.
     */
    tempoT getTempoAtTime(timeT t) const;

    /**
     * Return the tempo in effect at the current playback position.
     */
    tempoT getCurrentTempo() const;

    /**
     * Set a default tempo for the composition.  This will be
     * overridden by any tempo events encountered during playback.
     */
    void setCompositionDefaultTempo(tempoT tempo) { m_defaultTempo = tempo; }
    tempoT getCompositionDefaultTempo() const { return m_defaultTempo; }

    /**
     * Add a tempo-change event at the given time, to the given tempo.
     * Removes any existing tempo event at that time.  Returns the
     * index of the new tempo event in a form suitable for passing to
     * removeTempoChange.
     *
     * If targetTempo == -1, adds a single constant tempo change.
     * If targetTempo == 0, adds a smooth tempo ramp from this tempo
     * change to the next.
     * If targetTempo > 0, adds a smooth tempo ramp from this tempo
     * ending at targetTempo at the time of the next tempo change.
     */
    int addTempoAtTime(timeT time, tempoT tempo, tempoT targetTempo = -1);

    /**
     * Return the number of tempo changes in the composition.
     */
    int getTempoChangeCount() const;

    /**
     * Return the index of the last tempo change before the given
     * time, in a range suitable for passing to getTempoChange.
     * Return -1 if the default tempo is in effect at this time.
     */
    int getTempoChangeNumberAt(timeT t) const;

    /**
     * Return the absolute time of and tempo introduced by tempo
     * change number n.  If the tempo is ramped, this returns only
     * the starting tempo.
     */
    std::pair<timeT, tempoT> getTempoChange(int n) const;

    /**
     * Return whether the tempo change number n is a ramped tempo or
     * not, and if it is, return the target tempo for the ramp.
     *
     * If calculate is false, return a target tempo of 0 if the tempo
     * change is defined to ramp to the following tempo.  If calculate
     * is true, return a target tempo equal to the following tempo in
     * this case.
     */
    std::pair<bool, tempoT> getTempoRamping(int n, bool calculate = true) const;

    /**
     * Remove tempo change event n from the composition.
     */
    void removeTempoChange(int n);

    /**
     * Get the slowest assigned tempo in the composition.
     */
    tempoT getMinTempo() const {
        return ((m_minTempo != 0) ? m_minTempo : m_defaultTempo);
    }

    /**
     * Get the fastest assigned tempo in the composition.
     */
    tempoT getMaxTempo() const {
        return ((m_maxTempo != 0) ? m_maxTempo : m_defaultTempo);
    }

    /// Compare time signatures and tempos between two Composition objects.
    bool compareSignaturesAndTempos(const Composition &other) const;


    //////
    //
    //  REAL TIME

    /**
     * Return the number of microseconds elapsed between
     * the beginning of the composition and the given timeT time.
     * (timeT units are independent of tempo; this takes into
     * account any tempo changes in the first t units of time.)
     *
     * This is a fairly efficient operation, not dependent on the
     * magnitude of t or the number of tempo changes in the piece.
     */
    RealTime getElapsedRealTime(timeT t) const;

    /**
     * Return the nearest time in timeT units to the point at the
     * given number of microseconds after the beginning of the
     * composition.  (timeT units are independent of tempo; this takes
     * into account any tempo changes in the first t microseconds.)
     * The result will be approximate, as timeT units are obviously
     * less precise than microseconds.
     *
     * This is a fairly efficient operation, not dependent on the
     * magnitude of t or the number of tempo changes in the piece.
     */
    timeT getElapsedTimeForRealTime(RealTime t) const;

    /**
     * Return the number of microseconds elapsed between
     * the two given timeT indices into the composition, taking
     * into account any tempo changes between the two times.
     */
    RealTime getRealTimeDifference(timeT t0, timeT t1) const {
        if (t1 > t0) return getElapsedRealTime(t1) - getElapsedRealTime(t0);
        else         return getElapsedRealTime(t0) - getElapsedRealTime(t1);
    }

    static tempoT
        timeRatioToTempo(const RealTime &realTime,
                         timeT beatTime, tempoT rampTo);

    //////
    //
    //  OTHER TIME CONVERSIONS

    /**
     * Return (by reference) the bar number and beat/division values
     * corresponding to a given absolute time.
     */
    void getMusicalTimeForAbsoluteTime(timeT absTime,
                                       int &bar, int &beat,
                                       int &fraction, int &remainder) const;

    /**
     * Return (by reference) the number of bars and beats/divisions
     * corresponding to a given duration.  The absolute time at which
     * the duration starts is also required, so as to know the correct
     * time signature.
     */
    void getMusicalTimeForDuration(timeT absTime, timeT duration,
                                   int &bars, int &beats,
                                   int &fractions, int &remainder) const;

    /**
     * Return the absolute time corresponding to a given bar number
     * and beat/division values.
     */
    timeT getAbsoluteTimeForMusicalTime(int bar, int beat,
                                        int fraction, int remainder) const;

    /**
     * Return the duration corresponding to a given number of bars and
     * beats/divisions.  The absolute time at which the duration
     * starts is also required, so as to know the correct time
     * signature.
     */
    timeT getDurationForMusicalTime(timeT absTime,
                                    int bars, int beats,
                                    int fractions, int remainder) const;

    enum class TimeMode {
        MusicalTime,
        RealTime,
        RawTime
    };
    /// Convert a time in MIDI Ticks (timeT) to various human-readable formats.
    QString makeTimeString(timeT midiTicks, TimeMode timeMode) const;
    /// Convert a duration to various human-readable formats.
    QString makeDurationString(timeT time, timeT duration, TimeMode timeMode) const;
    /// Convert MIDI ticks to a QVariant suitable for a table key column.
    QVariant makeTimeVariant(timeT midiTicks, TimeMode timeMode) const;

    //////
    //
    // LOOP

    enum LoopMode { LoopOff, LoopOn, LoopAll };
    void setLoopMode(LoopMode loopMode)  { m_loopMode = loopMode; }
    void setLoopStart(const timeT &t)  { m_loopStart = t; }
    void setLoopEnd(const timeT &t)  { m_loopEnd = t; }

    LoopMode getLoopMode() const  { return m_loopMode; }
    timeT getLoopStart() const  { return m_loopStart; }
    timeT getLoopEnd() const  { return m_loopEnd; }



    //////
    //
    // OTHER STUFF


    // Some set<> API delegation
    /// Segment begin iterator.
    iterator       begin()       { return m_segments.begin(); }
    /// Segment begin iterator.
    const_iterator begin() const { return m_segments.begin(); }
    /// Segment end iterator.
    iterator       end()         { return m_segments.end(); }
    /// Segment end iterator.
    const_iterator end() const   { return m_segments.end(); }


    // XML exportable method
    //
    std::string toXmlString() const override;

    // Who's making this racket?
    //
    Configuration &getMetadata() {
        return m_metadata;
    }
    const Configuration &getMetadata() const {
        return m_metadata;
    }

    std::string getCopyrightNote() const {
        return m_metadata.get<String>(CompositionMetadataKeys::Copyright,
                                      "");
    }
    void setCopyrightNote(const std::string &cr) {
        m_metadata.set<String>(CompositionMetadataKeys::Copyright, cr);
    }


    // We can have the metronome on or off while playing or
    // recording - get and set values from here
    //
    bool usePlayMetronome() const { return m_playMetronome; }
    bool useRecordMetronome() const { return m_recordMetronome; }

    void setPlayMetronome(bool value);
    void setRecordMetronome(bool value);


    // Colour stuff
    ColourMap& getSegmentColourMap() { return m_segmentColourMap; }
    const ColourMap& getSegmentColourMap() const { return m_segmentColourMap; }
    void setSegmentColourMap(const ColourMap &newmap);

    // General colourmap for non-segments
    //
    ColourMap& getGeneralColourMap() { return m_generalColourMap; }
    void setGeneralColourMap(const ColourMap &newmap);

    /// NotationView spacing
    int m_notationSpacing;


    //////
    //
    // QUANTIZERS

    /**
     * Return a quantizer that quantizes to the our most basic
     * units (i.e. a unit quantizer whose unit is our shortest
     * note duration).
     */
    const BasicQuantizer *getBasicQuantizer() const {
        return m_basicQuantizer;
    }

    /**
     * Return a quantizer that does quantization for notation
     * only.
     */
    const NotationQuantizer *getNotationQuantizer() const {
        return m_notationQuantizer;
    }


    //////
    //
    // REFRESH STATUS

    // delegate RefreshStatusArray API
    unsigned int getNewRefreshStatusId() {
        return m_refreshStatusArray.getNewRefreshStatusId();
    }

    RefreshStatus& getRefreshStatus(unsigned int id) {
        return m_refreshStatusArray.getRefreshStatus(id);
    }

    /// Set all refresh statuses to true
    void updateRefreshStatuses() {
        m_refreshStatusArray.updateRefreshStatuses();
    }


    /// Change notification mechanism.
    /// @see removeObserver()
    /// @see notifyTracksAdded()
    void    addObserver(CompositionObserver *obs) { m_observers.push_back(obs); }
    /// Change notification mechanism.
    /// @see addObserver()
    // cppcheck-suppress constParameterPointer
    void removeObserver(CompositionObserver *obs) { m_observers.remove(obs); }

    /// Change notification mechanism.
    /**
     * See the various other "notify*" functions.
     *
     * These functions have been made public in the interests of improving
     * performance.  There are at least two main approaches to sending change
     * notifications to observers.  The first is to have each modifier
     * function (e.g. deleteTrack()) send the change notification.  The second
     * is to make the change notification functions public and let the code
     * that modifies the object also call the change notification function.
     * The first approach is convenient and less likely to be forgotten.  The
     * second approach has the advantage of performance in situations where
     * there are many changes being made at once.  All of the changes can be
     * made, then a single notification can be sent out once the changes are
     * complete.  With the first approach, many change notifications might get
     * sent out, each of which might lead to a potentially costly UI update.
     */
    void notifyTracksAdded(std::vector<TrackId> trackIds) const;
    /// Change notification mechanism.
    /**
     * @see notifyTracksAdded()
     */
    void notifyTrackChanged(Track*);
    /// Change notification mechanism.
    /**
     * @see notifyTracksAdded()
     */
    void notifyTracksDeleted(std::vector<TrackId> trackIds) const;
    /// Call when the selected track is changed.
    /**
     * @see setSelectedTrack()
     * @see notifyTracksAdded()
     */
    void notifyTrackSelectionChanged(TrackId) const;

    //////
    // LYRICS WITH REPEATED SEGMENTS
    void distributeVerses();

    /// Follow playback for Matrix and Notation.
    bool getEditorFollowPlayback() const { return m_editorFollowPlayback; }
    /// Follow playback for Matrix and Notation.
    void setEditorFollowPlayback(bool b) { m_editorFollowPlayback = b; }
    /// Follow playback for the main window.
    bool getMainFollowPlayback() const { return m_mainFollowPlayback; }
    /// Follow playback for the main window.
    void setMainFollowPlayback(bool b) { m_mainFollowPlayback = b; }

    //////
    // DEBUG FACILITIES
    void dump() const;

protected:

    static const std::string TempoEventType;
    static const PropertyName TempoProperty;
    static const PropertyName TargetTempoProperty;

    static const PropertyName NoAbsoluteTimeProperty;
    static const PropertyName BarNumberProperty;
    static const PropertyName TempoTimestampProperty;

    // Compares Event times.
    struct ReferenceSegmentEventCmp
    {
        bool operator()(const Event &e1, const Event &e2) const;
        bool operator()(const Event *e1, const Event *e2) const {
            return operator()(*e1, *e2);
        }
    };

    struct BarNumberComparator
    {
        bool operator()(const Event &e1, const Event &e2) const {
            return (e1.get<Int>(BarNumberProperty) <
                    e2.get<Int>(BarNumberProperty));
        }
        bool operator()(const Event *e1, const Event *e2) const {
            return operator()(*e1, *e2);
        }
    };

    /**
     * Ensure the selected and record trackids still point to something valid
     * Must be called after deletion of detach of a track
     */
    void checkSelectedAndRecordTracks();
    TrackId getClosestValidTrackId(TrackId id) const;


    //--------------- Data members ---------------------------------
    //
    TrackMap m_tracks;
    SegmentMultiSet m_segments;

    // The tracks we are armed for record on
    //
    TrackIdSet m_recordTracks;

    TrackId m_selectedTrackId;

    /**
     * This is a bit like a segment, but can only contain one type of
     * event, and can only have one event at each absolute time.
     *
     * Composition has two instances of this: m_timeSigSegment and
     * m_tempoSegment.
     */
    class ReferenceSegment
    {
    public:
        explicit ReferenceSegment(const std::string& eventType);
        ~ReferenceSegment();

        typedef std::vector<Event*>::size_type size_type;
        typedef std::vector<Event*>::iterator iterator;
        typedef std::vector<Event*>::const_iterator const_iterator;

        iterator begin();
        const_iterator begin() const;
        iterator end();
        const_iterator end() const;

        size_type size() const;
        bool empty() const;
        iterator erase(iterator position);
        void clear();

        Event* operator[] (size_type n);
        const Event* operator[] (size_type n) const;

        timeT getDuration() const;

        /// Inserts a single event, removing any existing one at that time
        iterator insertEvent(Event *e); // may throw Event::BadType

        void eraseEvent(Event *e);

        /// Find the Event at or before the given time (pulses).
        /**
         * Returns end() if there is no Event at or prior to the given time.
         */
        iterator findAtOrBefore(timeT t);

        /// Find the Event at or before the given real time (seconds).
        /**
         * Returns end() if there is no Event at or prior to the given time.
         */
        iterator findAtOrBefore(RealTime t);

        std::string getEventType() const { return m_eventType; }

    private:
        // Hide copy ctor and op=.
        ReferenceSegment(const ReferenceSegment &);
        ReferenceSegment &operator=(const ReferenceSegment &);

        /// This class can only hold exactly one event type.
        /**
         * Tempo and TimeSig are the event types this might hold.
         */
        std::string m_eventType;

        // The vector of Event objects.
        // not a set: want random access for bars
        std::vector<Event*> m_events;
        /// Find the Event at or after e.
        iterator find(Event *e);
    };

    /// Contains time signature events
    mutable ReferenceSegment m_timeSigSegment;

    /// Contains tempo events
    mutable ReferenceSegment m_tempoSegment;

    /// affects m_timeSigSegment
    void calculateBarPositions() const;
    mutable bool m_barPositionsNeedCalculating;
    ReferenceSegment::iterator getTimeSignatureAtAux(timeT t) const;

    /// affects m_tempoSegment
    void calculateTempoTimestamps() const;
    mutable bool m_tempoTimestampsNeedCalculating;
    static RealTime time2RealTime(timeT t, tempoT tempo);
    static RealTime time2RealTime(timeT time, tempoT tempo,
                                  timeT targetTime, tempoT targetTempo);
    static timeT realTime2Time(RealTime rt, tempoT tempo);
    static timeT realTime2Time(RealTime rt, tempoT tempo,
                               timeT targetTime, tempoT targetTempo);
    bool getTempoTarget(ReferenceSegment::const_iterator i,
                        tempoT &target,
                        timeT &targetTime) const;

    static RealTime getTempoTimestamp(const Event *e);
    static void setTempoTimestamp(Event *e, RealTime t);

    /// No more than one armed track per instrument.
    void enforceArmRule(const Track *track);

    typedef std::list<CompositionObserver *> ObserverSet;
    ObserverSet m_observers;

    void notifySegmentAdded(Segment *) const;
    void notifySegmentRemoved(Segment *) const;
    void notifySegmentRepeatChanged(Segment *, bool) const;
    /**
     * ??? This is always called with segment->getRepeatEndTime() for the
     *     repeatEndTime argument.  Get rid of the second argument and call
     *     getRepeatEndTime() within this routine instead.
     */
    void notifySegmentRepeatEndChanged(Segment *segment, timeT repeatEndTime) const;
    void notifySegmentEventsTimingChanged(Segment *s, timeT delay, RealTime rtDelay) const;
    void notifySegmentTransposeChanged(Segment *s, int transpose) const;
    void notifySegmentTrackChanged(Segment *s, TrackId oldId, TrackId newId) const;
    void notifySegmentStartChanged(Segment *, timeT);
    void notifySegmentEndMarkerChange(Segment *s, bool shorten);
    void notifyEndMarkerChange(bool shorten) const;
    void notifyMetronomeChanged() const;
    void notifyTimeSignatureChanged() const;
    void notifyTempoChanged() const;
    void notifySelectedTrackChanged() const;
    void notifySourceDeletion() const;

    void clearVoiceCaches();
    void rebuildVoiceCaches() const;

    void updateExtremeTempos();

    BasicQuantizer                   *m_basicQuantizer;
    NotationQuantizer                *m_notationQuantizer;

    tempoT                            m_defaultTempo;
    tempoT                            m_minTempo; // cached from tempo segment
    tempoT                            m_maxTempo; // cached from tempo segment

    // Notional Composition markers - these define buffers for the
    // start and end of the piece, Segments can still exist outside
    // of these markers - these are for visual and playback cueing.
    //
    timeT                             m_startMarker;
    timeT                             m_endMarker;
    bool                              m_autoExpand;

    // Duration Cache.  See getDuration().
    mutable timeT m_durationWithRepeats = 0;
    mutable bool m_durationWithRepeatsDirty = true;
    mutable timeT m_durationWithoutRepeats = 0;
    mutable bool m_durationWithoutRepeatsDirty = true;

    // Loop
    LoopMode m_loopMode = LoopOff;
    timeT m_loopStart = 0;
    timeT m_loopEnd = 0;

    Configuration                     m_metadata;

    bool                              m_playMetronome;
    bool                              m_recordMetronome;

    RefreshStatusArray<RefreshStatus> m_refreshStatusArray;

    /// Sorted vector of Markers.  See addMarker().
    MarkerVector m_markers;

    // Trigger segments (unsorted segments fired by events elsewhere)
    //
    TriggerSegmentSet           m_triggerSegments;
    TriggerSegmentId                  m_nextTriggerSegmentId;

    ColourMap                         m_segmentColourMap;
    ColourMap                         m_generalColourMap;

    // Caches of segment voice indices and track voice counts
    //
    mutable std::map<TrackId, int>    m_trackVoiceCountCache;
    mutable std::map<const Segment *, int>  m_segmentVoiceIndexCache;

    /// Follow playback for Matrix and Notation.
    bool                              m_editorFollowPlayback;
    /// Follow playback for the main window.
    bool                              m_mainFollowPlayback;
};


/// Base class for those that want notification of Composition changes.
/**
 * If you subclass from CompositionObserver, you can then attach to a
 * Composition to receive notification when something changes.
 *
 * Normally all the methods in this class would be pure virtual.  But
 * because there are so many, that imposes far too much work on the
 * subclass implementation in a case where it only really wants to
 * know about one thing, such as segments being deleted.  So we have
 * empty default implementations, and you'll just have to take a bit
 * more care to make sure you really are making the correct
 * declarations in the subclass.
 */
class CompositionObserver
{
public:
    CompositionObserver() : m_compositionDeleted(false)  { }

    virtual ~CompositionObserver()  { }

    /// A segment has been added to the Composition.
    virtual void segmentAdded(const Composition *, Segment *) { }

    /// A Segment has been removed from the Composition.
    /**
     * Called before the Segment is deleted.
     */
    virtual void segmentRemoved(const Composition *, Segment *) { }

    /// The Segment's repeat status has changed
    virtual void segmentRepeatChanged(const Composition *, Segment *, bool) { }

    /// The Segment's repeat end time has changed.
    virtual void segmentRepeatEndChanged(const Composition *, Segment *, timeT) { }

    /// The Segment's delay timing has changed.
    virtual void segmentEventsTimingChanged(const Composition *, Segment *,
                                            timeT /* delay */,
                                            RealTime /* rtDelay */) { }

    /// The Segment's transpose value has changed
    virtual void segmentTransposeChanged(const Composition *, Segment *,
                                         int /* transpose */) { }

    /// The Segment's start time has changed.
    virtual void segmentStartChanged(const Composition *, Segment *,
				     timeT /* newStartTime */) { }

    /// The Segment's end marker time has changed
    virtual void segmentEndMarkerChanged(const Composition *, Segment *,
                                         bool /* shorten */) { }

    /// The Segment's track has changed.
    virtual void segmentTrackChanged(const Composition *, Segment *,
                                     TrackId) { }

    /// The Composition's end time has been changed.
    /**
     * ??? rename: endTimeChanged() to differentiate from SegmentObserver.
     */
    virtual void endMarkerTimeChanged(const Composition *,
                                      bool /* shorten */) { }

    /// A different track has been selected.
    /**
     * This can happen when the user clicks on a track label, or presses
     * the up/down arrow keys to change which track is currently selected.
     *
     * See selectedTrackChanged().
     */
    virtual void trackSelectionChanged(const Composition *, TrackId) { }

    /// A Track has changed (instrument id, muted status...)
    virtual void trackChanged(const Composition *, Track *) { }

    virtual void tracksDeleted(const Composition *,
                               std::vector<TrackId> & /*trackIds*/) { }

    virtual void tracksAdded(const Composition *,
                             std::vector<TrackId> & /*trackIds*/) { }

    /// Some time signature has changed.
    virtual void timeSignatureChanged(const Composition *) { }

    /// Metronome status has changed (on/off)
    virtual void metronomeChanged(const Composition *) { }

    virtual void tempoChanged(const Composition *) { }

    /// Like trackChanged() but for the Track that is selected.
    /**
     * This avoids the need to check the TrackId with trackChanged().
     *
     * See trackChanged().
     *
     * ??? We can probably get rid of this easily and just use
     *     trackChanged().
     */
    virtual void selectedTrackChanged(const Composition *) { }


    /// Called from the Composition dtor.
    virtual void compositionDeleted(const Composition *) {
        m_compositionDeleted = true;
    }

    bool isCompositionDeleted() const { return m_compositionDeleted; }

protected:
    bool m_compositionDeleted;
};

}


#endif
