/* -*- 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 "[Device]"
#define RG_NO_DEBUG_PRINT

#include "Device.h"

#include "Composition.h"
#include "Controllable.h"
#include "MidiDevice.h"
#include "SoftSynthDevice.h"

#include "misc/Debug.h"
#include "document/RosegardenDocument.h"


namespace Rosegarden
{


Device::~Device()
{
    //SEQUENCER_DEBUG << "~Device";
    InstrumentVector::iterator it = m_instruments.begin();
    // For each Instrument
    for (; it != m_instruments.end(); ++it) {
        (*it)->sendWholeDeviceDestroyed();
        delete (*it);
    }

    if (!m_observers.empty()) {
        RG_WARNING << "dtor: Warning:" << m_observers.size() <<
            "observers still extant";
    }
}

// Return a Controllable if we are a subtype that also inherits from
// Controllable, otherwise return nullptr
const Controllable *
Device::getControllable() const
{
    const Controllable *c = dynamic_cast<const MidiDevice *>(this);
    if (!c) {
        c = dynamic_cast<const SoftSynthDevice *>(this);
    }
    // Even if it's zero, return it now.
    return c;
}

// Base case: Device itself doesn't know AllocateChannels so gives nullptr.
// @author Tom Breton (Tehom)
AllocateChannels *
Device::getAllocator() const
{ return nullptr; }

void
Device::sendChannelSetups() const
{
    // For each Instrument, send channel setup
    for (InstrumentVector::const_iterator it = m_instruments.begin();
         it != m_instruments.end();
         ++it) {
        (*it)->sendChannelSetup();
    }
}

InstrumentId
Device::getAvailableInstrument(const Composition *composition) const
{
    InstrumentVector instruments = getPresentationInstruments();
    if (instruments.empty())
        return NoInstrument;

    if (!composition)
        composition = &RosegardenDocument::currentDocument->getComposition();

    // Assume not found.
    InstrumentId firstInstrumentID{NoInstrument};

    // For each instrument on the device
    for (const Instrument *instrument : instruments) {
        if (!instrument)
            continue;

        const InstrumentId instrumentID = instrument->getId();

        // If we've not found the first one yet, save it in case we don't
        // find anything available.
        if (firstInstrumentID == NoInstrument)
            firstInstrumentID = instrumentID;

        // If this instrumentID is not in use, return it.
        if (!composition->hasTrack(instrumentID))
            return instrumentID;
    }

    // Return the first instrumentID for this device.
    return firstInstrumentID;
}

void Device::addObserver(DeviceObserver *obs)
{
    //RG_DEBUG << "addObserver" << this << obs;
    m_observers.push_back(obs);
}

// cppcheck-suppress constParameterPointer
void Device::removeObserver(DeviceObserver *obs)
{
    //RG_DEBUG << "removeObserver" << this << obs;
    m_observers.remove(obs);
}

void Device::notifyDeviceModified()
{
    if (m_notificationsBlocked)
        return;

    // ??? CRASH: This will crash if the observer removes itself while
    //            handling a notification.  Need to use the "increment before
    //            use" idiom.

    for (ObserverList::iterator i = m_observers.begin();
         i != m_observers.end();
         ++i) {
        (*i)->deviceModified(this);
    }

}

void Device::blockNotify(bool block)
{
    m_notificationsBlocked = block;
    // if we are unblocking then notify
    if (!block) {
        notifyDeviceModified();
    }
}

}
