#include "channel.hh"
#include "contact.hh"
#include "rxgrouplist.hh"
#include "config.hh"
#include "scanlist.hh"
#include "logger.hh"
#include <cmath>
#include "utils.hh"
//#include "application.hh"
#include <QAbstractProxyModel>
#include <QMetaEnum>
#include <QRegularExpression>

#include "opengd77_extension.hh"


/* ********************************************************************************************* *
 * Implementation of Channel
 * ********************************************************************************************* */
Channel::Channel(QObject *parent)
  : ConfigObject("ch", parent), _rxFreq(Frequency::fromHz(0)), _txFreq(Frequency::fromHz(0)),
    _defaultPower(true), _power(Power::Low), _txTimeOut(std::numeric_limits<unsigned>::max()),
    _rxOnly(false), _vox(std::numeric_limits<unsigned>::max()), _scanlist(),
    _openGD77ChannelExtension(nullptr),
    _tytChannelExtension(nullptr)
{
  // Link scan list modification event (e.g., scan list gets deleted).
  connect(&_scanlist, SIGNAL(modified()), this, SLOT(onReferenceModified()));
}

Channel::Channel(const Channel &other, QObject *parent)
  : ConfigObject("ch", parent), _scanlist(), _openGD77ChannelExtension(nullptr),
    _tytChannelExtension(nullptr)
{
  Channel::copy(other);

  // Link scan list modification event (e.g., scan list gets deleted).
  connect(&_scanlist, SIGNAL(modified()), this, SLOT(onReferenceModified()));
}

bool
Channel::copy(const ConfigItem &other) {
  const Channel *c = other.as<Channel>();
  if ((nullptr == c) || (! ConfigObject::copy(other)))
    return false;

  if (c->defaultPower())
    setDefaultPower();
  if (c->defaultTimeout())
    setDefaultTimeout();
  if (c->defaultVOX())
    setVOXDefault();

  return true;
}

void
Channel::clear() {
  ConfigObject::clear();

  setRXFrequency(Frequency::fromHz(0));
  setTXFrequency(Frequency::fromHz(0));
  setDefaultPower();
  setDefaultTimeout();
  setRXOnly(false);
  setVOXDefault();

  _scanlist.clear();

  setOpenGD77ChannelExtension(nullptr);
  setTyTChannelExtension(nullptr);
}


Frequency Channel::rxFrequency() const {
  return _rxFreq;
}

bool
Channel::setRXFrequency(Frequency freq) {
  if (freq == _rxFreq)
    return true;

  _rxFreq = freq;
  emit modified(this);

  return true;
}

Frequency
Channel::txFrequency() const {
  return _txFreq;
}

bool
Channel::setTXFrequency(Frequency freq) {
  if (freq == _txFreq)
    return true;

  _txFreq = freq;
  emit modified(this);

  return true;
}

FrequencyOffset
Channel::offsetFrequency() const {

    return _txFreq - _rxFreq;
}

Channel::OffsetShift
Channel::offsetShift() const {
    if (_rxFreq > _txFreq) {
        return OffsetShift::Negative;
    } else if (_txFreq > _rxFreq) {
        return OffsetShift::Positive;
    } else {
        return OffsetShift::None;
    }
}

bool
Channel::defaultPower() const {
  return _defaultPower;
}

Channel::Power
Channel::power() const {
  return _power;
}

void
Channel::setPower(Power power) {
  if ((power == _power) && (! _defaultPower))
    return;
  _power = power;
  _defaultPower = false;
  emit modified(this);
}

void
Channel::setDefaultPower() {
  if (defaultPower())
    return;

  _defaultPower = true;
  emit modified(this);
}


bool
Channel::defaultTimeout() const {
  return std::numeric_limits<unsigned>::max() == timeout();
}

bool
Channel::timeoutDisabled() const {
  return 0 == timeout();
}

unsigned
Channel::timeout() const {
  return _txTimeOut;
}

bool
Channel::setTimeout(unsigned dur) {
  if (dur == _txTimeOut)
    return true;

  _txTimeOut = dur;
  emit modified(this);

  return true;
}

void
Channel::setDefaultTimeout() {
  if (defaultTimeout())
    return;

  setTimeout(std::numeric_limits<unsigned>::max());
  emit modified(this);
}

void
Channel::disableTimeout() {
  setTimeout(0);
}


bool
Channel::rxOnly() const {
  return _rxOnly;
}
bool
Channel::setRXOnly(bool enable) {
  _rxOnly = enable;
  emit modified(this);
  return true;
}

bool
Channel::voxDisabled() const {
  return 0==vox();
}
bool
Channel::defaultVOX() const {
  return std::numeric_limits<unsigned>::max() == vox();
}
unsigned
Channel::vox() const {
  return _vox;
}
void
Channel::setVOX(unsigned level) {
  _vox = level;
  emit modified(this);
}
void
Channel::setVOXDefault() {
  setVOX(std::numeric_limits<unsigned>::max());
}
void
Channel::disableVOX() {
  setVOX(0);
}

const ScanListReference *
Channel::scanListRef() const {
  return &_scanlist;
}

ScanListReference *
Channel::scanListRef() {
  return &_scanlist;
}

ScanList *
Channel::scanList() const {
  return _scanlist.as<ScanList>();
}
bool
Channel::setScanList(ScanList *list) {
  return _scanlist.set(list);
}

void
Channel::onReferenceModified() {
  emit modified(this);
}

OpenGD77ChannelExtension *
Channel::openGD77ChannelExtension() const {
  return _openGD77ChannelExtension;
}
void
Channel::setOpenGD77ChannelExtension(OpenGD77ChannelExtension *ext) {
  if (_openGD77ChannelExtension == ext)
    return;
  if (_openGD77ChannelExtension)
    _openGD77ChannelExtension->deleteLater();
  _openGD77ChannelExtension = ext;
  if (_openGD77ChannelExtension) {
    _openGD77ChannelExtension->setParent(this);
    connect(_openGD77ChannelExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onReferenceModified()));
  }
}

TyTChannelExtension *
Channel::tytChannelExtension() const {
  return _tytChannelExtension;
}
void
Channel::setTyTChannelExtension(TyTChannelExtension *ext) {
  if (_tytChannelExtension == ext)
    return;
  if (_tytChannelExtension)
    _tytChannelExtension->deleteLater();
  _tytChannelExtension = ext;
  if (_tytChannelExtension) {
    _tytChannelExtension->setParent(this);
    connect(_tytChannelExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onReferenceModified()));
  }
}

bool
Channel::populate(YAML::Node &node, const Context &context, const ErrorStack &err) {
  if (! ConfigObject::populate(node, context, err))
    return false;

  // Serialize freuqencies in MHz
  node["rxFrequency"] = _rxFreq;
  node["txFrequency"] = _txFreq;

  if (defaultPower()) {
    YAML::Node def = YAML::Node(YAML::NodeType::Scalar); def.SetTag("!default");
    node["power"] = def;
  } else {
    QMetaEnum metaEnum = QMetaEnum::fromType<Power>();
    node["power"] = metaEnum.valueToKey((unsigned)power());
  }

  if (defaultTimeout()) {
    YAML::Node def = YAML::Node(YAML::NodeType::Scalar); def.SetTag("!default");
    node["timeout"] = def;
  } else {
    node["timeout"] = timeout();
  }

  if (defaultVOX()) {
    YAML::Node def = YAML::Node(YAML::NodeType::Scalar); def.SetTag("!default");
    node["vox"] = def;
  } else {
    node["vox"] = vox();
  }

  return true;
}

bool
Channel::parse(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) {
  if (! node)
    return false;

  if ((! node.IsMap()) || (1 != node.size())) {
    errMsg(err) << node.Mark().line << ":" << node.Mark().column
                << ": Cannot parse channel: Expected object with one child.";
    return false;
  }

  YAML::Node ch = node.begin()->second;
  // Parse frequencies
  if (ch["rxFrequency"].IsNull()) {
    errMsg(err) << node.Mark().line << ":" << node.Mark().column
                << "Cannot parse channel. No rxFreuqency specified.";
    return false;
  }
  setRXFrequency(ch["rxFrequency"].as<Frequency>());

  if (ch["txFrequency"].IsNull()) {
    errMsg(err) << node.Mark().line << ":" << node.Mark().column
                << "Cannot parse channel. No txFrequency specified.";
    return false;
  }
  setTXFrequency(ch["txFrequency"].as<Frequency>());

  if ((!ch["power"]) || ("!default" == ch["power"].Tag())) {
    setDefaultPower();
  } else if (ch["power"] && ch["power"].IsScalar()) {
    QMetaEnum meta = QMetaEnum::fromType<Channel::Power>();
    setPower((Channel::Power)meta.keyToValue(ch["power"].as<std::string>().c_str()));
  }

  if ((!ch["timeout"]) || ("!default" == ch["timeout"].Tag())) {
    setDefaultTimeout();
  } else if (ch["timeout"] && ch["timeout"].IsScalar()) {
    setTimeout(ch["timeout"].as<unsigned>());
  }

  if ((!ch["vox"]) || ("!default" == ch["vox"].Tag())) {
    setVOXDefault();
  } else if (ch["vox"] && ch["vox"].IsScalar()) {
    setVOX(ch["vox"].as<unsigned>());
  }

  return ConfigObject::parse(ch, ctx, err);
}

bool
Channel::link(const YAML::Node &node, const ConfigItem::Context &ctx, const ErrorStack &err) {
  if (! node)
    return false;

  if ((! node.IsMap()) || (1 != node.size())) {
    errMsg(err) << node.Mark().line << ":" << node.Mark().column
                << ": Cannot link channel: Expected object with one child.";
    return false;
  }

  return ConfigObject::link(node.begin()->second, ctx, err);
}


/* ********************************************************************************************* *
 * Implementation of AnalogChannel
 * ********************************************************************************************* */
AnalogChannel::AnalogChannel(QObject *parent)
  : Channel(parent)
{
  // pass...
}

AnalogChannel::AnalogChannel(const AnalogChannel &other, QObject *parent)
  : Channel(other, parent)
{
  // pass...
}



/* ********************************************************************************************* *
 * Implementation of FMChannel
 * ********************************************************************************************* */
FMChannel::FMChannel(QObject *parent)
  : AnalogChannel(parent),
    _admit(Admit::Always), _squelch(std::numeric_limits<unsigned>::max()),
    _rxTone(), _txTone(), _bw(Bandwidth::Narrow),
    _aprsSystem(), _anytoneExtension(nullptr)
{
  // Link APRS system reference
  connect(&_aprsSystem, SIGNAL(modified()), this, SLOT(onReferenceModified()));
}

bool
FMChannel::copy(const ConfigItem &other) {
  const FMChannel *c = other.as<FMChannel>();
  if ((nullptr==c) || (! AnalogChannel::copy(other)))
    return false;

  setRXTone(c->rxTone());
  setTXTone(c->txTone());

  return true;
}

ConfigItem *
FMChannel::clone() const {
  FMChannel *c = new FMChannel();
  if (! c->copy(*this)) {
    c->deleteLater();
    return nullptr;
  }
  return c;
}

void
FMChannel::clear() {
  AnalogChannel::clear();
  setAdmit(Admit::Always);
  setSquelchDefault();
  setRXTone(SelectiveCall());
  setTXTone(SelectiveCall());
  setBandwidth(Bandwidth::Narrow);
  setAPRSSystem(nullptr);
  setAnytoneChannelExtension(nullptr);
}

FMChannel::Admit
FMChannel::admit() const {
  return _admit;
}
void
FMChannel::setAdmit(Admit admit) {
  _admit = admit;
  emit modified(this);
}

bool
FMChannel::defaultSquelch() const {
  return std::numeric_limits<unsigned>::max()==squelch();
}
bool
FMChannel::squelchDisabled() const {
  return 0==squelch();
}
unsigned
FMChannel::squelch() const {
  return _squelch;
}
bool
FMChannel::setSquelch(unsigned val) {
  _squelch = val;
  emit modified(this);
  return true;
}
void
FMChannel::disableSquelch() {
  setSquelch(0);
}
void
FMChannel::setSquelchDefault() {
  setSquelch(std::numeric_limits<unsigned>::max());
}

SelectiveCall
FMChannel::rxTone() const {
  return _rxTone;
}
bool
FMChannel::setRXTone(SelectiveCall code) {
  _rxTone = code;
  emit modified(this);
  return true;
}

SelectiveCall
FMChannel::txTone() const {
  return _txTone;
}
bool
FMChannel::setTXTone(SelectiveCall code) {
  _txTone = code;
  emit modified(this);
  return true;
}

FMChannel::Bandwidth
FMChannel::bandwidth() const {
  return _bw;
}
bool
FMChannel::setBandwidth(Bandwidth bw) {
  _bw = bw;
  emit modified(this);
  return true;
}

const APRSSystemReference *
FMChannel::aprs() const {
  return &_aprsSystem;
}

APRSSystemReference *
FMChannel::aprs() {
  return &_aprsSystem;
}

void
FMChannel::setAPRS(APRSSystemReference *ref) {
  if (nullptr == ref)
    return _aprsSystem.clear();
  _aprsSystem.copy(ref);
}

APRSSystem *
FMChannel::aprsSystem() const {
  return _aprsSystem.as<APRSSystem>();
}
void
FMChannel::setAPRSSystem(APRSSystem *sys) {
  _aprsSystem.set(sys);
}

AnytoneFMChannelExtension *
FMChannel::anytoneChannelExtension() const {
  return _anytoneExtension;
}
void
FMChannel::setAnytoneChannelExtension(AnytoneFMChannelExtension *ext) {
  if (_anytoneExtension == ext)
    return;
  if (_anytoneExtension)
    _anytoneExtension->deleteLater();
  _anytoneExtension = ext;
  if (_anytoneExtension) {
    _anytoneExtension->setParent(this);
    connect(_anytoneExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onReferenceModified()));
  }
}

YAML::Node
FMChannel::serialize(const Context &context, const ErrorStack &err) {
  YAML::Node node = AnalogChannel::serialize(context, err);
  if (node.IsNull())
    return node;

  YAML::Node type;
  type["analog"] = node;
  return type;
}

bool
FMChannel::populate(YAML::Node &node, const Context &context, const ErrorStack &err) {
  if (! AnalogChannel::populate(node, context, err))
    return false;

  if (defaultSquelch()) {
    YAML::Node def = YAML::Node(YAML::NodeType::Scalar); def.SetTag("!default");
    node["squelch"] = def;
  } else {
    node["squelch"] = squelch();
  }

  return true;
}

bool
FMChannel::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) {
  if (! node)
    return false;

  if ((! node.IsMap()) || (1 != node.size())) {
    errMsg(err) << node.Mark().line << ":" << node.Mark().column
                << ": Cannot parse analog channel: Expected object with one child.";
    return false;
  }

  YAML::Node ch = node.begin()->second;

  if ((!ch["squelch"]) || ("!default" == ch["squelch"].Tag())) {
    setSquelchDefault();
  } else if (ch["squelch"].IsDefined() && ch["squelch"].IsScalar()) {
    setSquelch(ch["squelch"].as<unsigned>());
  }

  return AnalogChannel::parse(node, ctx, err);
}


/* ********************************************************************************************* *
 * Implementation of DigitalChannel
 * ********************************************************************************************* */
DigitalChannel::DigitalChannel(QObject *parent)
  : Channel(parent)
{
  // pass...
}

DigitalChannel::DigitalChannel(const DigitalChannel &other, QObject *parent)
  : Channel(other, parent)
{
  // pass...
}


/* ********************************************************************************************* *
 * Implementation of DMRChannel
 * ********************************************************************************************* */
DMRChannel::DMRChannel(QObject *parent)
  : DigitalChannel(parent), _admit(Admit::Always),
    _colorCode(1), _timeSlot(TimeSlot::TS1),
    _rxGroup(), _txContact(), _posSystem(), _roaming(), _radioId(),
    _commercialExtension(nullptr), _anytoneExtension(nullptr)
{
  // Register default tags
  if (! ConfigItem::Context::hasTag(staticMetaObject.className(), "roaming", "!default"))
    ConfigItem::Context::setTag(staticMetaObject.className(), "roaming", "!default", DefaultRoamingZone::get());
  if (! ConfigItem::Context::hasTag(staticMetaObject.className(), "radioId", "!default"))
    ConfigItem::Context::setTag(staticMetaObject.className(), "radioId", "!default", DefaultRadioID::get());

  // Set default DMR Id
  _radioId.set(DefaultRadioID::get());

  // Connect signals of references
  connect(&_rxGroup, SIGNAL(modified()), this, SLOT(onReferenceModified()));
  connect(&_txContact, SIGNAL(modified()), this, SLOT(onReferenceModified()));
  connect(&_posSystem, SIGNAL(modified()), this, SLOT(onReferenceModified()));
  connect(&_roaming, SIGNAL(modified()), this, SLOT(onReferenceModified()));
  connect(&_radioId, SIGNAL(modified()), this, SLOT(onReferenceModified()));
}

void
DMRChannel::clear() {
  DigitalChannel::clear();
  setColorCode(1);
  setTimeSlot(TimeSlot::TS1);
  setGroupListObj(nullptr);
  setTXContactObj(nullptr);
  setAPRSObj(nullptr);
  setRoamingZone(nullptr);
  setRadioIdObj(DefaultRadioID::get());
  setCommercialExtension(nullptr);
  setAnytoneChannelExtension(nullptr);
}

ConfigItem *
DMRChannel::clone() const {
  DMRChannel *c = new DMRChannel();
  if (! c->copy(*this)) {
    c->deleteLater();
    return nullptr;
  }
  return c;
}

DMRChannel::Admit
DMRChannel::admit() const {
  return _admit;
}
void
DMRChannel::setAdmit(Admit admit) {
  _admit = admit;
  emit modified(this);
}

unsigned
DMRChannel::colorCode() const {
  return _colorCode;
}
bool
DMRChannel::setColorCode(unsigned cc) {
  if (15 < cc) {
    logWarn() << "Color-code " << cc << " is not in range [0,15], set to 15.";
    cc = 15;
  }
  _colorCode = cc;
  emit modified(this);
  return true;
}

DMRChannel::TimeSlot
DMRChannel::timeSlot() const {
  return _timeSlot;
}
bool
DMRChannel::setTimeSlot(TimeSlot slot) {
  _timeSlot = slot;
  emit modified(this);
  return true;
}

const GroupListReference *
DMRChannel::groupList() const {
  return &_rxGroup;
}

GroupListReference *
DMRChannel::groupList() {
  return &_rxGroup;
}

void
DMRChannel::setGroupList(GroupListReference *ref) {
  if (nullptr == ref)
    _rxGroup.clear();
  else
    _rxGroup.copy(ref);
}

RXGroupList *
DMRChannel::groupListObj() const {
  return _rxGroup.as<RXGroupList>();
}

bool
DMRChannel::setGroupListObj(RXGroupList *g) {
  if(! _rxGroup.set(g))
    return false;
  emit modified(this);
  return true;
}

const DMRContactReference *
DMRChannel::contact() const {
  return &_txContact;
}

DMRContactReference *
DMRChannel::contact() {
  return &_txContact;
}

void
DMRChannel::setContact(DMRContactReference *ref) {
  if (nullptr == ref)
    _txContact.clear();
  else
    _txContact.copy(ref);
}

DMRContact *
DMRChannel::txContactObj() const {
  return _txContact.as<DMRContact>();
}

bool
DMRChannel::setTXContactObj(DMRContact *c) {
  if(! _txContact.set(c))
    return false;
  emit modified(this);
  return true;
}

const PositioningSystemReference *
DMRChannel::aprs() const {
  return &_posSystem;
}

PositioningSystemReference *
DMRChannel::aprs() {
  return &_posSystem;
}

void
DMRChannel::setAPRS(PositioningSystemReference *ref) {
  if (nullptr == ref)
    _posSystem.clear();
  else
    _posSystem.copy(ref);
}

PositioningSystem *
DMRChannel::aprsObj() const {
  return _posSystem.as<PositioningSystem>();
}

bool
DMRChannel::setAPRSObj(PositioningSystem *sys) {
  if (! _posSystem.set(sys))
    return false;
  emit modified(this);
  return true;
}

const RoamingZoneReference *
DMRChannel::roaming() const {
  return &_roaming;
}

RoamingZoneReference *
DMRChannel::roaming() {
  return &_roaming;
}

void
DMRChannel::setRoaming(RoamingZoneReference *ref) {
  if (nullptr == ref)
    _roaming.clear();
  else
    _roaming.copy(ref);
}

RoamingZone *
DMRChannel::roamingZone() const {
  return _roaming.as<RoamingZone>();
}

bool
DMRChannel::setRoamingZone(RoamingZone *zone) {
  _roaming.set(zone);
  emit modified(this);
  return true;
}

const DMRRadioIDReference *
DMRChannel::radioId() const {
  return &_radioId;
}

DMRRadioIDReference *
DMRChannel::radioId() {
  return &_radioId;
}

void
DMRChannel::setRadioId(DMRRadioIDReference *ref) {
  if (nullptr == ref)
    _radioId.clear();
  else
    _radioId.copy(ref);
}

DMRRadioID *
DMRChannel::radioIdObj() const {
  return _radioId.as<DMRRadioID>();
}

bool
DMRChannel::setRadioIdObj(DMRRadioID *id) {
  if (! _radioId.set(id))
    return false;
  emit modified(this);
  return true;
}

CommercialChannelExtension *
DMRChannel::commercialExtension() const {
  return _commercialExtension;
}
void
DMRChannel::setCommercialExtension(CommercialChannelExtension *ext) {
  if (_commercialExtension == ext)
    return;
  if (_commercialExtension)
    _commercialExtension->deleteLater();
  _commercialExtension = ext;
  if (_commercialExtension) {
    _commercialExtension->setParent(this);
    connect(_commercialExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onReferenceModified()));
  }
}

AnytoneDMRChannelExtension *
DMRChannel::anytoneChannelExtension() const {
  return _anytoneExtension;
}
void
DMRChannel::setAnytoneChannelExtension(AnytoneDMRChannelExtension *ext) {
  if (_anytoneExtension == ext)
    return;
  if (_anytoneExtension)
    _anytoneExtension->deleteLater();
  _anytoneExtension = ext;
  if (_anytoneExtension) {
    _anytoneExtension->setParent(this);
    connect(_anytoneExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onReferenceModified()));
  }
}

YAML::Node
DMRChannel::serialize(const Context &context, const ErrorStack &err) {
  YAML::Node node = DigitalChannel::serialize(context, err);
  if (node.IsNull())
    return node;

  YAML::Node type;
  type["digital"] = node;
  return type;
}


/* ********************************************************************************************* *
 * Implementation of SelectedChannel
 * ********************************************************************************************* */
SelectedChannel *SelectedChannel::_instance = nullptr;

SelectedChannel::SelectedChannel()
  : Channel()
{
  setName("[Selected]");
}

SelectedChannel::~SelectedChannel() {
  SelectedChannel::_instance = nullptr;
}

bool
SelectedChannel::copy(const ConfigItem &other) {
  Q_UNUSED(other);
  return false;
}
ConfigItem *
SelectedChannel::clone() const {
  return nullptr;
}

SelectedChannel *
SelectedChannel::get() {
  if (nullptr == SelectedChannel::_instance)
    SelectedChannel::_instance = new SelectedChannel();
  return SelectedChannel::_instance;
}


/* ********************************************************************************************* *
 * Implementation of ChannelList
 * ********************************************************************************************* */
ChannelList::ChannelList(QObject *parent)
  : ConfigObjectList(Channel::staticMetaObject, parent)
{
  // pass...
}

int
ChannelList::add(ConfigObject *obj, int row, bool unique) {
  if ((nullptr == obj) || (! obj->is<Channel>())) {
    logError() << "Cannot add nullptr or non-channel objects to channel list.";
    return -1;
  }
  return ConfigObjectList::add(obj, row, unique);
}

Channel *
ChannelList::channel(int idx) const {
  if (ConfigItem *obj = get(idx))
    return obj->as<Channel>();
  return nullptr;
}

DMRChannel *
ChannelList::findDMRChannel(Frequency rx, Frequency tx, DMRChannel::TimeSlot ts, unsigned cc) const {
  for (int i=0; i<count(); i++) {
    if (! _items[i]->is<DMRChannel>())
      continue;
    /// @bug I should certainly change the frequency handling to integer values!
    if ((channel(i)->txFrequency()!=tx) || (channel(i)->rxFrequency()!=rx))
      continue;
    DMRChannel *digi = channel(i)->as<DMRChannel>();
    if (digi->timeSlot() != ts)
      continue;
    if (digi->colorCode() != cc)
      continue;
    return digi;
  }
  return nullptr;
}

FMChannel *
ChannelList::findFMChannelByTxFreq(Frequency freq) const {
  for (int i=0; i<count(); i++) {
    if (! channel(i)->is<FMChannel>())
      continue;
    if (channel(i)->txFrequency() == freq)
      return channel(i)->as<FMChannel>();
  }
  return nullptr;
}

ConfigItem *
ChannelList::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) {
  Q_UNUSED(ctx)
  if (! node)
    return nullptr;

  if ((! node.IsMap()) || (1 != node.size())) {
    errMsg(err) << node.Mark().line << ":" << node.Mark().column
                << ": Cannot create channel: Expected object with one child.";
    return nullptr;
  }

  QString type = QString::fromStdString(node.begin()->first.as<std::string>());
  if (("digital" == type) || ("dmr" == type)) {
    return new DMRChannel();
  } else if (("analog" == type) || ("fm"==type)) {
    return new FMChannel();
  }

  errMsg(err) << node.Mark().line << ":" << node.Mark().column
              << ": Cannot create channel: Unknown type '" << type << "'.";

  return nullptr;
}

