#include "qsacnuniverse.h"

/**
 * @brief QSacnUniverse::QSacnUniverse
 * @param parent
 * @param universe
 */
QSacnUniverse::QSacnUniverse(QObject *parent, std::shared_ptr<sACN::Universe> universe)
  : QObject(parent)
  , universe_(universe)
{
  if (!universe)
    return;

  data_change_token = universe_->onData([this](DMX::Universe*) {
    emit changed();
  });

  if (!universe->isEditable())
    list_change_token = universe_->onSourceListChange([this]() {
      syncSources();
    });
};


/**
 * @brief QSacnUniverse::~QSacnUniverse
 */
QSacnUniverse::~QSacnUniverse()
{
  // delete QSacnUniverse sources
  auto i = sources_.constBegin();
  while (i != sources_.constEnd()) {
      delete i.value();
      ++i;
  }
}


/**
 * @brief QSacnUniverse::description
 * @return
 */
const QString QSacnUniverse::description() const
{
  auto metadata = universe_->provenance();
  if (!metadata)
    return QString();
  return QString::fromUtf8(metadata->source_name.c_str());
}


/**
 * @brief QSacnUniverse::number
 * @return
 */
uint16_t QSacnUniverse::number() const
{
  auto metadata = universe_->provenance();
  if (!metadata)
    return 0;
  return metadata->universe;
}


/**
 * @brief QSacnUniverse::priority
 * @return
 */
uint8_t QSacnUniverse::priority() const
{
  auto metadata = universe_->provenance();
  if (!metadata)
    return 0;
  return metadata->priority;
}


/**
 * @brief QSacnUniverse::rxRate
 * @return
 */
double QSacnUniverse::rxRate() const
{
  return (universe_) ? universe_->rxRate() : -1;
}


/**
 * @brief QSacnUniverse::slot
 * @param slot
 * @return
 */
uint8_t QSacnUniverse::slot(const uint16_t slot) const
{
  return (universe_) ? universe_->slot(slot) : 0;
}


/**
 * @brief QSacnUniverse::isEditable
 * @return
 */
bool QSacnUniverse::isEditable() const
{
  return (universe_) ? universe_->isEditable() : false;
}


/**
 * @brief QSacnUniverse::sources
 * @return
 */
QList<sACN::DATA::data_header> QSacnUniverse::sources() const
{
  QList<sACN::DATA::data_header> ret;

  if (!universe_)
    return ret;

  for (const auto & src : universe_->sources())
    ret.append(src);

  return ret;
}


/**
 * @brief QSacnUniverse::sourceUniverse
 * @param metadata
 * @return
 */
QSacnUniverse* QSacnUniverse::sourceUniverse(
                                        const sACN::DATA::data_header& metadata)
{
  if (!universe_)
    return nullptr;

  if (!sources_.count(metadata))
    return nullptr;

  return sources_.value(metadata);
}


/**
 * @brief QSacnUniverse::activeSlots
 * @return
 */
uint16_t QSacnUniverse::activeSlots() const
{
  return (universe_) ? universe_->activeSlots() : 0;
}


/**
 * @brief QSacnUniverse::setDescription
 * @param desc
 */
void QSacnUniverse::setDescription(std::string desc)
{
  if (!isEditable())
    return;

  universe_->provenance()->source_name = desc;
  emit changed();
}


/**
 * @brief QSacnUniverse::setOptions
 * @param o
 */
void QSacnUniverse::setOptions(sACN::DATA::data_options o)
{
  if (!isEditable())
    return;

  universe_->provenance()->options = o;
  emit changed();
}


/**
 * @brief QSacnUniverse::setPriority
 * @param p
 */
void QSacnUniverse::setPriority(uint8_t p)
{
  if (!isEditable())
    return;

  universe_->provenance()->priority = p;
  emit changed();
}


/**
 * @brief QSacnUniverse::setSyncAddress
 * @param a
 */
void QSacnUniverse::setSyncAddress(uint16_t a)
{
  if (!isEditable())
    return;

  universe_->provenance()->sync_address = a;
  emit changed();
}


/**
 * @brief QSacnUniverse::setValue
 * @param addr
 * @param level
 */
void QSacnUniverse::setValue (const uint16_t addr, const uint8_t level)
{
  if (!isEditable())
    return;

  universe_->sACN::Universe::setValue(addr, level);
  emit changed();
}


/**
 * @brief QSacnUniverse::setValue
 * @param addr
 * @param size
 * @param profile
 */
void QSacnUniverse::setValue (const uint16_t addr, const uint16_t size,
               const uint8_t* profile)
{
  if (!isEditable())
    return;

  universe_->sACN::Universe::setValue(addr, size, profile);
  emit changed();
}


/**
 * @brief QSacnUniverse::syncSources
 */
void QSacnUniverse::syncSources()
{
  QMap<sACN::DATA::data_header, QSacnUniverse*> newSources;

  auto headers = universe_->sources();
  for (const auto & metadata : headers)
    {
      auto it = sources_.find(metadata);
      if (it == sources_.end())
        { // not found
          auto universe = universe_->sourceUniverse(metadata);
          newSources.insert(metadata, new QSacnUniverse(this, universe));
        }
      else
        { // found
          auto universe = sources_.take(metadata);
          newSources.insert(metadata, universe);
        }
    }

  // delete any QSacnUniverse that are no longer sources
  auto i = sources_.constBegin();
  while (i != sources_.constEnd()) {
      delete i.value();
      ++i;
  }

  sources_ = newSources;
  emit sourceListChanged();
}
