#if !defined(WIN32) && defined(_MSC_VER)
#define WIN32
#endif

#ifdef WIN32
#define NOMINMAX
#include <Winsock2.h>
#include <Ws2tcpip.h>
#include <iphlpapi.h>
#define _USE_MATH_DEFINES
#else
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#endif

#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstring>
#include <ctime>
#include <condition_variable>
#include <deque>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <random>
#include <sstream>
#include <string>
#include <thread>
#include <utility>
#include <vector>

namespace stdex {

static inline size_t _htons(const unsigned char* srcFirst, const unsigned char* srcLast, unsigned char* dst)
{
  size_t result = 0;
  size_t packet16count = (srcLast-srcFirst)/sizeof(uint16_t);
  size_t packet8count  = (srcLast-srcFirst)%sizeof(uint16_t);
  const uint16_t* reader16 = reinterpret_cast<const uint16_t*>(srcFirst);
  uint16_t* writer16 = reinterpret_cast<uint16_t*>(dst);
  while(packet16count--)
    *writer16++ = htons(*reader16++);
  if (packet8count)
    memcpy(writer16, reader16, packet8count);
  result = (srcLast-srcFirst);
  return result;
}
//end _htons()

}//end namespace stdex

class Bitmap
{
  public:
    Bitmap(int width = 0, int height=0, int stride=0)
          :width(std::max(0, width)),height(std::max(0, height)),stride(!stride ? 12*((width+11)/12) : stride) {
      this->data.resize(this->height*this->stride);
      if (!this->data.empty())
        memset(&this->data[0], 0, this->data.size()*sizeof(unsigned char));
    }
    Bitmap(const Bitmap& other)
          :width(other.width),height(other.height),stride(other.stride),data(other.data) {
    }
    Bitmap(Bitmap&& other)
          :width(other.width),height(other.height),stride(other.stride) {
      other.width = 0;
      other.height = 0;
      other.stride = 0;
      this->data = std::move(other.data);
    }
  public:
    Bitmap& operator=(const Bitmap& other) {
      this->width = other.width;
      this->height = other.height;
      this->stride = other.stride;
      this->data = other.data;
      return *this;
    }//end operator=()
  public:
    bool empty(void)     const {return this->data.empty();}
    int  getWidth(void)  const {return this->width;}
    int  getHeight(void) const {return this->height;}
    int  getStride(void) const {return this->stride;}
    const std::vector<unsigned char>& getData(void) const {return this->data;}
  public:
    void resize(int aWidth = 0, int aHeight=0, int aStride=0)
    {
      this->width  = std::max(0, aWidth);
      this->height = std::max(0, aHeight);
      this->stride = std::max(0, !aStride ? 12*((this->width+11)/12) : aStride);
      this->data.resize(this->height*this->stride);
      if (!this->data.empty())
        memset(&this->data[0], 0, this->data.size()*sizeof(unsigned char));
    }//end resiz()
  private:
    int width;
    int height;
    int stride;
    std::vector<unsigned char> data;
};
//end class Bitmap

static volatile bool threadsShouldRun = true;

static std::mutex bitmapsQueueMutex;
static std::condition_variable bitmapsQueueSemaphore;
static std::deque<std::pair<Bitmap, std::chrono::system_clock::duration> > bitmapsQueue;

typedef enum {PIXEL_FORMAT_UNDEFINED, PIXEL_FORMAT_RGB888, PIXEL_FORMAT_YCrCb422_8, PIXEL_FORMAT_YCrCb422_10, PIXEL_FORMAT_YCrCb422_12, PIXEL_FORMAT_YCrCb422_14, PIXEL_FORMAT_YCrCb422_16} pixelFormat_t;

void bitmap_draw(Bitmap& bitmap, const std::pair<int, int>& squarePosition, int squareSize)
{
  if (!bitmap.empty())
  {
    auto now = std::chrono::system_clock::now();
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
    auto shift = (ms%10000)/10000.;
    const std::vector<unsigned char>& data = bitmap.getData();
    unsigned char* ptr = const_cast<unsigned char*>(&data[0]);
    for(int row = 0 ; row < bitmap.getHeight() ; ++row)
    {
      unsigned char* pRow = ptr+row*bitmap.getStride();
      unsigned char* pRowEnd = pRow+bitmap.getStride();
      memset(pRow, static_cast<unsigned char>(196*std::abs(sin(2*M_PI*row/bitmap.getHeight()-shift*2*M_PI))), pRowEnd-pRow);
    }//end for each row
    for(int row = squarePosition.second ; row < std::min(squarePosition.second+squareSize, bitmap.getHeight()) ; ++row)
    {
      unsigned char* pRow = ptr+row*bitmap.getStride();
      unsigned char* pRowEnd = pRow+bitmap.getStride();
      unsigned char* first = std::min(pRowEnd, pRow+squarePosition.first);
      unsigned char* last = std::min(pRowEnd, first+squareSize);
      memset(first, 0xFF, (last-first)*sizeof(unsigned char));
    }//end for each row
  }//end if (!bitmap.empty())
}
//end bitmap_draw()

void bitmap_generation(double fps, int bitmap_width, int bitmap_height)
{
  const size_t bitmapsQueueCapacity = 100;
  const unsigned int delayMs = static_cast<unsigned int>(std::round(1000./fps));
  int squareSize = 20;
  std::pair<int, int> step(1, 1);
  std::pair<int, int> squarePosition(1, 1);

  auto timestampTimeReference = std::chrono::system_clock::now();
  auto fpsTimeReference = std::chrono::system_clock::now();
  auto nextEmissionDate = std::chrono::system_clock::now();
  size_t bitmapEmittedCount = 0;

  Bitmap bitmap(bitmap_width, bitmap_height);
  while(threadsShouldRun)
  {
    bitmap.resize(bitmap_width, bitmap_height);
    bitmap_draw(bitmap, squarePosition, squareSize);
    std::this_thread::sleep_until(nextEmissionDate);
    auto now = std::chrono::system_clock::now();

    {
      std::unique_lock<std::mutex> bitmapsQueueLock(bitmapsQueueMutex);
      if (bitmapsQueue.size() < bitmapsQueueCapacity)
      {
        auto duration = now-timestampTimeReference;
        bitmapsQueue.emplace_back(std::make_pair(std::move(bitmap), duration));
        bitmapsQueueSemaphore.notify_all();
        ++bitmapEmittedCount;
      }//end if (bitmapsQueue.size() < bitmapsQueueCapacity)
    }

    if (!(bitmapEmittedCount%100))
    {
      printf("<~%.2ffps>", 1000.*bitmapEmittedCount/std::chrono::duration_cast<std::chrono::milliseconds>(now-fpsTimeReference).count());
      fflush(stdout);
      fpsTimeReference = now;
      bitmapEmittedCount = 0;
    }//end if (!(bitmapEmittedCount%100))

    if (((squarePosition.first+step.first)<0) || ((squarePosition.first+step.first)>=(bitmap_width-squareSize)))
      step.first = -step.first;
    if (((squarePosition.second+step.second)<0) || ((squarePosition.second+step.second)>=(bitmap_height-squareSize)))
      step.second = -step.second;
    squarePosition.first += step.first;
    squarePosition.second += step.second;

    nextEmissionDate += std::chrono::milliseconds(delayMs);
  }//end while(threadsShouldRun)
}
//end bitmap_generation()

void bitmap_emit(const std::pair<Bitmap, std::chrono::system_clock::duration>& bitmapAndTime, const pixelFormat_t outPixelFormat,
                 const unsigned int ssrc, const int dynPt, unsigned int& rtpSequenceNumber,
                 int socket, const struct sockaddr_in* address, const unsigned int mtu,
                 std::vector<unsigned char>& workBuffer)
{
  #pragma pack(push, 1)
  typedef struct {
    uint8_t cc:4;
    uint8_t padding:1;
    uint8_t extension:1;
    uint8_t version:2;

    uint8_t pt:7;
    uint8_t marker:1;

    uint16_t sequenceNumber:16;

    uint32_t timestamp:32;

    uint32_t ssrc:32;

    uint16_t extendedSequenceNumber:16;
  } rtp_header_t;
  typedef struct {
    uint16_t length;

    uint16_t lineNo:15;
    uint16_t F:1;

    uint16_t offset:15;
    uint16_t C:1;
  } line_header_t;
  #pragma pack(pop)

  const size_t validMtu = !mtu ? 1500 : mtu;
  const size_t ipv4HeaderMinSize = 20;
  const size_t ipv6HeaderMinSize = 48;
  const size_t ipHeaderMinSize = !address ? 0 :
    (address->sin_family == AF_INET)  ? ipv4HeaderMinSize :
    (address->sin_family == AF_INET6) ? ipv6HeaderMinSize :
    0;
  const size_t udpHeaderSize = 8;
  const size_t maximumUDPPayload = std::max(validMtu, (ipHeaderMinSize+udpHeaderSize))-(ipHeaderMinSize+udpHeaderSize);

  const size_t pgroup =
    (outPixelFormat == PIXEL_FORMAT_RGB888)           ?  3 : 
    (outPixelFormat == PIXEL_FORMAT_YCrCb422_8)       ?  4 :
    (outPixelFormat == PIXEL_FORMAT_YCrCb422_10)      ?  5 :
    (outPixelFormat == PIXEL_FORMAT_YCrCb422_12)      ?  6 :
    (outPixelFormat == PIXEL_FORMAT_YCrCb422_14)      ?  7 :
    (outPixelFormat == PIXEL_FORMAT_YCrCb422_16)      ?  8 :
    0;
  const size_t rtp_header_size = sizeof(rtp_header_t);
  const size_t line_header_size = sizeof(line_header_t);
  const size_t min_line_size = line_header_size + pgroup;
  const size_t payload = maximumUDPPayload-rtp_header_size;

  const Bitmap& bitmap = bitmapAndTime.first;
  const std::chrono::system_clock::duration& time = bitmapAndTime.second;
  if (!bitmap.empty())
  {
    const std::vector<unsigned char>& bitmapData = bitmap.getData();
    const unsigned char* pixelData = &bitmapData[0];

    rtp_header_t rtpHeader = {0};
    rtpHeader.version = 2;
    rtpHeader.padding = 0;
    rtpHeader.extension = 0;
    rtpHeader.cc = 0;
    rtpHeader.marker = 0;
    rtpHeader.pt = dynPt;
    auto timeStampMus = std::chrono::duration_cast<std::chrono::milliseconds>(time);
    uint32_t rtpTimestamp = static_cast<uint32_t>(90000*timeStampMus.count()/1000);
    rtpHeader.timestamp = htonl(rtpTimestamp);
    rtpHeader.ssrc = htonl(ssrc);

    std::vector<unsigned char>& buffer = workBuffer;
    buffer.resize(rtp_header_size+payload);
    std::vector<std::pair<const unsigned char*, const unsigned char*> > fragmentsToWrite;
    unsigned char* bufferStart = &buffer[0];
    unsigned char* bufferEnd = bufferStart+buffer.size();
    int currentRow = 0;
    const unsigned char* pRowSrc = pixelData+currentRow*bitmap.getStride();
    const unsigned char* pRowSrcEnd = pRowSrc+bitmap.getWidth();
    const unsigned char* pRowReader = pRowSrc;
    while(currentRow < bitmap.getHeight())
    {
      unsigned char* writer = bufferStart+rtp_header_size;
      fragmentsToWrite.resize(0);
      size_t remainingRoom = bufferEnd-writer;
      while(remainingRoom >= min_line_size)
      {
        size_t remainingRoomForPixels = (remainingRoom-line_header_size);
        size_t maxEmittablePixelsCount =
         (outPixelFormat == PIXEL_FORMAT_RGB888)           ? 1*(remainingRoomForPixels/pgroup) :
         (outPixelFormat == PIXEL_FORMAT_YCrCb422_8)       ? 2*(remainingRoomForPixels/pgroup) :
         (outPixelFormat == PIXEL_FORMAT_YCrCb422_10)      ? 2*(remainingRoomForPixels/pgroup) :
         (outPixelFormat == PIXEL_FORMAT_YCrCb422_12)      ? 2*(remainingRoomForPixels/pgroup) :
         (outPixelFormat == PIXEL_FORMAT_YCrCb422_14)      ? 2*(remainingRoomForPixels/pgroup) :
         (outPixelFormat == PIXEL_FORMAT_YCrCb422_16)      ? 2*(remainingRoomForPixels/pgroup) :
         0;
        size_t remainingPixelsToEmitFromCurrentRow = (pRowSrcEnd-pRowReader);
        size_t pixelsToEmitOnCurrentFragment = std::min(maxEmittablePixelsCount, remainingPixelsToEmitFromCurrentRow);
        size_t bytesToEmitOnCurrentFragment =
          (outPixelFormat == PIXEL_FORMAT_RGB888)           ? (((pixelsToEmitOnCurrentFragment+0)/1)*pgroup) :
          (outPixelFormat == PIXEL_FORMAT_YCrCb422_8)       ? (((pixelsToEmitOnCurrentFragment+1)/2)*pgroup) :
          (outPixelFormat == PIXEL_FORMAT_YCrCb422_10)      ? (((pixelsToEmitOnCurrentFragment+1)/2)*pgroup) :
          (outPixelFormat == PIXEL_FORMAT_YCrCb422_12)      ? (((pixelsToEmitOnCurrentFragment+1)/2)*pgroup) :
          (outPixelFormat == PIXEL_FORMAT_YCrCb422_14)      ? (((pixelsToEmitOnCurrentFragment+1)/2)*pgroup) :
          (outPixelFormat == PIXEL_FORMAT_YCrCb422_16)      ? (((pixelsToEmitOnCurrentFragment+1)/2)*pgroup) :
          0;
        bool isLastFragmentInCurrentPacket = (line_header_size+bytesToEmitOnCurrentFragment+min_line_size > remainingRoom);
        line_header_t lineHeader = {0};
        lineHeader.length = static_cast<uint16_t>(bytesToEmitOnCurrentFragment);
        lineHeader.F = 0;
        lineHeader.lineNo = static_cast<uint16_t>(currentRow);
        lineHeader.C = isLastFragmentInCurrentPacket ? 0 : 1;
        lineHeader.offset = static_cast<uint16_t>(pRowReader-pRowSrc);
        writer += stdex::_htons(reinterpret_cast<const unsigned char*>(&lineHeader), reinterpret_cast<const unsigned char*>(&lineHeader)+line_header_size, writer);
        remainingRoom -= line_header_size+bytesToEmitOnCurrentFragment;
        const unsigned char* pRowReaderNext = std::min(pRowSrcEnd, pRowReader+pixelsToEmitOnCurrentFragment);
        fragmentsToWrite.push_back(std::make_pair(pRowReader, pRowReaderNext));
        pRowReader = pRowReaderNext;
        bool isRowCompleted = (pRowReader >= pRowSrcEnd);
        if (isRowCompleted)
        {
          ++currentRow;
          if (currentRow < bitmap.getHeight())
          {
            pRowSrc = pixelData+currentRow*bitmap.getStride();
            pRowSrcEnd = pRowSrc+bitmap.getWidth();
            pRowReader = pRowSrc;
          }//end if (currentRow < bitmapHeight)
        }//end if (isRowCompleted)
      }//end while(remainingRoom >= min_line_size)
      rtpHeader.marker = (currentRow < bitmap.getHeight()) ? 0 : 1;
      if (outPixelFormat == PIXEL_FORMAT_RGB888)
      {
        for(auto lineFragment : fragmentsToWrite)
        {
          for(const unsigned char* pPixel = lineFragment.first ; pPixel < lineFragment.second ; )
          {
            uint8_t pixel = static_cast<uint8_t>((pPixel < lineFragment.second) ? *pPixel++ : 0);
            uint8_t R = pixel;
            uint8_t G = pixel;
            uint8_t B = pixel;
            *writer++ = R;
            *writer++ = G;
            *writer++ = B;
          }//end for each pixel
        }//end for each fragment
      }//end if (outPixelFormat == PIXEL_FORMAT_RGB888)
      else if (outPixelFormat == PIXEL_FORMAT_YCrCb422_8)
      {
        for(auto lineFragment : fragmentsToWrite)
        {
          for(const unsigned char* pPixel = lineFragment.first ; pPixel < lineFragment.second ; )
          {
            uint8_t Cr = static_cast<uint8_t>((1<<8)/2);
            uint8_t Cb = static_cast<uint8_t>((1<<8)/2);
            uint8_t Y0 = static_cast<uint8_t>((pPixel < lineFragment.second) ? *pPixel++ : 0);
            uint8_t Y1 = static_cast<uint8_t>((pPixel < lineFragment.second) ? *pPixel++ : 0);
            *writer++ = Cr;
            *writer++ = Y0;
            *writer++ = Cb;
            *writer++ = Y1;
          }//end for each pixel
        }//end for each fragment
      }//end if (outPixelFormat == PIXEL_FORMAT_YCrCb422_8)
      else if (outPixelFormat == PIXEL_FORMAT_YCrCb422_10)
      {
        for(auto lineFragment : fragmentsToWrite)
        {
          for(const unsigned char* pPixel = lineFragment.first ; pPixel < lineFragment.second ; )
          {
            uint16_t Cr = static_cast<uint16_t>((1<<10)/2);
            uint16_t Cb = static_cast<uint16_t>((1<<10)/2);
            uint16_t Y0 = static_cast<uint16_t>((pPixel < lineFragment.second) ? *pPixel++ : 0) << 2;
            uint16_t Y1 = static_cast<uint16_t>((pPixel < lineFragment.second) ? *pPixel++ : 0) << 2;
            *writer++ =                 0  | ((Cr >> 2) & 0xFF);//             Cr high (8)
            *writer++ = ((Cr & 0x03) << 6) | ((Y0 >> 4) & 0x3F);//Cr low (2) | Y0 high (6)
            *writer++ = ((Y0 & 0x0F) << 4) | ((Cb >> 6) & 0x0F);//Y0 low (4) | Cb high (4)
            *writer++ = ((Cb & 0x3F) << 2) | ((Y1 >> 8) & 0x03);//Cb low (6) | Y1 high (2)
            *writer++ = ((Y1 & 0xFF) << 0) |                  0;//Y1 low (8)
          }//end for each pixel
        }//end for each fragment
      }//end if (outPixelFormat == PIXEL_FORMAT_YCrCb422_10)
      else if (outPixelFormat == PIXEL_FORMAT_YCrCb422_12)
      {
        for(auto lineFragment : fragmentsToWrite)
        {
          for(const unsigned char* pPixel = lineFragment.first ; pPixel < lineFragment.second ; )
          {
            uint16_t Cr = static_cast<uint16_t>((1<<12)/2);
            uint16_t Cb = static_cast<uint16_t>((1<<12)/2);
            uint16_t Y0 = static_cast<uint16_t>((pPixel < lineFragment.second) ? *pPixel++ : 0) << 4;
            uint16_t Y1 = static_cast<uint16_t>((pPixel < lineFragment.second) ? *pPixel++ : 0) << 4;
            *writer++ =                 0  | ((Cr >> 4) & 0xFF);//            | Cr high (8)
            *writer++ = ((Cr & 0x0F) << 4) | ((Y0 >> 8) & 0x0F);//Cr  low (4) | Y0 high (4)
            *writer++ = ((Y0 & 0xFF) << 0) |                  0;//Y0  low (8) |
            *writer++ =                 0  | ((Cb >> 4) & 0xFF);//            | Cb high (8)
            *writer++ = ((Cb & 0x0F) << 4) | ((Y1 >> 8) & 0x0F);//Cb  low (4) | Y1 high (4)
            *writer++ = ((Y1 & 0xFF) << 0) |                  0;//Y1  low (8) |
          }//end for each pixel
        }//end for each fragment
      }//end if (outPixelFormat == PIXEL_FORMAT_YCrCb422_12)
      else if (outPixelFormat == PIXEL_FORMAT_YCrCb422_14)
      {
        for(auto lineFragment : fragmentsToWrite)
        {
          for(const unsigned char* pPixel = lineFragment.first ; pPixel < lineFragment.second ; )
          {
            uint16_t Cr = static_cast<uint16_t>((1<<14)/2);
            uint16_t Cb = static_cast<uint16_t>((1<<14)/2);
            uint16_t Y0 = static_cast<uint16_t>((pPixel < lineFragment.second) ? *pPixel++ : 0) << 6;
            uint16_t Y1 = static_cast<uint16_t>((pPixel < lineFragment.second) ? *pPixel++ : 0) << 6;
            *writer++ =                 0  | ((Cr >>  6) & 0xFF);//            | Cr high (8)
            *writer++ = ((Cr & 0x3F) << 2) | ((Y0 >> 12) & 0x03);//Cr  low (6) | Y0 high (2)
            *writer++ = ((Y0 >> 4) & 0xFF) |                   0;//Y0  mid (8) |
            *writer++ = ((Y0 & 0x0F) << 4) | ((Cb >> 10) & 0x0F);//Y0  low (4) | Cb high (4)
            *writer++ = ((Cb >> 2) & 0xFF) |                   0;//Cb  mid (8) |
            *writer++ = ((Cb & 0x03) << 6) | ((Y0 >>  8) & 0x3F);//Cb  low (2) | Y1  mid (6)
            *writer++ = ((Y1 & 0xFF) << 0) |                   0;//Y1  low (8) |
          }//end for each pixel
        }//end for each fragment
      }//end if (outPixelFormat == PIXEL_FORMAT_YCrCb422_14)
      else if (outPixelFormat == PIXEL_FORMAT_YCrCb422_16)
      {
        uint16_t* writer16 = reinterpret_cast<uint16_t*>(writer);
        for(auto lineFragment : fragmentsToWrite)
        {
          for(const unsigned char* pPixel = lineFragment.first ; pPixel < lineFragment.second ; )
          {
            uint16_t Cr = static_cast<uint16_t>((1<<16)/2);
            uint16_t Cb = static_cast<uint16_t>((1<<16)/2);
            uint16_t Y0 = static_cast<uint16_t>((pPixel < lineFragment.second) ? *pPixel++ : 0) << 8;
            uint16_t Y1 = static_cast<uint16_t>((pPixel < lineFragment.second) ? *pPixel++ : 0) << 8;
            *writer16++ = htons(Cr);
            *writer16++ = htons(Y0);
            *writer16++ = htons(Cb);
            *writer16++ = htons(Y1);
          }//end for each pixel
        }//end for each fragment
        writer = reinterpret_cast<unsigned char*>(writer16);
      }//end if (outPixelFormat == PIXEL_FORMAT_YCrCb422_16)
      if (rtpHeader.marker)
        std::cout << '.' << std::flush;
      unsigned int currentRtpSequenceNumber = rtpSequenceNumber++;
      rtpHeader.sequenceNumber = htons(static_cast<uint16_t>(currentRtpSequenceNumber) & 0xFFFF);
      rtpHeader.extendedSequenceNumber = htons(static_cast<uint16_t>(currentRtpSequenceNumber >> 16) & 0xFFFF);
      memcpy(&buffer[0], &rtpHeader, rtp_header_size);
      if ((socket >= 0) && address)
        sendto(socket, reinterpret_cast<const char*>(&buffer[0]), static_cast<int>(buffer.size()), 0, reinterpret_cast<const sockaddr*>(address), sizeof(*address));
    }//end while(currentRow < bitmapHeight)
  }//end if (!bitmap.empty())
}
//end bitmap_emit()

void bitmap_emission(const pixelFormat_t outPixelFormat, const std::string& ipString, const uint16_t server_port, const unsigned int mtu, const unsigned int ssrc, const int dynPt)
{
  int error = 0;
  unsigned int rtpSequenceNumber = 0;

  int server_socket = (int)socket(AF_INET, SOCK_DGRAM, (int)IPPROTO_UDP);

  char broadcast = 1;
  error = setsockopt(server_socket, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char*>(&broadcast), sizeof(broadcast));

  struct sockaddr_in broadcast_addr = {0};
  broadcast_addr.sin_family = AF_INET;
  broadcast_addr.sin_port = htons(server_port);
  inet_pton(AF_INET, ipString.c_str(), &broadcast_addr.sin_addr);
  *reinterpret_cast<uint32_t*>(&broadcast_addr.sin_addr) |= htonl(0x000000FF);//make broadcast address
  char addressBuffer[INET_ADDRSTRLEN] = {0};
  inet_ntop(AF_INET, &broadcast_addr.sin_addr, addressBuffer, sizeof(addressBuffer));
  std::cout << "will broadcast to " << addressBuffer << ':' << server_port << std::endl;
  
  std::vector<unsigned char> workBuffer;

  while(threadsShouldRun)
  {
    std::unique_lock<std::mutex> bitmapsQueueLock(bitmapsQueueMutex);
    std::cv_status status = bitmapsQueueSemaphore.wait_for(bitmapsQueueLock, std::chrono::milliseconds(1000));
    while(!bitmapsQueue.empty())
    {
      auto front = std::move(bitmapsQueue.front()); 
      bitmap_emit(front, outPixelFormat, ssrc, dynPt, rtpSequenceNumber, server_socket, &broadcast_addr, mtu, workBuffer);
      bitmapsQueue.pop_front();
    }//end if (!bitmapsQueue.empty())
  }//end while(threadsShouldRun)
}
//end bitmap_emission()

void test_listen_udp(const uint16_t server_port)
{
  std::this_thread::sleep_for(std::chrono::milliseconds(1000));

  int error = 0;

  int listeningSocket = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  struct sockaddr_in sockaddr = {0};
  sockaddr.sin_family = AF_INET;
  sockaddr.sin_port = htons(server_port);
  sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  struct timeval timeV = {0};
  timeV.tv_sec = 2;
  timeV.tv_usec = 0;
    
  error = setsockopt(listeningSocket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeV), sizeof(timeV));

  error = bind(listeningSocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr));
  std::vector<unsigned char> buffer(1U*4*1024*1024);
  while(true)
  {
    memset(&buffer[0], 0, buffer.size()*sizeof(unsigned char));
    struct sockaddr_in receiveSockaddr = {0};
    socklen_t receiveSockaddrLen = sizeof(receiveSockaddr);
    int numbytes = recvfrom(listeningSocket, reinterpret_cast<char*>(&buffer[0]), static_cast<int>(buffer.size()*sizeof(unsigned char)), 0, (struct sockaddr*)&receiveSockaddr, &receiveSockaddrLen);
    std::cout << '<' << numbytes << '>';
  }
}
//end test_listen_udp()

int main(void)
{
  const double fps_generation = 25;

  const int bitmap_width  = 640;
  const int bitmap_height = 480;
  const pixelFormat_t outPixelFormat = PIXEL_FORMAT_RGB888;//PIXEL_FORMAT_YCrCb422_10;//PIXEL_FORMAT_YCrCb422_16;

  const uint16_t server_port = 4567;

  std::random_device randomDevice;
  std::uniform_int_distribution<unsigned int> distribution;
  const unsigned int ssrc = distribution(randomDevice);

  #if defined(WIN32)
  WSADATA wsa = {0};
  WSAStartup(MAKEWORD(2,2), &wsa);
  #endif

  std::string preferredIpString = "127.0.0.1";
  unsigned int mtu = 0;

  #if defined(WIN32)
  std::vector<unsigned char> buffer(1U*4*1024);
  ULONG actualSize = static_cast<ULONG>(buffer.size());
  PMIB_IPADDRTABLE addrTable = reinterpret_cast<PMIB_IPADDRTABLE>(&buffer[0]);
  GetIpAddrTable(addrTable, &actualSize, 0);
  int ifIpv4Index = 0;
  if (actualSize)
  for(DWORD i=0 ; i < addrTable->dwNumEntries ; ++i)
  {
    const MIB_IPADDRROW& entry = addrTable->table[i];
    IN_ADDR ipAddress = {0};
    ipAddress.S_un.S_addr = entry.dwAddr;
    char addressBuffer[INET_ADDRSTRLEN] = {0};
    inet_ntop(AF_INET, &ipAddress, addressBuffer, sizeof(addressBuffer));
    std::string candidate(addressBuffer, strlen(addressBuffer));
    if (candidate != preferredIpString)
    {
      preferredIpString = candidate;
      ifIpv4Index = i;
      break;
    }
  }
  PIP_ADAPTER_ADDRESSES adapterAddresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(&buffer[0]);
  actualSize = static_cast<ULONG>(buffer.size());
  GetAdaptersAddresses(AF_INET, 0, 0, adapterAddresses, &actualSize);
  PIP_ADAPTER_ADDRESSES current = adapterAddresses;
  while(ifIpv4Index && current)
  {
    current = current->Next;
    --ifIpv4Index;
  }
  mtu = !current ? 0 : current->Mtu;
  #else
  struct ifaddrs* ifAddresses = 0;
  getifaddrs(&ifAddresses);
  for (struct ifaddrs* ifa = ifAddresses; ifa != 0; ifa = ifa->ifa_next)
  {
    if (!ifa->ifa_addr) {
    }
    else if (ifa->ifa_addr->sa_family == AF_INET)
    {
      void* tmpAddrPtr = &((struct sockaddr_in*)ifa->ifa_addr)->sin_addr;
      char addressBuffer[INET_ADDRSTRLEN] = {0};
      inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
      std::string candidate(addressBuffer, strlen(addressBuffer));
      if (candidate != preferredIpString)
      {
        preferredIpString = candidate;
        int dummy_socket = (int)socket(AF_INET, SOCK_DGRAM, (int)IPPROTO_UDP);
        struct ifreq ifr = {0};
        strcpy(ifr.ifr_name, ifa->ifa_name);
        int error = ioctl(dummy_socket, SIOCGIFMTU, &ifr);
        mtu = ifr.ifr_mtu;
        if (dummy_socket >= 0)
          close(dummy_socket);
        break;
      }//end if (candidate != preferredIpString)
    }
  }//end for each ifa
  if (ifAddresses)
    freeifaddrs(ifAddresses);
  #endif

  std::cout << "Detected MTU : " << mtu << std::endl;
  std::cout << "here is a SDP description:" << std::endl;
  std::cout << "=================================" << std::endl;
  const int ttl = 127;
  const int dynPt = 112;
  std::stringstream ss;
  ss << "SDP:" << std::endl;
  ss << "v=0" << std::endl;
  ss << "o=" << "toto" << ' ' << ssrc << ' ' << ssrc << ' ' << preferredIpString << std::endl;
  ss << "s=" << ssrc << std::endl;
  ss << "c=" << "IN IP4" << ' ' <<  preferredIpString << '/' << ttl << std::endl;
  ss << "m=" << "video" << ' ' << server_port << ' ' << "RTP/AVP" << ' ' << dynPt << std::endl;
  ss << "a=" << "rtpmap:" << dynPt << ' ' << "raw/90000" << std::endl;
  if (outPixelFormat == PIXEL_FORMAT_RGB888)
    ss << "a=" << "fmtp:" << dynPt << ' ' << "sampling=RGB; width=" << bitmap_width << "; height=" << bitmap_height << "; depth=8; colorimetry=BT.709-2" << std::endl;
  else if (outPixelFormat == PIXEL_FORMAT_YCrCb422_8)
    ss << "a=" << "fmtp:" << dynPt << ' ' << "sampling=YCbCr-4:2:2; width=" << bitmap_width << "; height=" << bitmap_height << "; depth=8; colorimetry=BT.709-2; chroma-position=1" << std::endl;
  else if (outPixelFormat == PIXEL_FORMAT_YCrCb422_10)
    ss << "a=" << "fmtp:" << dynPt << ' ' << "sampling=YCbCr-4:2:2; width=" << bitmap_width << "; height=" << bitmap_height << "; depth=10; colorimetry=BT.709-2; chroma-position=1" << std::endl;
  else if (outPixelFormat == PIXEL_FORMAT_YCrCb422_12)
    ss << "a=" << "fmtp:" << dynPt << ' ' << "sampling=YCbCr-4:2:2; width=" << bitmap_width << "; height=" << bitmap_height << "; depth=12; colorimetry=BT.709-2; chroma-position=1" << std::endl;
  else if (outPixelFormat == PIXEL_FORMAT_YCrCb422_14)
    ss << "a=" << "fmtp:" << dynPt << ' ' << "sampling=YCbCr-4:2:2; width=" << bitmap_width << "; height=" << bitmap_height << "; depth=14; colorimetry=BT.709-2; chroma-position=1" << std::endl;
  else if (outPixelFormat == PIXEL_FORMAT_YCrCb422_16)
    ss << "a=" << "fmtp:" << dynPt << ' ' << "sampling=YCbCr-4:2:2; width=" << bitmap_width << "; height=" << bitmap_height << "; depth=16; colorimetry=BT.709-2; chroma-position=1" << std::endl;
  ss << "a=" << "framerate:" << fps_generation << std::endl;
  std::string sdpString = ss.str();
  std::cout << sdpString << std::endl;
  std::cout << "=================================" << std::endl;
  std::ofstream fo("rtpgenerator.sdp");
  fo << sdpString << std::endl;
  fo.close();


  std::thread bitmap_generator_thread(bitmap_generation, fps_generation, bitmap_width, bitmap_height);
  std::thread bitmap_emission_thread(bitmap_emission, outPixelFormat, preferredIpString, server_port, mtu, ssrc, dynPt);
  //std::thread test_udp_listen_thread(test_listen_udp, server_port);

  std::cin.get();
  threadsShouldRun = false;

  bitmap_generator_thread.join();
  bitmap_emission_thread.join();
  //test_udp_listen_thread.join();

  #if defined(WIN32)
  WSACleanup();
  #endif

  return 0;
}

