#include "muebtransmitter.h"

#include "muebtransmitter_p.h"

MuebTransmitter::MuebTransmitter(QObject* parent)
    : QObject(parent), d_ptr_(new MuebTransmitterPrivate(this)) {}

MuebTransmitter::~MuebTransmitter() { delete d_ptr_; }

void MuebTransmitter::SendFrame(libmueb::Frame frame) {
  Q_D(MuebTransmitter);

  if (frame.isNull() || frame.format() != QImage::Format_RGB888 ||
      frame.width() != d->configuration_.width() ||
      frame.height() != d->configuration_.height()) {
    qWarning() << "[MuebTransmitter] Frame is invalid";
    return;
  }

  frame.convertTo(QImage::Format_RGB888);

  QByteArray reduced_compressed_frame;
  // Frame color reduction and compression
  if (d->configuration_.color_depth() < 5) {
    reduced_compressed_frame = QtConcurrent::blockingMappedReduced<QByteArray>(
        frame.constBits(), frame.constBits() + frame.sizeInBytes(),
        /* Reference:
         * http://threadlocalmutex.com/?p=48
         * http://threadlocalmutex.com/?page_id=60
         */
        [d](const uchar& color) -> uchar {
          if (d->configuration_.color_depth() == 3) {
            return (color * 225 + 4096) >> 13;
          } else if (d->configuration_.color_depth() == 4) {
            return (color * 15 + 135) >> 8;
          }

          return color;
        },
        [d](QByteArray& compressed_colors, const uchar& color) {
          static bool msb{true};

          // Compress 2 color components into 1 byte
          if (msb) {
            compressed_colors.append(color << d->configuration_.factor());
          } else {
            compressed_colors.back() = compressed_colors.back() | color;
          }

          msb = !msb;
        },
        QtConcurrent::OrderedReduce | QtConcurrent::SequentialReduce);
  }
  // No compression
  else {
    reduced_compressed_frame.setRawData(
        reinterpret_cast<const char*>(frame.bits()), frame.sizeInBytes());
  }

  if (d->configuration_.max_packet_number() == 1) {
    reduced_compressed_frame.insert(0, d->configuration_.protocol_type())
        .insert(1, static_cast<char>(0) /*packet number*/);

    d->datagram_.setData(reduced_compressed_frame);
    d->socket_.writeDatagram(d->datagram_);
  } else {
    for (std::uint8_t i = 0; i < d->configuration_.max_packet_number(); ++i) {
      QByteArray data;
      data.append(d->configuration_.protocol_type())
          .append(i /*packet number*/)
          .append(reduced_compressed_frame.sliced(
              i * d->configuration_.packet_payload_size(),
              d->configuration_.packet_payload_size()));

      d->datagram_.setData(data);
      d->socket_.writeDatagram(d->datagram_);
    }
  }
}

MuebTransmitter& MuebTransmitter::Instance() {
  static MuebTransmitter instance;

  return instance;
}

std::int32_t MuebTransmitter::width() const {
  Q_D(const MuebTransmitter);

  return d->configuration_.width();
}

std::int32_t MuebTransmitter::height() const {
  Q_D(const MuebTransmitter);

  return d->configuration_.height();
}

libmueb::Frame MuebTransmitter::frame() const {
  Q_D(const MuebTransmitter);

  return d->configuration_.frame();
}