/*
 *      Copyright (C) 2005-2013 Team XBMC
 *      http://xbmc.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with XBMC; see the file COPYING.  If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */


#include "DVDCodecs/DVDCodecs.h"
#include "DVDInputStreams/DVDInputStream.h"
#include "DVDInputStreams/DVDInputStreamHTSP.h"
#include "DVDDemuxHTSP.h"
#include "DVDDemuxUtils.h"
#include "DVDClock.h"
#include "dialogs/GUIDialogKaiToast.h"
#include "utils/log.h"
#include "utils/StringUtils.h"
#include <arpa/inet.h>

extern "C" {
#include "lib/libhts/net.h"
#include "lib/libhts/htsmsg.h"
#include "lib/libhts/htsmsg_binary.h"
}

using namespace std;
using namespace HTSP;

class CDemuxStreamVideoHTSP
  : public CDemuxStreamVideo
{
  CDVDDemuxHTSP *m_parent;
  string         m_codec;
public:
  CDemuxStreamVideoHTSP(CDVDDemuxHTSP *parent, const string& codec)
    : m_parent(parent)
    , m_codec(codec)
  {}
  void GetStreamInfo(std::string& strInfo)
  {
    strInfo = StringUtils::Format("%s, delay: %u, drops: %ub %up %ui"
                                          , m_codec.c_str()
                                          , m_parent->m_QueueStatus.delay
                                          , m_parent->m_QueueStatus.bdrops
                                          , m_parent->m_QueueStatus.pdrops
                                          , m_parent->m_QueueStatus.idrops);
  }
};

class CDemuxStreamAudioHTSP
  : public CDemuxStreamAudio
{
  CDVDDemuxHTSP *m_parent;
  string         m_codec;
public:
  CDemuxStreamAudioHTSP(CDVDDemuxHTSP *parent, const string& codec)
    : m_parent(parent)
    , m_codec(codec)

  {}
  void GetStreamInfo(string& strInfo)
  {
    strInfo = StringUtils::Format("%s", m_codec.c_str());
  }
};

CDVDDemuxHTSP::CDVDDemuxHTSP()
  : CDVDDemux()
  , m_Input(NULL)
  , m_StatusCount(0)
{
}

CDVDDemuxHTSP::~CDVDDemuxHTSP()
{
  Dispose();
}

bool CDVDDemuxHTSP::Open(CDVDInputStream* input)
{
  Dispose();

  if(!input->IsStreamType(DVDSTREAM_TYPE_HTSP))
    return false;

  m_Input       = (CDVDInputStreamHTSP*)input;
  m_StatusCount = 0;

  while(m_Streams.empty() && m_StatusCount == 0)
  {
    DemuxPacket* pkg = Read();
    if(!pkg)
      return false;
    CDVDDemuxUtils::FreeDemuxPacket(pkg);
  }

  return true;
}

void CDVDDemuxHTSP::Dispose()
{
}

void CDVDDemuxHTSP::Reset()
{
}


void CDVDDemuxHTSP::Flush()
{
}

bool CDVDDemuxHTSP::ReadStream(uint8_t* buf, int len)
{
  while(len > 0)
  {
    int ret = m_Input->Read(buf, len);
    if(ret <= 0)
      return false;
    len -= ret;
    buf += ret;
  }
  return true;
}

htsmsg_t* CDVDDemuxHTSP::ReadStream()
{
  if(m_Input->IsStreamType(DVDSTREAM_TYPE_HTSP))
    return ((CDVDInputStreamHTSP*)m_Input)->ReadStream();

  uint32_t l;
  if(!ReadStream((uint8_t*)&l, 4))
    return NULL;

  l = ntohl(l);
  if(l == 0)
    return htsmsg_create_map();

  uint8_t* buf = (uint8_t*)malloc(l);
  if(!buf)
    return NULL;

  if(!ReadStream(buf, l))
    return NULL;

  return htsmsg_binary_deserialize(buf, l, buf); /* consumes 'buf' */
}

DemuxPacket* CDVDDemuxHTSP::Read()
{
  htsmsg_t *  msg;
  while((msg = ReadStream()))
  {
    const char* method = htsmsg_get_str(msg, "method");
    if(method == NULL)
      break;

    if     (strcmp("subscriptionStart",  method) == 0)
      SubscriptionStart(msg);
    else if(strcmp("subscriptionStop",   method) == 0)
      SubscriptionStop (msg);
    else if(strcmp("subscriptionStatus", method) == 0)
      SubscriptionStatus(msg);
    else if(strcmp("queueStatus"       , method) == 0)
      CHTSPSession::ParseQueueStatus(msg, m_QueueStatus);
    else if(strcmp("muxpkt"            , method) == 0)
    {
      uint32_t    index, duration;
      const void* bin;
      size_t      binlen;
      int64_t     ts;

      if(htsmsg_get_u32(msg, "stream" , &index)  ||
         htsmsg_get_bin(msg, "payload", &bin, &binlen))
        break;

      DemuxPacket* pkt = CDVDDemuxUtils::AllocateDemuxPacket(binlen);

      memcpy(pkt->pData, bin, binlen);
      pkt->iSize = binlen;

      if(!htsmsg_get_u32(msg, "duration", &duration))
        pkt->duration = (double)duration * DVD_TIME_BASE / 1000000;

      if(!htsmsg_get_s64(msg, "dts", &ts))
        pkt->dts = (double)ts * DVD_TIME_BASE / 1000000;
      else
        pkt->dts = DVD_NOPTS_VALUE;

      if(!htsmsg_get_s64(msg, "pts", &ts))
        pkt->pts = (double)ts * DVD_TIME_BASE / 1000000;
      else
        pkt->pts = DVD_NOPTS_VALUE;

      pkt->iStreamId = -1;
      for(int i = 0; i < (int)m_Streams.size(); i++)
      {
        if(m_Streams[i]->iPhysicalId == (int)index)
        {
          pkt->iStreamId = i;
          break;
        }
      }

      htsmsg_destroy(msg);
      return pkt;
    }

    break;
  }

  if(msg)
  {
    htsmsg_destroy(msg);
    return CDVDDemuxUtils::AllocateDemuxPacket(0);
  }
  return NULL;
}

void CDVDDemuxHTSP::SubscriptionStart (htsmsg_t *m)
{
  htsmsg_t       *streams;
  htsmsg_t       *info;
  htsmsg_field_t *f;

  if((info = htsmsg_get_map(m, "sourceinfo")))
  {
    HTSMSG_FOREACH(f, info)
    {
      if(f->hmf_type != HMF_STR)
        continue;
      CLog::Log(LOGDEBUG, "CDVDDemuxHTSP::SubscriptionStart - %s: %s", f->hmf_name, htsmsg_field_get_string(f));
    }
  }

  if((streams = htsmsg_get_list(m, "streams")) == NULL)
  {
    CLog::Log(LOGERROR, "CDVDDemuxHTSP::SubscriptionStart - malformed message");
    return;
  }

  for(int i = 0; i < (int)m_Streams.size(); i++)
    delete m_Streams[i];
  m_Streams.clear();

  HTSMSG_FOREACH(f, streams)
  {
    uint32_t    index;
    const char* type;
    const char* lang;
    htsmsg_t* sub;

    if(f->hmf_type != HMF_MAP)
      continue;
    sub = &f->hmf_msg;

    if((type = htsmsg_get_str(sub, "type")) == NULL)
      continue;

    if(htsmsg_get_u32(sub, "index", &index))
      continue;

    union {
      CDemuxStream*      g;
      CDemuxStreamAudio* a;
      CDemuxStreamVideo* v;
      CDemuxStreamSubtitle* s;
      CDemuxStreamTeletext* t;
    } st;

    CLog::Log(LOGDEBUG, "CDVDDemuxHTSP::SubscriptionStart - id: %d, type: %s", index, type);

    if(!strcmp(type, "AC3")) {
      st.a = new CDemuxStreamAudioHTSP(this, type);
      st.a->codec = AV_CODEC_ID_AC3;
    } else if(!strcmp(type, "EAC3")) {
      st.a = new CDemuxStreamAudioHTSP(this, type);
      st.a->codec = AV_CODEC_ID_EAC3;
    } else if(!strcmp(type, "MPEG2AUDIO")) {
      st.a = new CDemuxStreamAudioHTSP(this, type);
      st.a->codec = AV_CODEC_ID_MP2;
    } else if(!strcmp(type, "AAC")) {
      st.a = new CDemuxStreamAudioHTSP(this, type);
      st.a->codec = AV_CODEC_ID_AAC;
    } else if(!strcmp(type, "MPEG2VIDEO")) {
      st.v = new CDemuxStreamVideoHTSP(this, type);
      st.v->codec = AV_CODEC_ID_MPEG2VIDEO;
      st.v->iWidth  = htsmsg_get_u32_or_default(sub, "width" , 0);
      st.v->iHeight = htsmsg_get_u32_or_default(sub, "height", 0);
    } else if(!strcmp(type, "H264")) {
      st.v = new CDemuxStreamVideoHTSP(this, type);
      st.v->codec = AV_CODEC_ID_H264;
      st.v->iWidth  = htsmsg_get_u32_or_default(sub, "width" , 0);
      st.v->iHeight = htsmsg_get_u32_or_default(sub, "height", 0);
    } else if(!strcmp(type, "DVBSUB")) {
      st.s = new CDemuxStreamSubtitle();
      st.s->codec = AV_CODEC_ID_DVB_SUBTITLE;
      uint32_t composition_id = 0, ancillary_id = 0;
      htsmsg_get_u32(sub, "composition_id", &composition_id);
      htsmsg_get_u32(sub, "ancillary_id"  , &ancillary_id);
      if(composition_id || ancillary_id)
      {
        st.s->ExtraData = new uint8_t[4];
        st.s->ExtraSize = 4;
        st.s->ExtraData[0] = (composition_id >> 8) & 0xff;
        st.s->ExtraData[1] = (composition_id >> 0) & 0xff;
        st.s->ExtraData[2] = (ancillary_id   >> 8) & 0xff;
        st.s->ExtraData[3] = (ancillary_id   >> 0) & 0xff;
      }
    } else if(!strcmp(type, "TEXTSUB")) {
      st.s = new CDemuxStreamSubtitle();
      st.s->codec = AV_CODEC_ID_TEXT;
    } else if(!strcmp(type, "TELETEXT")) {
      st.t = new CDemuxStreamTeletext();
      st.t->codec = AV_CODEC_ID_DVB_TELETEXT;
    } else {
      continue;
    }

    if((lang = htsmsg_get_str(sub, "language")))
    {
      strncpy(st.g->language, lang, sizeof(st.g->language));
      st.g->language[sizeof(st.g->language) - 1] = '\0';
    }

    st.g->iId         = m_Streams.size();
    st.g->iPhysicalId = index;
    m_Streams.push_back(st.g);
  }
}
void CDVDDemuxHTSP::SubscriptionStop  (htsmsg_t *m)
{
  for(int i = 0; i < (int)m_Streams.size(); i++)
    delete m_Streams[i];
  m_Streams.clear();
}

void CDVDDemuxHTSP::SubscriptionStatus(htsmsg_t *m)
{
  const char* status;
  status = htsmsg_get_str(m, "status");
  if(status == NULL)
    m_Status = "";
  else
  {
    m_StatusCount++;
    m_Status = status;
    CLog::Log(LOGDEBUG, "CDVDDemuxHTSP::SubscriptionStatus - %s", status);
    CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, "TVHeadend Status", status, TOAST_DISPLAY_TIME, false);
  }
}

CDemuxStream* CDVDDemuxHTSP::GetStream(int iStreamId)
{
  if(iStreamId >= 0 && iStreamId < (int)m_Streams.size())
    return m_Streams[iStreamId];

  return NULL;
}

int CDVDDemuxHTSP::GetNrOfStreams()
{
  return m_Streams.size();
}

std::string CDVDDemuxHTSP::GetFileName()
{
  if(m_Input)
    return m_Input->GetFileName();
  else
    return "";
}

void CDVDDemuxHTSP::Abort()
{
  if(m_Input)
    return m_Input->Abort();
}
