#include "dec.h"
#include "page.h"
#include <stdlib.h>
#include <stdint.h>
#include <netinet/in.h>
#include "clut.h"
#include "object.h"
#include "region.h"
#define SEGMENT_BUFFER_SIZE 100*1024
#define SEGMENT_SYNC_BYTE 0x0F
#define PRIVATE_STREAM_1 0xBD

enum {
  PAGE_COMPOSITION_SEGMENT = 0x10,
  REGION_COMPOSITION_SEGMENT= 0x11,
  CLUT_DEFINITION_SEGMENT = 0x12,
  OBJECT_DATA_SEGMENT = 0x13,
  END_OF_DISPLAY_SEGMENT = 0x80
};

// Page states
enum {
  NORMAL_CASE = 0,
  ACQUISITION_POINT,
  MODE_CHANGE,
  RESERVED
};


// Segment definitions

#define PACK  __attribute__((packed))

struct pes_data_field
{
  uint8_t data_identifier;
  uint8_t stream_identifier;
  uint8_t cont_byte;
} PACK;


struct subtitling_segment
{
  uint8_t  sync_byte ;
  uint8_t  segment_type ;
  uint16_t page_id ;
  uint16_t segment_length ;
} PACK;

struct page_composition_segment_part
{
    uint8_t  id;
    uint8_t  reserved;
    uint16_t horizontal_address;
    uint16_t vertical_address;
} PACK;

struct page_composition_segment
{
  uint8_t  sync_byte;
  uint8_t  segment_type;
  uint16_t page_id;
  uint16_t segment_length;
  uint8_t  timeout;
  uint8_t  version_state; // version(4), state(2), reserved(2);
  struct page_composition_segment_part region[1];
} PACK;

struct objects_to_regions
{
  uint16_t id;
  uint8_t  comp1; //type(2), provider flag 2, horiz. pos. high(4)
  uint8_t  horizontal_position_low;
  uint8_t  vertical_position_high; // reserved(4), vertical pos. high (4)
  uint8_t  vertical_position_low;
} PACK;


// XXX doesn't support textual objects
struct region_composition_segment
{
  uint8_t  sync_byte;
  uint8_t  segment_type;
  uint16_t page_id;
  uint16_t segment_length;
  uint8_t  id;
  uint8_t  version_fill_flag; // version (4), fill_flag(1), reserved (3)
  uint16_t width;
  uint16_t height;
  uint8_t  loc_depth;
  uint8_t  CLUT_id;
  uint8_t  _8bpc;
  uint8_t  _4_2bpc; // 4 bit pixel code (4), 2bpc(2), reserved(2)
  objects_to_regions object[1];
} PACK;

struct clut_definition_segment
{
  uint8_t  sync_byte;
  uint8_t  segment_type;
  uint16_t page_id;
  uint16_t segment_length;
  uint8_t  id;
  uint8_t  version_number; //version(4), reserved(4)
  // XXX
} PACK;

struct graphical_object_data_segment {
  uint8_t  sync_byte;
  uint8_t  segment_type;
  uint16_t page_id;
  uint16_t segment_length;
  uint16_t id;
  uint8_t  comp1; // version(4),coding_method(2),non_modif_clr_flg(1),rsvd(1)
  uint16_t top_field_data_block_length;
  uint16_t bottom_field_data_block_length;
} PACK;

struct textual_object_data_segment_part
{
   uint16_t character_code;
} PACK;

struct textual_object_data_segment {
  uint8_t  sync_byte;
  uint8_t  segment_type;
  uint16_t page_id;
  uint16_t segment_length;
  uint16_t id;
  uint8_t  comp1; // version(4),coding_method(2),non_modif_clr_flg(1),rsvd(1)
  uint8_t  number_of_codes;
  struct textual_object_data_segment_part codes[1];
} PACK;

typedef subtitling_segment end_of_display_set_segment;



cDecoder::cDecoder()
{

  reset();
  // XXX handle out of memory
  segmentBuffer = (unsigned char*)malloc(SEGMENT_BUFFER_SIZE);
}


cDecoder::~cDecoder()
{
  free( segmentBuffer );
  restart(); // FIXME
}


void cDecoder::addPesData(const unsigned char* Data, int Length, bool newPacket)
{

  if ( pesFound && newPacket )
  {
    //    if (writeIndex && readIndex != (writeIndex-1) )
    //    fprintf(stderr, "New PES packet but the old one isn't processed fully, readIndex=%i,writeIndex=%i\n",readIndex,writeIndex);
    reset();
  }

  if ( newPacket && Length > 8)
  {
    // pes header
    if ( Data[0] == 0x00 && Data[1] == 0x00 && Data[2] == 0x01 )
    {
      if ( Data[3] != PRIVATE_STREAM_1 )
	return; // reset?

      iPagePts  = (int64_t) (Data[ 9] & 0x0E) << 29 ;
      iPagePts |= (int64_t)  Data[ 10]         << 22 ;
      iPagePts |= (int64_t) (Data[ 11] & 0xFE) << 14 ;
      iPagePts |= (int64_t)  Data[ 12]         <<  7 ;
      iPagePts |= (int64_t) (Data[ 13] & 0xFE) >>  1 ;
#ifdef DEBUG
      fprintf(stderr, "sub pts = %20lld\n",iPagePts);
#endif
      // extract pes
      pesFound = true;
      int hlength = Data[8];
      skip=hlength+9;
    }
    else
    {
      reset();
      return;
    }
  }
  if ( !pesFound )
    return;

  if ( ( skip > 0 ) && ( Length <= skip ) )
  {
    skip-=Length;
    return;
  }


  if ( (writeIndex +Length-skip) > SEGMENT_BUFFER_SIZE)
  {
    //  fprintf(stderr, "overflow\n");
    reset();
    return;
  }

  // Copy pes payload to buffer
  memcpy( segmentBuffer + writeIndex,  Data + skip, Length-skip );
  writeIndex += Length-skip;
  skip=0;

  // Check PES packet for data identfiefier and subtitling stream id
  if ( readIndex==0 && writeIndex>=3 )
  {
    pes_data_field* pdf = (pes_data_field*)segmentBuffer;
    if ( pdf->data_identifier==0x20 && pdf->stream_identifier==0x00 )
    {
      readIndex=2;
    }
    else
    {
      //    fprintf(stderr, "Not a subtitling packet\n");
      reset();
      return;
    }
  }

  // process available segments
  while ( readIndex < writeIndex && segmentBuffer[readIndex] == SEGMENT_SYNC_BYTE )
  {
    if ( available() >= sizeof( subtitling_segment ) )
    {
      subtitling_segment* segment = (subtitling_segment*)(segmentBuffer+readIndex);
      unsigned int required_size = ntohs(segment->segment_length) + sizeof(subtitling_segment);

      if ( available() >= required_size )
      {
	processSegment( segment );
	readIndex += required_size;

	if ( pageCompleted() )
	{
	  iPages.push_back( new cSubtitlesPage( iVisibleRegions, iPagePts, iDecodedTimeout) );
	  iPageDecoded = false;
	}
      }
      else {
	/*fprintf(stderr, " not enough for actual segment\n");*/return;} // not enough data
    }
    else
      {/*fprintf(stderr, " not enough for segment header\n");*/return;} // not enough data
  }
  //  if ( segmentBuffer[readIndex] != 0xFF)
  //    fprintf(stderr, "Unaligned subtitling data\n");

}


void cDecoder::processSegment(subtitling_segment* Segment)
{

  Segment->page_id = ntohs(Segment->page_id);
  Segment->segment_length = ntohs(Segment->segment_length);

  switch ( Segment->segment_type )
  {
  case PAGE_COMPOSITION_SEGMENT:
    {
      pageComposition( Segment );
      break;
    }
  case REGION_COMPOSITION_SEGMENT:
    {
      regionComposition( Segment );
      break;
    }
  case CLUT_DEFINITION_SEGMENT:
    {
      clutDefinition( Segment );
      break;
    }
  case OBJECT_DATA_SEGMENT:
    {
      objectData( Segment );
    }
  case END_OF_DISPLAY_SEGMENT:
    {
      //
      break;
    }
  default:
    {
      //fprintf(stderr, "Unknown segment type (%02X)\n", Segment->segment_type );
      break;
    }
  }
}

void cDecoder::reset()
{
  writeIndex=0;
  readIndex=0;
  pesFound=false;
  iPageDecoded = false;
  iNumRegions = 0;
  iRequiredObjects.clear();
  iAvailableObjects.clear();
  iDecodedTimeout = 0;
  modeChange(); // reset != modeChange but for now it's the same
}



inline unsigned int cDecoder::available()
{
  int i = writeIndex-readIndex;
  if ( i < 0 )
    return 0;
  else
    return i;
}



void cDecoder::pageComposition(subtitling_segment* Segment)
{
  page_composition_segment* page = (page_composition_segment*)Segment;
  iPageDecoded = true;
  iVisibleRegions.clear();
#ifdef DEBUG
  fprintf(stderr, "Page composition: PageId = %i\n", page->page_id);
#endif
  int page_state = ( page->version_state & 0x0C ) >> 2;

  switch ( page_state )
  {
  case NORMAL_CASE:
    {
      normalCase();
      break;
    }
  case ACQUISITION_POINT:
    {
      acqPoint();
      break;
    }
  case MODE_CHANGE:
    {
      modeChange();
      break;
    }
  default:
    {
      // Reserved
      break;
    }
  }

  iDecodedTimeout = page->timeout;
  iNumRegions = (page->segment_length - 2) / sizeof(page_composition_segment_part);
  for (int i = 0; i < iNumRegions; i++)
  {
    Region* region = RegionById( page->region[i].id );
#ifdef DEBUG
    fprintf(stderr, "  Visible region id = %i\n",page->region[i].id);
#endif
    region->SetPosition( ntohs( page->region[i].horizontal_address ),
			 ntohs( page->region[i].vertical_address ) );
    iVisibleRegions.push_back( region );
  }


}
void cDecoder::regionComposition(subtitling_segment* Segment)
{
  region_composition_segment* rcs = (region_composition_segment*)Segment;
#ifdef DEBUG
  fprintf(stderr,"Region Composition: PageId = %i, RegionId=%i\n",
    	  rcs->page_id,rcs->id);
#endif
  Region* region = RegionById( rcs->id );
  bool fill_flag = ( rcs->version_fill_flag & 0x08 ) != 0;


  // Should't call SetSize if region already exists!
  region->SetSize( ntohs( rcs->width ), ntohs( rcs->height ) );
  region->SetClut( ClutById( rcs->CLUT_id ) );
  //  0001 1100
  static int depths[] = {2,4,8};



  int bpp = 2;
  int tmp = (rcs->loc_depth&0x1C)>>2;

  if (tmp>0 && tmp < 4)
  {
      bpp = depths[tmp-1];
  }

  tmp = (rcs->loc_depth & 0xE0)>>5;
  int loc = 0;
  if (tmp>0 && tmp < 4)
  {
      loc = depths[tmp-1];
  }

  region->SetBpp(bpp, loc);

  if ( fill_flag )
  {
      // clear region. Need to decode region_[842]bit_pixel_code
  }

  // object 2 region mapping

  int pl = sizeof (region_composition_segment) - sizeof( subtitling_segment ) - sizeof( objects_to_regions );
  int num_objects = (rcs->segment_length - pl) / sizeof(objects_to_regions);
  for ( int i = 0; i < num_objects; i++ )
  {
    if ( (rcs->object[i].comp1 & 0xC0) )
    {
      //      fprintf(stderr, "Textual objects not supported\n");
      return;
    }

    Object* object = ObjectById( ntohs(rcs->object[i].id) );
    int x = ((rcs->object[i].comp1 & 0x0F ) << 8) + rcs->object[i].horizontal_position_low;
    int y = ((rcs->object[i].vertical_position_high & 0x0F ) << 8) + rcs->object[i].vertical_position_low;
    object->AddToRegion( region, x, y );
    iRequiredObjects.insert( object );
    --iNumRegions;
  }
}
void cDecoder::clutDefinition(subtitling_segment* Segment)
{

  clut_definition_segment* cd = (clut_definition_segment*)Segment;
#ifdef DEBUG
  fprintf(stderr, "Clut definition: PageId = %i, CLUT id =%i\n", cd->page_id,
  	  cd->id);
#endif

  Clut* clut = ClutById(cd->id);

  unsigned char* data = (unsigned char*)Segment +
      sizeof(clut_definition_segment);

  int size = cd->segment_length -
      (sizeof(clut_definition_segment)-2);

  clut->Decode(data,size);

}
void cDecoder::objectData(subtitling_segment* Segment)
{
  graphical_object_data_segment* ods = (graphical_object_data_segment*)Segment;

  if ( ods->comp1 & 0x0C )
  {
    //  fprintf(stderr, "Textual objects not supported\n");
    return;
  }

  // convert 16bit values
  ods->id = ntohs(ods->id);
  ods->top_field_data_block_length = ntohs(ods->top_field_data_block_length);
  ods->bottom_field_data_block_length = ntohs(ods->bottom_field_data_block_length);
#ifdef DEBUG
  fprintf(stderr, "Object data: PageId = %i, ObjectId = %i\n", ods->page_id,
  	  ods->id);
#endif

  Object* object = ObjectById( ods->id );
#ifdef DEBUG
  //fprintf( stderr, "Segment-length = %i\n", ods->segment_length );
#endif

  // ugh
  unsigned char* pixel_data =  (unsigned char*)(Segment);
  pixel_data += sizeof(graphical_object_data_segment);

  int pixel_data_size = ods->segment_length- ( sizeof(graphical_object_data_segment) - sizeof(subtitling_segment));

  object->Decode(pixel_data,
		 pixel_data_size,
		 ods->top_field_data_block_length,
		 ods->bottom_field_data_block_length
		 );
  iAvailableObjects.insert( object );
}




Region* cDecoder::RegionById(int aId)
{

  RegionMap::iterator i = iRegions.find( aId );

  if ( i != iRegions.end() )
    return i->second;

  // No region found -> create one
  Region* region = new Region( aId );
  iRegions[ aId ] =  region;
  return region;

}

Object* cDecoder::ObjectById(int aId)
{

  ObjectMap::iterator i = iObjects.find( aId );

  if ( i != iObjects.end() )
    return i->second;

  // No object found -> create one
  Object* object = new Object( aId );
  iObjects[ aId ] = object;
  return object;


}

Clut*  cDecoder::ClutById(int aId)
{

  ClutMap::iterator i = iCluts.find( aId );

  if ( i != iCluts.end() )
    return i->second;


  // No clut found -> create one
  Clut* clut = new Clut( aId );
  iCluts[ aId ] =  clut;
  return clut;

}


bool cDecoder::pageCompleted()
{
  return ( iPageDecoded && ( iNumRegions == 0 ) && ( iRequiredObjects == iAvailableObjects ) );
}


void cDecoder::modeChange()
{
  iVisibleRegions.clear();
  iRequiredObjects.clear();
  iAvailableObjects.clear();

  for (RegionMap::iterator i = iRegions.begin();
       i != iRegions.end();
       ++i)
  {
    Region* region = i->second;
    delete region;
  }
  iRegions.clear();

  for (ObjectMap::iterator i = iObjects.begin();
       i != iObjects.end();
       ++i)
  {
    Object* object = i->second;
    delete object;
  }
  iObjects.clear();

  for (ClutMap::iterator i = iCluts.begin();
       i != iCluts.end();
       ++i)
  {
    Clut* clut = i->second;
    delete clut;
  }
  iCluts.clear();
}

void cDecoder::acqPoint()
{

}
void cDecoder::normalCase()
{
}

bool cDecoder::PageAvailable()
{
  return (iPages.begin() != iPages.end());

}

cSubtitlesPage* cDecoder::GetPage()
{
  if (iPages.begin() == iPages.end())
    return 0;


  cSubtitlesPage* page = iPages.front();
  iPages.pop_front();
  return page;

}

void cDecoder::restart()
{
  while (!iPages.empty())
  {
    cSubtitlesPage* page = iPages.front();
    delete page;
    iPages.pop_front();
  }
  reset();
}

