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

/*
    Rosegarden
    A MIDI and audio sequencer and musical notation editor.
 
    This program is Copyright 2000-2007
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <richard.bown@ferventsoftware.com>
 
    The moral rights of Guillaume Laurent, Chris Cannam, and Richard
    Bown to claim authorship of this work have been asserted.
 
    Other copyrights also apply to some parts of this work.  Please
    see the AUTHORS file and individual file headers for 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.
*/

#include <cmath>
#include "CompositionModelImpl.h"

#include "base/BaseProperties.h"
#include "misc/Debug.h"
#include "misc/Strings.h"
#include "AudioPreviewThread.h"
#include "AudioPreviewUpdater.h"
#include "base/Composition.h"
#include "base/Event.h"
#include "base/MidiProgram.h"
#include "base/NotationTypes.h"
#include "base/Profiler.h"
#include "base/RulerScale.h"
#include "base/Segment.h"
#include "base/Selection.h"
#include "base/SnapGrid.h"
#include "base/Studio.h"
#include "base/Track.h"
#include "CompositionItemHelper.h"
#include "CompositionItemImpl.h"
#include "CompositionModel.h"
#include "CompositionRect.h"
#include "AudioPreviewPainter.h"
#include "gui/general/GUIPalette.h"
#include "SegmentOrderer.h"
#include <qbrush.h>
#include <qcolor.h>
#include <qpen.h>
#include <qpoint.h>
#include <qrect.h>
#include <qregexp.h>
#include <qsize.h>
#include <qstring.h>


namespace Rosegarden
{

CompositionModelImpl::CompositionModelImpl(Composition& compo,
        Studio& studio,
        RulerScale *rulerScale,
        int vStep)
        : m_composition(compo),
        m_studio(studio),
        m_grid(rulerScale, vStep),
        m_pointerTimePos(0),
        m_audioPreviewThread(0)
{
    m_notationPreviewDataCache.setAutoDelete(true);
    m_audioPreviewDataCache.setAutoDelete(true);
    m_composition.addObserver(this);

    const Composition::segmentcontainer& segments = m_composition.getSegments();
    Composition::segmentcontainer::iterator segEnd = segments.end();

    for (Composition::segmentcontainer::iterator i = segments.begin();
            i != segEnd; ++i) {

        (*i)->addObserver(this);
    }
}

CompositionModelImpl::~CompositionModelImpl()
{
    RG_DEBUG << "CompositionModelImpl::~CompositionModelImpl()" << endl;

    if (!isCompositionDeleted()) {

        m_composition.removeObserver(this);

        const Composition::segmentcontainer& segments = m_composition.getSegments();
        Composition::segmentcontainer::iterator segEnd = segments.end();

        for (Composition::segmentcontainer::iterator i = segments.begin();
                i != segEnd; ++i) {

            (*i)->removeObserver(this);
        }
    }

    RG_DEBUG << "CompositionModelImpl::~CompositionModelImpl(): removal from Segment & Composition observers OK" << endl;

    if (m_audioPreviewThread) {
        while (!m_audioPreviewUpdaterMap.empty()) {
            // Cause any running previews to be cancelled
            delete m_audioPreviewUpdaterMap.begin()->second;
            m_audioPreviewUpdaterMap.erase(m_audioPreviewUpdaterMap.begin());
        }
    }
}

struct RectCompare {
    bool operator()(const QRect &r1, const QRect &r2) const {
        return r1.x() < r2.x();
    }
};

void CompositionModelImpl::makeNotationPreviewRects(RectRanges* npRects, QPoint basePoint,
        const Segment* segment, const QRect& clipRect)
{

    rectlist* cachedNPData = getNotationPreviewData(segment);

    if (cachedNPData->empty())
        return ;

    rectlist::iterator npEnd = cachedNPData->end();

    rectlist::iterator npi = std::lower_bound(cachedNPData->begin(), npEnd, clipRect, RectCompare());

    if (npi == npEnd)
        return ;

    if (npi != cachedNPData->begin())
        --npi;

    RectRange interval;

    interval.range.first = npi;

    int segEndX = int(nearbyint(m_grid.getRulerScale()->getXForTime(segment->getEndMarkerTime())));
    int xLim = std::min(clipRect.topRight().x(), segEndX);

    //     RG_DEBUG << "CompositionModelImpl::makeNotationPreviewRects : basePoint.x : "
    //              << basePoint.x() << endl;

    // move iterator forward
    //
    while (npi->x() < xLim && npi != npEnd)
        ++npi;

    interval.range.second = npi;
    interval.basePoint.setX(0);
    interval.basePoint.setY(basePoint.y());
    interval.color = computeSegmentPreviewColor(segment);

    npRects->push_back(interval);
}

void CompositionModelImpl::makeNotationPreviewRectsMovingSegment(RectRanges* npRects, QPoint basePoint,
        const Segment* segment, const QRect& currentSR)
{
    CompositionRect unmovedSR = computeSegmentRect(*segment);

    rectlist* cachedNPData = getNotationPreviewData(segment);

    if (cachedNPData->empty())
        return ;

    rectlist::iterator npEnd = cachedNPData->end(),
                               npBegin = cachedNPData->begin();

    rectlist::iterator npi;

    if (getChangeType() == ChangeResizeFromStart)
        npi = std::lower_bound(npBegin, npEnd, currentSR, RectCompare());
    else
        npi = std::lower_bound(npBegin, npEnd, unmovedSR, RectCompare());

    if (npi == npEnd)
        return ;

    if (npi != npBegin && getChangeType() != ChangeResizeFromStart) {
        --npi;
    }

    RectRange interval;

    interval.range.first = npi;

    int xLim = getChangeType() == ChangeMove ? unmovedSR.topRight().x() : currentSR.topRight().x();

    //     RG_DEBUG << "CompositionModelImpl::makeNotationPreviewRectsMovingSegment : basePoint.x : "
    //              << basePoint.x() << endl;

    // move iterator forward
    //
    while (npi->x() < xLim && npi != npEnd)
        ++npi;

    interval.range.second = npi;
    interval.basePoint.setY(basePoint.y());

    if (getChangeType() == ChangeMove)
        interval.basePoint.setX(basePoint.x() - unmovedSR.x());
    else
        interval.basePoint.setX(0);

    interval.color = computeSegmentPreviewColor(segment);

    npRects->push_back(interval);
}

void CompositionModelImpl::makeAudioPreviewRects(AudioPreviewDrawData* apRects, const Segment* segment,
        const CompositionRect& segRect, const QRect& clipRect)
{
    Profiler profiler("CompositionModelImpl::makeAudioPreviewRects", true);
    RG_DEBUG << "CompositionModelImpl::makeAudioPreviewRects - segRect = " << segRect << endl;

    PixmapArray previewImage = getAudioPreviewPixmap(segment);

    QPoint basePoint = segRect.topLeft();

    AudioPreviewDrawDataItem previewItem(previewImage, basePoint, segRect);

    if (getChangeType() == ChangeResizeFromStart) {
        CompositionRect originalRect = computeSegmentRect(*segment);
        previewItem.resizeOffset = segRect.x() - originalRect.x();
    }

    apRects->push_back(previewItem);
}

void CompositionModelImpl::computeRepeatMarks(CompositionItem& item)
{
    Segment* s = CompositionItemHelper::getSegment(item);
    CompositionRect& sr = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)item)->getCompRect();
    computeRepeatMarks(sr, s);
}

void CompositionModelImpl::computeRepeatMarks(CompositionRect& sr, const Segment* s)
{
    if (s->isRepeating()) {

        timeT startTime = s->getStartTime();
        timeT endTime = s->getEndMarkerTime();
        timeT repeatInterval = endTime - startTime;

        if (repeatInterval <= 0) {
            //	    std::cerr << "WARNING: CompositionModelImpl::computeRepeatMarks: Segment at " << startTime << " has repeatInterval " << repeatInterval << std::endl;
            //	    std::cerr << kdBacktrace() << std::endl;
            return ;
        }

        timeT repeatStart = endTime;
        timeT repeatEnd = s->getRepeatEndTime();
        sr.setWidth(int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime,
                                  repeatEnd - startTime))));

        CompositionRect::repeatmarks repeatMarks;

        //         RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : repeatStart = "
        //                  << repeatStart << " - repeatEnd = " << repeatEnd << endl;

        for (timeT repeatMark = repeatStart; repeatMark < repeatEnd; repeatMark += repeatInterval) {
            int mark = int(nearbyint(m_grid.getRulerScale()->getXForTime(repeatMark)));
            //             RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : mark at " << mark << endl;
            repeatMarks.push_back(mark);
        }
        sr.setRepeatMarks(repeatMarks);
        if (repeatMarks.size() > 0)
            sr.setBaseWidth(repeatMarks[0] - sr.x());
        else {
            //             RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : no repeat marks\n";
            sr.setBaseWidth(sr.width());
        }

        //         RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : s = "
        //                  << s << " base width = " << sr.getBaseWidth()
        //                  << " - nb repeat marks = " << repeatMarks.size() << endl;

    }
}

void CompositionModelImpl::setAudioPreviewThread(AudioPreviewThread *thread)
{
    //    std::cerr << "\nCompositionModelImpl::setAudioPreviewThread()\n" << std::endl;

    while (!m_audioPreviewUpdaterMap.empty()) {
        // Cause any running previews to be cancelled
        delete m_audioPreviewUpdaterMap.begin()->second;
        m_audioPreviewUpdaterMap.erase(m_audioPreviewUpdaterMap.begin());
    }

    m_audioPreviewThread = thread;
}

void CompositionModelImpl::clearPreviewCache()
{
    RG_DEBUG << "CompositionModelImpl::clearPreviewCache\n";

    m_notationPreviewDataCache.clear();
    m_audioPreviewDataCache.clear();
    m_audioSegmentPreviewMap.clear();

    for (AudioPreviewUpdaterMap::iterator i = m_audioPreviewUpdaterMap.begin();
            i != m_audioPreviewUpdaterMap.end(); ++i) {
        i->second->cancel();
    }

    const Composition::segmentcontainer& segments = m_composition.getSegments();
    Composition::segmentcontainer::iterator segEnd = segments.end();

    for (Composition::segmentcontainer::iterator i = segments.begin();
            i != segEnd; ++i) {

        if ((*i)->getType() == Segment::Audio) {
            // This will create the audio preview updater.  The
            // preview won't be calculated and cached until the
            // updater completes and calls back.
            updatePreviewCacheForAudioSegment((*i), 0);
        }
    }
}

void CompositionModelImpl::updatePreviewCacheForNotationSegment(const Segment* segment, rectlist* npData)
{
    npData->clear();

    int segStartX = int(nearbyint(m_grid.getRulerScale()->getXForTime(segment->getStartTime())));

    for (Segment::iterator i = segment->begin();
            i != segment->end(); ++i) {

        long pitch = 0;
        if (!(*i)->isa(Note::EventType) ||
                !(*i)->get
                <Int>
                (BaseProperties::PITCH, pitch)) {
            continue;
        }

        timeT eventStart = (*i)->getAbsoluteTime();
        timeT eventEnd = eventStart + (*i)->getDuration();
        // 	if (eventEnd > segment->getEndMarkerTime()) {
        // 	    eventEnd = segment->getEndMarkerTime();
        // 	}

        int x = int(nearbyint(m_grid.getRulerScale()->getXForTime(eventStart)));
        int width = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(eventStart,
                                  eventEnd - eventStart)));

        if (x <= segStartX) {
            ++x;
            if (width > 1)
                --width;
        }
        if (width > 1)
            --width;
        if (width < 1)
            ++width;

        double y0 = 0;
        double y1 = m_grid.getYSnap();
        double y = y1 + ((y0 - y1) * (pitch - 16)) / 96;
        if (y < y0)
            y = y0;
        if (y > y1 - 1)
            y = y1 - 1;

        QRect r(x, (int)y, width, 2);

        //         RG_DEBUG << "CompositionModelImpl::updatePreviewCacheForNotationSegment() : npData = "
        //                  << npData << ", preview rect = "
        //                  << r << endl;
        npData->push_back(r);
    }

}

QColor CompositionModelImpl::computeSegmentPreviewColor(const Segment* segment)
{
    // compute the preview color so it's as visible as possible over the segment's color
    QColor segColor = GUIPalette::convertColour(m_composition.getSegmentColourMap().getColourByIndex(segment->getColourIndex()));
    int h, s, v;
    segColor.hsv(&h, &s, &v);

    // colors with saturation lower than value should be pastel tints, and
    // they get a value of 0; yellow and green hues close to the dead center
    // point for that hue were taking a value of 255 with the (s < v)
    // formula, so I added an extra hack to force hues in those two narrow
    // ranges toward black.  Black always looks good, while white washes out
    // badly against intense yellow, and doesn't look very good against
    // intense green either...  hacky, but this produces pleasant results against
    // every bizarre extreme of color I could cook up to throw at it, plus
    // (the real reason for all this convoluted fiddling, it does all that while keeping
    // white against bright reds and blues, which looks better than black)
    if ( ((((h > 57) && (h < 66)) || ((h > 93) && (h < 131))) && (s > 127) && (v > 127) ) ||
            (s < v) ) {
        v = 0;
    } else {
        v = 255;
    }
    s = 31;
    h += 180;

    segColor.setHsv(h, s, v);

    return segColor;
}

void CompositionModelImpl::updatePreviewCacheForAudioSegment(const Segment* segment, AudioPreviewData* apData)
{
    if (m_audioPreviewThread) {
        //	std::cerr << "CompositionModelImpl::updatePreviewCacheForAudioSegment() - new audio preview started" << std::endl;

        CompositionRect segRect = computeSegmentRect(*segment);
        segRect.setWidth(segRect.getBaseWidth()); // don't use repeating area
        segRect.moveTopLeft(QPoint(0, 0));

        if (apData)
            apData->setSegmentRect(segRect);

        if (m_audioPreviewUpdaterMap.find(segment) ==
                m_audioPreviewUpdaterMap.end()) {

            AudioPreviewUpdater *updater = new AudioPreviewUpdater
                                           (*m_audioPreviewThread, m_composition, segment, segRect, this);

            connect(updater, SIGNAL(audioPreviewComplete(AudioPreviewUpdater*)),
                    this, SLOT(slotAudioPreviewComplete(AudioPreviewUpdater*)));

            m_audioPreviewUpdaterMap[segment] = updater;

        } else {

            m_audioPreviewUpdaterMap[segment]->setDisplayExtent(segRect);
        }

        m_audioPreviewUpdaterMap[segment]->update();

    } else {
        RG_DEBUG << "CompositionModelImpl::updatePreviewCacheForAudioSegment() - no audio preview thread set\n";
    }
}

void CompositionModelImpl::slotAudioPreviewComplete(AudioPreviewUpdater* apu)
{
    RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete()\n";

    AudioPreviewData *apData = getAudioPreviewData(apu->getSegment());
    QRect updateRect;

    if (apData) {
        RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete(" << apu << "): apData contains " << apData->getValues().size() << " values already" << endl;
        unsigned int channels = 0;
        const std::vector<float> &values = apu->getComputedValues(channels);
        if (channels > 0) {
            RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete: set "
            << values.size() << " samples on " << channels << " channels\n";
            apData->setChannels(channels);
            apData->setValues(values);
            updateRect = postProcessAudioPreview(apData, apu->getSegment());
        }
    }

    if (!updateRect.isEmpty())
        emit needContentUpdate(updateRect);
}

QRect CompositionModelImpl::postProcessAudioPreview(AudioPreviewData* apData, const Segment* segment)
{
    //     RG_DEBUG << "CompositionModelImpl::postProcessAudioPreview()\n";

    AudioPreviewPainter previewPainter(*this, apData, m_composition, segment);
    previewPainter.paintPreviewImage();

    m_audioSegmentPreviewMap[segment] = previewPainter.getPreviewImage();

    return previewPainter.getSegmentRect();
}

void CompositionModelImpl::slotInstrumentParametersChanged(InstrumentId id)
{
    //     RG_DEBUG << "CompositionModelImpl::slotInstrumentParametersChanged()\n";
    const Composition::segmentcontainer& segments = m_composition.getSegments();
    Composition::segmentcontainer::iterator segEnd = segments.end();

    for (Composition::segmentcontainer::iterator i = segments.begin();
            i != segEnd; ++i) {

        Segment* s = *i;
        if (s->getType() == Segment::Audio) {

            TrackId trackId = s->getTrack();
            Track *track = getComposition().getTrackById(trackId);
            if (track && track->getInstrument() == id) {
                makeAudioPreviewDataCache(s);
            }
        }
    }
}

void CompositionModelImpl::slotAudioFileFinalized(Segment* s)
{
    //     RG_DEBUG << "CompositionModelImpl::slotAudioFileFinalized()\n";
    removePreviewCache(s);
}

PixmapArray CompositionModelImpl::getAudioPreviewPixmap(const Segment* s)
{
    getAudioPreviewData(s);
    return m_audioSegmentPreviewMap[s];
}

void CompositionModelImpl::eventAdded(const Segment *s, Event *)
{
    //     RG_DEBUG << "CompositionModelImpl::eventAdded()\n";
    removePreviewCache(s);
    emit needContentUpdate(computeSegmentRect(*s));
}

void CompositionModelImpl::eventRemoved(const Segment *s, Event *)
{
    //     RG_DEBUG << "CompositionModelImpl::eventRemoved" << endl;
    removePreviewCache(s);
    emit needContentUpdate(computeSegmentRect(*s));
}

void CompositionModelImpl::appearanceChanged(const Segment *s)
{
    //     RG_DEBUG << "CompositionModelImpl::appearanceChanged" << endl;
    clearInCache(s, true);
    emit needContentUpdate(computeSegmentRect(*s));
}

void CompositionModelImpl::endMarkerTimeChanged(const Segment *s, bool shorten)
{
    //     RG_DEBUG << "CompositionModelImpl::endMarkerTimeChanged(" << shorten << ")" << endl;
    clearInCache(s, true);
    if (shorten) {
        emit needContentUpdate(); // no longer know former segment dimension
    } else {
        emit needContentUpdate(computeSegmentRect(*s));
    }
}

void CompositionModelImpl::makePreviewCache(const Segment *s)
{
    if (s->getType() == Segment::Internal) {
        makeNotationPreviewDataCache(s);
    } else {
        makeAudioPreviewDataCache(s);
    }
}

void CompositionModelImpl::removePreviewCache(const Segment *s)
{
    if (s->getType() == Segment::Internal) {
        m_notationPreviewDataCache.remove(const_cast<Segment*>(s));
    } else {
        m_audioPreviewDataCache.remove(const_cast<Segment*>(s));
        m_audioSegmentPreviewMap.erase(s);
    }

}

void CompositionModelImpl::segmentAdded(const Composition *, Segment *s)
{
    makePreviewCache(s);
    s->addObserver(this);
    emit needContentUpdate();
}

void CompositionModelImpl::segmentRemoved(const Composition *, Segment *s)
{
    QRect r = computeSegmentRect(*s);

    m_selectedSegments.erase(s);

    clearInCache(s, true);
    s->removeObserver(this);
    m_recordingSegments.erase(s); // this could be a recording segment
    emit needContentUpdate(r);
}

void CompositionModelImpl::segmentRepeatChanged(const Composition *, Segment *s, bool)
{
    clearInCache(s);
    emit needContentUpdate();
}

void CompositionModelImpl::endMarkerTimeChanged(const Composition *, bool)
{
    emit needSizeUpdate();
}

void CompositionModelImpl::setSelectionRect(const QRect& r)
{
    m_selectionRect = r.normalize();

    m_previousTmpSelectedSegments = m_tmpSelectedSegments;
    m_tmpSelectedSegments.clear();

    const Composition::segmentcontainer& segments = m_composition.getSegments();
    Composition::segmentcontainer::iterator segEnd = segments.end();

    QRect updateRect = m_selectionRect;

    for (Composition::segmentcontainer::iterator i = segments.begin();
            i != segEnd; ++i) {

        Segment* s = *i;
        CompositionRect sr = computeSegmentRect(*s);
        if (sr.intersects(m_selectionRect)) {
            m_tmpSelectedSegments.insert(s);
            updateRect |= sr;
        }
    }

    updateRect = updateRect.normalize();

    if (!updateRect.isNull() && !m_previousSelectionUpdateRect.isNull()) {

        if (m_tmpSelectedSegments != m_previousTmpSelectedSegments)
            emit needContentUpdate(updateRect | m_previousSelectionUpdateRect);

        emit needArtifactsUpdate();
    }


    m_previousSelectionUpdateRect = updateRect;

}

void CompositionModelImpl::finalizeSelectionRect()
{
    const Composition::segmentcontainer& segments = m_composition.getSegments();
    Composition::segmentcontainer::iterator segEnd = segments.end();

    for (Composition::segmentcontainer::iterator i = segments.begin();
            i != segEnd; ++i) {

        Segment* s = *i;
        CompositionRect sr = computeSegmentRect(*s);
        if (sr.intersects(m_selectionRect)) {
            setSelected(s);
        }
    }

    m_previousSelectionUpdateRect = m_selectionRect = QRect();
    m_tmpSelectedSegments.clear();
}

QRect CompositionModelImpl::getSelectionContentsRect()
{
    QRect selectionRect;

    SegmentSelection sel = getSelectedSegments();
    for (SegmentSelection::iterator i = sel.begin();
            i != sel.end(); ++i) {

        Segment* s = *i;
        CompositionRect sr = computeSegmentRect(*s);
        selectionRect |= sr;
    }

    return selectionRect;
}

void CompositionModelImpl::addRecordingItem(const CompositionItem& item)
{
    m_recordingSegments.insert(CompositionItemHelper::getSegment(item));
    emit needContentUpdate();

    RG_DEBUG << "CompositionModelImpl::addRecordingItem: now have "
    << m_recordingSegments.size() << " recording items\n";
}

void CompositionModelImpl::removeRecordingItem(const CompositionItem &item)
{
    Segment* s = CompositionItemHelper::getSegment(item);

    m_recordingSegments.erase(s);
    clearInCache(s, true);

    emit needContentUpdate();

    RG_DEBUG << "CompositionModelImpl::removeRecordingItem: now have "
    << m_recordingSegments.size() << " recording items\n";
}

void CompositionModelImpl::clearRecordingItems()
{
    for (recordingsegmentset::iterator i = m_recordingSegments.begin();
            i != m_recordingSegments.end(); ++i)
        clearInCache(*i, true);

    m_recordingSegments.clear();

    emit needContentUpdate();
    RG_DEBUG << "CompositionModelImpl::clearRecordingItem\n";
}

bool CompositionModelImpl::isMoving(const Segment* sm) const
{
    itemcontainer::const_iterator movEnd = m_changingItems.end();

    for (itemcontainer::const_iterator i = m_changingItems.begin(); i != movEnd; ++i) {
        const CompositionItemImpl* ci = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)(*i));
        const Segment* s = ci->getSegment();
        if (sm == s)
            return true;
    }

    return false;
}

bool CompositionModelImpl::isRecording(const Segment* s) const
{
    return m_recordingSegments.find(const_cast<Segment*>(s)) != m_recordingSegments.end();
}

CompositionModel::itemcontainer CompositionModelImpl::getItemsAt(const QPoint& point)
{
    itemcontainer res;

    const Composition::segmentcontainer& segments = m_composition.getSegments();

    for (Composition::segmentcontainer::iterator i = segments.begin();
            i != segments.end(); ++i) {

        Segment* s = *i;

        CompositionRect sr = computeSegmentRect(*s);
        if (sr.contains(point)) {
            //            RG_DEBUG << "CompositionModelImpl::getItemsAt() adding " << sr << " for segment " << s << endl;
            CompositionItem item(new CompositionItemImpl(*s, sr));
            unsigned int z = computeZForSegment(s);
            //            RG_DEBUG << "CompositionModelImpl::getItemsAt() z = " << z << endl;
            item->setZ(z);
            res.insert(item);
        } else {
            //             RG_DEBUG << "CompositionModelImpl::getItemsAt() skiping " << sr << endl;
        }

    }

    if (res.size() == 1) { // only one segment under click point
        Segment* s = CompositionItemHelper::getSegment(*(res.begin()));
        m_segmentOrderer.segmentClicked(s);
    }

    return res;
}

void CompositionModelImpl::setPointerPos(int xPos)
{
    m_pointerTimePos = grid().getRulerScale()->getTimeForX(xPos);

    for (recordingsegmentset::iterator i = m_recordingSegments.begin();
            i != m_recordingSegments.end(); ++i) {
        emit needContentUpdate(computeSegmentRect(**i));
    }
}

void CompositionModelImpl::setSelected(const CompositionItem& item, bool selected)
{
    const CompositionItemImpl* itemImpl = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)item);
    if (itemImpl) {
        Segment* segment = const_cast<Segment*>(itemImpl->getSegment());
        setSelected(segment, selected);
    }
}

void CompositionModelImpl::setSelected(const itemcontainer& items)
{
    for (itemcontainer::const_iterator i = items.begin(); i != items.end(); ++i) {
        setSelected(*i);
    }
}

void CompositionModelImpl::setSelected(const Segment* segment, bool selected)
{
    RG_DEBUG << "CompositionModelImpl::setSelected " << segment << " - " << selected << endl;
    if (selected) {
        if (!isSelected(segment))
            m_selectedSegments.insert(const_cast<Segment*>(segment));
    } else {
        SegmentSelection::iterator i = m_selectedSegments.find(const_cast<Segment*>(segment));
        if (i != m_selectedSegments.end())
            m_selectedSegments.erase(i);
    }
    emit needContentUpdate();
}

void CompositionModelImpl::signalSelection()
{
    //     RG_DEBUG << "CompositionModelImpl::signalSelection()\n";
    emit selectedSegments(getSelectedSegments());
}

void CompositionModelImpl::signalContentChange()
{
    //     RG_DEBUG << "CompositionModelImpl::signalContentChange" << endl;
    emit needContentUpdate();
}

void CompositionModelImpl::clearSelected()
{
    RG_DEBUG << "CompositionModelImpl::clearSelected" << endl;
    m_selectedSegments.clear();
    emit needContentUpdate();
}

bool CompositionModelImpl::isSelected(const CompositionItem& ci) const
{
    const CompositionItemImpl* itemImpl = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)ci);
    return itemImpl ? isSelected(itemImpl->getSegment()) : 0;
}

bool CompositionModelImpl::isSelected(const Segment* s) const
{
    return m_selectedSegments.find(const_cast<Segment*>(s)) != m_selectedSegments.end();
}

bool CompositionModelImpl::isTmpSelected(const Segment* s) const
{
    return m_tmpSelectedSegments.find(const_cast<Segment*>(s)) != m_tmpSelectedSegments.end();
}

bool CompositionModelImpl::wasTmpSelected(const Segment* s) const
{
    return m_previousTmpSelectedSegments.find(const_cast<Segment*>(s)) != m_previousTmpSelectedSegments.end();
}

void CompositionModelImpl::startChange(const CompositionItem& item, CompositionModel::ChangeType change)
{
    m_changeType = change;

    itemcontainer::iterator i = m_changingItems.find(item);

    // if an "identical" composition item has already been inserted, drop this one
    if (i != m_changingItems.end()) {
        RG_DEBUG << "CompositionModelImpl::startChange : item already in\n";
        m_itemGC.push_back(item);
    } else {
        item->saveRect();
        m_changingItems.insert(item);
    }
}

void CompositionModelImpl::startChangeSelection(CompositionModel::ChangeType change)
{
    SegmentSelection::iterator i = m_selectedSegments.begin();
    for (; i != m_selectedSegments.end(); ++i) {
        Segment* s = *i;
        CompositionRect sr = computeSegmentRect(*s);
        startChange(CompositionItem(new CompositionItemImpl(*s, sr)), change);
    }

}

void CompositionModelImpl::endChange()
{
    for (itemcontainer::const_iterator i = m_changingItems.begin(); i != m_changingItems.end(); ++i) {
        delete *i;
    }

    m_changingItems.clear();

    for (itemgc::iterator i = m_itemGC.begin(); i != m_itemGC.end(); ++i) {
        delete *i;
    }
    m_itemGC.clear();
    RG_DEBUG << "CompositionModelImpl::endChange\n";
    emit needContentUpdate();
}

void CompositionModelImpl::setLength(int width)
{
    timeT endMarker = m_grid.snapX(width);
    m_composition.setEndMarker(endMarker);
}

int CompositionModelImpl::getLength()
{
    timeT endMarker = m_composition.getEndMarker();
    int w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(0, endMarker)));
    return w;
}

timeT CompositionModelImpl::getRepeatTimeAt(const QPoint& p, const CompositionItem& cItem)
{
    //     timeT timeAtClick = m_grid.getRulerScale()->getTimeForX(p.x());

    CompositionItemImpl* itemImpl = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)cItem);

    const Segment* s = itemImpl->getSegment();

    timeT startTime = s->getStartTime();
    timeT endTime = s->getEndMarkerTime();
    timeT repeatInterval = endTime - startTime;

    int rWidth = int(nearbyint(m_grid.getRulerScale()->getXForTime(repeatInterval)));

    int count = (p.x() - int(itemImpl->rect().x())) / rWidth;
    RG_DEBUG << "CompositionModelImpl::getRepeatTimeAt() : count = " << count << endl;

    return count != 0 ? startTime + (count * (s->getEndMarkerTime() - s->getStartTime())) : 0;
}

QPoint CompositionModelImpl::computeSegmentOrigin(const Segment& s)
{
    //    Profiler profiler("CompositionModelImpl::computeSegmentOrigin", true);

    int trackPosition = m_composition.getTrackById(s.getTrack())->getPosition();
    timeT startTime = s.getStartTime();

    QPoint res;

    res.setX(int(nearbyint(m_grid.getRulerScale()->getXForTime(startTime))));
    res.setY(m_grid.getYBinCoordinate(trackPosition));

    return res;
}

bool CompositionModelImpl::isCachedRectCurrent(const Segment& s, const CompositionRect& r, QPoint cachedSegmentOrigin, timeT cachedSegmentEndTime)
{
    return s.isRepeating() == r.isRepeating() &&
           ((cachedSegmentOrigin.x() != r.x() && s.getEndMarkerTime() != cachedSegmentEndTime) ||
            (cachedSegmentOrigin.x() == r.x() && s.getEndMarkerTime() == cachedSegmentEndTime));
}

void CompositionModelImpl::clearInCache(const Segment* s, bool clearPreview)
{
    if (s) {
        m_segmentRectMap.erase(s);
        m_segmentEndTimeMap.erase(s);
        if (clearPreview)
            removePreviewCache(s);
    } else { // clear the whole cache
        m_segmentRectMap.clear();
        m_segmentEndTimeMap.clear();
        if (clearPreview)
            clearPreviewCache();
    }
}

void CompositionModelImpl::putInCache(const Segment*s, const CompositionRect& cr)
{
    m_segmentRectMap[s] = cr;
    m_segmentEndTimeMap[s] = s->getEndMarkerTime();
}

CompositionRect CompositionModelImpl::computeSegmentRect(const Segment& s, bool computeZ)
{
    //    Profiler profiler("CompositionModelImpl::computeSegmentRect", true);

    QPoint origin = computeSegmentOrigin(s);

    bool isRecordingSegment = isRecording(&s);

    if (!isRecordingSegment) {
        timeT endTime = 0;

        CompositionRect cachedCR = getFromCache(&s, endTime);
        // don't cache repeating segments - it's just hopeless, because the segment's rect may have to be recomputed
        // in other cases than just when the segment itself is moved,
        // for instance if another segment is moved over it
        if (!s.isRepeating() && cachedCR.isValid() && isCachedRectCurrent(s, cachedCR, origin, endTime)) {
            //         RG_DEBUG << "CompositionModelImpl::computeSegmentRect() : using cache for seg "
            //                  << &s << " - cached rect repeating = " << cachedCR.isRepeating() << " - base width = "
            //                  << cachedCR.getBaseWidth() << endl;

            bool xChanged = origin.x() != cachedCR.x();
            bool yChanged = origin.y() != cachedCR.y();

            cachedCR.moveTopLeft(origin);

            if (s.isRepeating() && (xChanged || yChanged)) { // update repeat marks

                // this doesn't work in the general case (if there's another segment on the same track for instance),
                // it's better to simply recompute all the marks
                //                 CompositionRect::repeatmarks repeatMarks = cachedCR.getRepeatMarks();
                //                 for(unsigned int i = 0; i < repeatMarks.size(); ++i) {
                //                     repeatMarks[i] += deltaX;
                //                 }
                //                 cachedCR.setRepeatMarks(repeatMarks);
                computeRepeatMarks(cachedCR, &s);
            }
            putInCache(&s, cachedCR);
            return cachedCR;
        }
    }

    timeT startTime = s.getStartTime();
    timeT endTime = isRecordingSegment ? m_pointerTimePos /*s.getEndTime()*/ : s.getEndMarkerTime();


    int h = m_grid.getYSnap();
    int w;

    //     RG_DEBUG << "CompositionModelImpl::computeSegmentRect: x " << origin.x() << ", y " << origin.y() << " startTime " << startTime << ", endTime " << endTime << endl;

    if (s.isRepeating()) {
        timeT repeatStart = endTime;
        timeT repeatEnd = s.getRepeatEndTime();
        w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime,
                          repeatEnd - startTime)));
        //         RG_DEBUG << "CompositionModelImpl::computeSegmentRect : s is repeating - repeatStart = "
        //                  << repeatStart << " - repeatEnd : " << repeatEnd
        //                  << " w = " << w << endl;
    } else {
        w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime, endTime - startTime)));
        //          RG_DEBUG << "CompositionModelImpl::computeSegmentRect : s is NOT repeating"
        //                   << " w = " << w << " (x for time at start is " << m_grid.getRulerScale()->getXForTime(startTime) << ", end is " << m_grid.getRulerScale()->getXForTime(endTime) << ")" << endl;
    }

    CompositionRect cr(origin, QSize(w, h));
    QString label = strtoqstr(s.getLabel());
    if (s.getType() == Segment::Audio) {
        static QRegExp re1("( *\\([^)]*\\))*$"); // (inserted) (copied) (etc)
        static QRegExp re2("\\.[^.]+$"); // filename suffix
        label.replace(re1, "").replace(re2, "");
    }
    cr.setLabel(label);

    if (s.isRepeating()) {
        computeRepeatMarks(cr, &s);
    } else {
        cr.setBaseWidth(cr.width());
    }

    putInCache(&s, cr);

    return cr;
}

unsigned int CompositionModelImpl::computeZForSegment(const Rosegarden::Segment* s)
{
    return m_segmentOrderer.getZForSegment(s);
}

const CompositionRect& CompositionModelImpl::getFromCache(const Rosegarden::Segment* s, timeT& endTime)
{
    endTime = m_segmentEndTimeMap[s];
    return m_segmentRectMap[s];
}

unsigned int CompositionModelImpl::getNbRows()
{
    return m_composition.getNbTracks();
}

}
#include "CompositionModelImpl.moc"
