/* ---------------------------------------------------------------------------
** This software is in the public domain, furnished "as is", without technical
** support, and with no warranty, express or implied, as to its usefulness for
** any purpose.
**
**
** ZoneMinder Live555 source
**
** -------------------------------------------------------------------------*/

#include "zm_rtsp_server_device_source.h"

#include "zm_config.h"
#include "zm_logger.h"
#include "zm_rtsp_server_frame.h"
#include "zm_signal.h"

#if HAVE_RTSP_SERVER
ZoneMinderDeviceSource::ZoneMinderDeviceSource(
  UsageEnvironment& env,
  std::shared_ptr<Monitor> monitor,
  AVStream *stream,
  unsigned int queueSize
) :
  FramedSource(env),
  m_eventTriggerId(envir().taskScheduler().createEventTrigger(ZoneMinderDeviceSource::deliverFrameStub)),
  m_stream(stream),
  m_monitor(std::move(monitor)),
  m_packetqueue(nullptr),
  m_packetqueue_it(nullptr),
  m_queueSize(queueSize) {
  memset(&m_thid, 0, sizeof(m_thid));
  memset(&m_mutex, 0, sizeof(m_mutex));
  if ( m_monitor ) {
    m_packetqueue = m_monitor->GetPacketQueue();
    if ( !m_packetqueue ) {
      Fatal("No packetqueue");
    }
    pthread_mutex_init(&m_mutex, nullptr);
    pthread_create(&m_thid, nullptr, threadStub, this);
  } else {
    Error("No monitor in ZoneMinderDeviceSource");
  }
}

ZoneMinderDeviceSource::~ZoneMinderDeviceSource() {
  stop = 1;
  envir().taskScheduler().deleteEventTrigger(m_eventTriggerId);
  pthread_join(m_thid, nullptr);
  while ( m_captureQueue.size() ) {
    NAL_Frame * f = m_captureQueue.front();
    m_captureQueue.pop_front();
    delete f;
  }

  pthread_mutex_destroy(&m_mutex);
}

// thread mainloop
void* ZoneMinderDeviceSource::thread() {
  stop = 0;

  while ( !stop ) {
    getNextFrame();
  }
  return nullptr;
}

// getting FrameSource callback
void ZoneMinderDeviceSource::doGetNextFrame() {
  deliverFrame();
}

// stopping FrameSource callback
void ZoneMinderDeviceSource::doStopGettingFrames() {
  stop = 1;
  Debug(1, "ZoneMinderDeviceSource::doStopGettingFrames");
  FramedSource::doStopGettingFrames();
}

// deliver frame to the sink
void ZoneMinderDeviceSource::deliverFrame() {
  if ( !isCurrentlyAwaitingData() ) {
    Debug(4, "not awaiting data");
    return;
  }

  pthread_mutex_lock(&m_mutex);
  if ( m_captureQueue.empty() ) {
    Debug(4, "Queue is empty");
    pthread_mutex_unlock(&m_mutex);
    return;
  }

  NAL_Frame *frame = m_captureQueue.front();
  m_captureQueue.pop_front();
  pthread_mutex_unlock(&m_mutex);

  fDurationInMicroseconds = 0;
  fFrameSize = 0;

  unsigned int nal_size = frame->size();

  if ( nal_size > fMaxSize ) {
    fFrameSize = fMaxSize;
    fNumTruncatedBytes = nal_size - fMaxSize;
  } else {
    fFrameSize = nal_size;
  }
  Debug(2, "deliverFrame stream: %d timestamp: %jd.%06ld size: %d queuesize: %d",
        m_stream->index,
        static_cast<intmax_t>(frame->m_timestamp.tv_sec), frame->m_timestamp.tv_usec,
        fFrameSize,
        m_captureQueue.size()
       );

  fPresentationTime = frame->m_timestamp;
  memcpy(fTo, frame->buffer(), fFrameSize);

  if ( fFrameSize > 0 ) {
    // send Frame to the consumer
    FramedSource::afterGetting(this);
  }
  delete frame;
}  // end void ZoneMinderDeviceSource::deliverFrame()

// FrameSource callback on read event
void ZoneMinderDeviceSource::incomingPacketHandler() {
  if ( this->getNextFrame() <= 0 ) {
    handleClosure(this);
  }
}

// read from monitor
int ZoneMinderDeviceSource::getNextFrame() {
  if ( zm_terminate )
    return -1;

  if ( !m_packetqueue_it ) {
    m_packetqueue_it = m_packetqueue->get_video_it(true);
  }
  ZMPacket *zm_packet = m_packetqueue->get_packet(m_packetqueue_it);
  while ( zm_packet and (zm_packet->packet->stream_index != m_stream->index) ) {
    zm_packet->unlock();
    // We want our stream to start at the same it as the video
    // but if this is an audio stream we need to increment past that first packet
    Debug(4, "Have audio packet, skipping");
    m_packetqueue->increment_it(m_packetqueue_it, m_stream->index);
    zm_packet = m_packetqueue->get_packet(m_packetqueue_it);
  }
  if ( !zm_packet ) {
    Debug(1, "null zm_packet %p", zm_packet);
    return -1;
  }
  // packet is locked
  AVPacket *pkt = &zm_packet->packet;

  // Convert pts to timeval
  int64_t pts = av_rescale_q(pkt->dts, m_stream->time_base, AV_TIME_BASE_Q);
  timeval tv = { pts/1000000, pts%1000000 };
  ZM_DUMP_STREAM_PACKET(m_stream, (*pkt), "rtspServer");
  Debug(2, "pts %" PRId64 " pkt.pts %" PRId64 " tv %d.%d", pts, pkt->pts, tv.tv_sec, tv.tv_usec);

  std::list< std::pair<unsigned char*, size_t> > framesList = this->splitFrames(pkt->data, pkt->size);
  zm_packet->unlock();
  zm_packet = nullptr;// we no longer have the lock so shouldn't be accessing it
  m_packetqueue->increment_it(m_packetqueue_it, m_stream->index);

  while ( framesList.size() ) {
    std::pair<unsigned char*, size_t> nal = framesList.front();
    framesList.pop_front();

    NAL_Frame *frame  = new NAL_Frame(nal.first, nal.second, tv);

    pthread_mutex_lock(&m_mutex);
    if ( m_captureQueue.size() ) {
      NAL_Frame * f = m_captureQueue.front();
      while ( m_captureQueue.size() and ((f->m_timestamp.tv_sec - tv.tv_sec) > 10) ) {
        m_captureQueue.pop_front();
        delete f;
        f = m_captureQueue.front();
      }
    }
#if 0
    while ( m_captureQueue.size() >= m_queueSize ) {
      Debug(2, "Queue full dropping frame %d", m_captureQueue.size());
      NAL_Frame * f = m_captureQueue.front();
      m_captureQueue.pop_front();
      delete f;
    }
#endif
    m_captureQueue.push_back(frame);
    pthread_mutex_unlock(&m_mutex);

    // post an event to ask to deliver the frame
    envir().taskScheduler().triggerEvent(m_eventTriggerId, this);
  }  // end while we get frame from data
  return 1;
}

// split packet in frames
std::list< std::pair<unsigned char*,size_t> > ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) {
  std::list< std::pair<unsigned char*,size_t> > frameList;
  if ( frame != nullptr ) {
    frameList.push_back(std::pair<unsigned char*,size_t>(frame, frameSize));
  }
  return frameList;
}

// extract a frame
unsigned char*  ZoneMinderDeviceSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) {
  outsize = size;
  size = 0;
  return frame;
}
#endif // HAVE_RTSP_SERVER
