#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <semaphore.h>
#include <iostream>
#include <fstream>
#include <sys/shm.h>

#include <libcrystalhd/bc_dts_defs.h>
#include <libcrystalhd/bc_dts_types.h>
#include <libcrystalhd/libcrystalhd_if.h>

#define TRY_CALL_1(func, p1, errmsg) \
  if (BC_STS_SUCCESS != func(p1)) \
    throw errmsg;

#define TRY_CALL_2(func, p1, p2, errmsg) \
  if (BC_STS_SUCCESS != func(p1, p2)) \
    throw errmsg;

#define TRY_CALL_5(func, p1, p2, p3, p4, p5, errmsg) \
  if (BC_STS_SUCCESS != func(p1, p2, p3, p4, p5)) \
    throw errmsg;

#define TRY_CALL_6(func, p1, p2, p3, p4, p5, p6, errmsg) \
  if (BC_STS_SUCCESS != func(p1, p2, p3, p4, p5, p6)) \
    throw errmsg;

#define OUTPUT_PROC_TIMEOUT 2000

int main()
{
  BC_STATUS ret;
  HANDLE device = 0;
  std::fstream inFile;
  try
  {
    printf("starting up\n");
    // Initialize the Link and Decoder devices
    uint32_t mode = DTS_PLAYBACK_MODE          |
                    DTS_LOAD_FILE_PLAY_FW      |
                    DTS_SKIP_TX_CHK_CPB        |
                    DTS_DFLT_RESOLUTION(vdecRESOLUTION_1080i29_97);

    ret = DtsDeviceOpen(&device, mode);
    if (ret != BC_STS_SUCCESS) {
      printf("crap, DtsDeviceOpen failed\n");
      throw "Failed to open device";
    }
    ret = DtsOpenDecoder(device, BC_STREAM_TYPE_PES);
    if (ret != BC_STS_SUCCESS) {
      printf("crap, DtsOpenDecoder failed\n");
      throw "Failed to open decoder";
    }
    ret = DtsSetVideoParams(device, BC_VID_ALGO_MPEG2, FALSE, FALSE, TRUE, 0);
    if (ret != BC_STS_SUCCESS) {
      printf("crap, DtsSetVideoParams failed\n");
      throw "Failed to set video params";
    }
    ret = DtsSetColorSpace(device, MODE422_YUY2);
    if (ret != BC_STS_SUCCESS) {
      printf("crap, DtsSetColorSpace failed\n");
      throw "Failed to set colorspace mode";
    }
    ret = DtsStartDecoder(device);
    if (ret != BC_STS_SUCCESS) {
      printf("crap, DtsStartDecoder failed\n");
      throw "Failed to start decoder";
    }
    ret = DtsStartCapture(device);
    if (ret != BC_STS_SUCCESS) {
      printf("crap, DtsStartCapture failed\n");
      throw "Failed to start capture";
    }
    printf("try calls done\n");

    // Open the input stream
    inFile.open("/tmp/test.mpeg2", std::ios::in | std::ios::binary);
    if (!inFile.is_open())
      throw "Unable to open input file";
    else
      printf("file opened successfully\n");

    // Create a 4-byte aligned input buffer
    uint8_t oddBytes = 0;
    uint32_t inputLen = 32768;
    uint8_t* input = (uint8_t*)malloc(inputLen+4);
    printf("Input Buffer: %p\n", input);
    if(((uintptr_t)input)%4)
      oddBytes = 4 - ((uint8_t)((uintptr_t)input % 4));
    uint8_t* input_aligned = input + oddBytes;
    printf("Aligned Input Buffer: %p, Offset = %d\n", input_aligned, oddBytes);

    // Create a 4-byte aligned output buffer
    uint32_t ysize = 4147200; // 1920 x 1080
    uint32_t uvsize = 0;
    uint8_t* rawBuf =  (uint8_t*)malloc(ysize + uvsize + 4);
    uint8_t* alignedBuf = rawBuf;
    if(((uintptr_t)rawBuf)%4)
    {
      oddBytes = 4 - ((uint8_t)((uintptr_t)rawBuf % 4));
      alignedBuf = rawBuf + oddBytes;
      printf("Aligned Buffer: %p, Offset = %d\n", alignedBuf, oddBytes);
    }

    // If UV is in use, it's data immediately follows Y
    uint8_t* ybuf = alignedBuf;
    printf("Y Buffer: %p\n", ybuf);
    uint8_t* uvbuf = NULL;
    if (uvsize)
    {
      uvbuf = alignedBuf + ysize;
      printf("UV Buffer: %p\n", ybuf);
    }

    bool needData = true;
    uint32_t bytesRead = 0;
    bool formatChanged = false;

    // Open the output stream
    //std::fstream outFile;
    //outFile.open("/home/davilla/dozer/dump.yuv", std::ios::binary | std::ios::out);
    uint32_t chunksSent = 0;
    uint32_t bytesSent = 0;
    uint32_t picsDecoded = 0;
    uint32_t lastDecoded = 0xFF;
    for (;;)
    {
      for (int i = 0; i < 2; i++)
      {
        // Read from input file if previously-read data was sent successfully
        if (needData)
        {
          inFile.read((char*)input, inputLen);
          if (inFile.fail())
          {
            printf("Read %d pictures\n", picsDecoded);
            throw "Unable to read input file";
          }
          else if (inFile.eof())
            throw "Reached end of input file";

          bytesRead += inputLen;
        }

        // Push input data to driver
        ret = DtsProcInput(device, input, inputLen, 0, 0);
        if (ret == BC_STS_SUCCESS)
        {
          chunksSent++;
          bytesSent += inputLen;
        }
        else
          printf("DtsProcInput returned %d\n", ret);
        usleep(1000);
        needData = (ret == BC_STS_SUCCESS); // Only need more data if the send succeeded
      }

      // Prepare output structure
      BC_DTS_PROC_OUT output;
      memset(&output, 0, sizeof(BC_DTS_PROC_OUT));
      output.PicInfo.width = 1920;
      output.PicInfo.height = 1080;
      output.Ybuff = ybuf;
      output.YbuffSz = ysize/4;
      output.UVbuff = uvbuf;
      output.UVbuffSz = uvsize/4;
      output.PoutFlags = BC_POUT_FLAGS_SIZE;

      // Request decoded data from the driver
      ret = DtsProcOutput(device, OUTPUT_PROC_TIMEOUT, &output);
      if (ret == BC_STS_SUCCESS)
      {
        if (!(output.PoutFlags & BC_POUT_FLAGS_PIB_VALID))
        {
          printf("Invalid PIB received. Skipping picture. Flags: 0x%08x\n", output.PoutFlags);
          continue;
        }
        picsDecoded++;
        if (output.PicInfo.picture_number == lastDecoded)
        {
          /*BC_DTS_STATUS stat;
          if (BC_STS_SUCCESS == DtsGetDriverStatus(device, &stat))
          {
            printf("Driver Status\n-------------------\n", stat.ReadyListCount);
            printf("ReadyListCount: %u\n", stat.ReadyListCount);
            printf("FreeListCount: %u\n", stat.FreeListCount);
            printf("FramesDropped: %u\n", stat.FramesDropped);
            printf("FramesCaptured: %u\n", stat.FramesCaptured);
            printf("FramesRepeated: %u\n", stat.FramesRepeated);
            printf("InputCount: %u (ChunksSent: %u)\n", stat.ReadyListCount, chunksSent);
            printf("InputTotalSize: %llu (BytesSent: %u)\n", stat.InputTotalSize, bytesSent);
            printf("InputBusyCount: %u\n", stat.InputBusyCount);
            printf("PIBMissCount: %u\n", stat.PIBMissCount);
          }*/
          continue;
        }
        lastDecoded = output.PicInfo.picture_number;
        printf("Received Output. Bytes In: %d, Y: %d, UV: %d, Number: %d, H: %d, W: %d, Flags: 0x%08x\n", bytesSent, output.YBuffDoneSz, output.UVBuffDoneSz, output.PicInfo.picture_number, output.PicInfo.height, output.PicInfo.width, output.PoutFlags);
/*
        std::fstream picFile;
        char picName[255];
        sprintf(picName, "/home/davilla/dozer/frames/picture_%d.yuv", picsDecoded);
        picFile.open(picName, std::ios::binary | std::ios::out);
        picFile.write((const char*)output.Ybuff, ysize);
        output.PicInfo.picture_number -= 3; // Adjust for start-up pictures
        picFile.close();
        //outFile.write((const char*)output.Ybuff, ysize);
*/
      }
      else if (ret == BC_STS_FMT_CHANGE)
      {
        printf("Format Change Detected. Flags: 0x%08x\n", output.PoutFlags);
        if ((output.PoutFlags & BC_POUT_FLAGS_PIB_VALID) && (output.PoutFlags & BC_POUT_FLAGS_FMT_CHANGE))
        {
          // Read format data from driver
          printf("New Format\n----------------------------------\n");
          printf("\tTimeStamp: %llu\n", output.PicInfo.timeStamp);
          printf("\tPicture Number: %u\n", output.PicInfo.picture_number);
          printf("\tWidth: %u\n", output.PicInfo.width);
          printf("\tHeight: %u\n", output.PicInfo.height);
          printf("\tChroma: 0x%03x\n", output.PicInfo.chroma_format);
          printf("\tPulldown: %u\n", output.PicInfo.pulldown);
          printf("\tFlags: 0x%08x\n", output.PicInfo.flags);
          printf("\tFrame Rate/Res: %u\n", output.PicInfo.frame_rate);
          printf("\tAspect Ratio: %u\n", output.PicInfo.aspect_ratio);
          printf("\tColor Primaries: %u\n", output.PicInfo.colour_primaries);
          printf("\tMetaData: %u\n", output.PicInfo.picture_meta_payload);
          printf("\tSession Number: %u\n", output.PicInfo.sess_num);
          printf("\tTimeStamp: %u\n", output.PicInfo.ycom);
          printf("\tCustom Aspect: %u\n", output.PicInfo.custom_aspect_ratio_width_height);
          printf("\tFrames to Drop: %u\n", output.PicInfo.n_drop);
          printf("\tH264 Valid Fields: 0x%08x\n", output.PicInfo.other.h264.valid);
        }
        // TODO: Handle change
      }
      else if (ret == BC_STS_TIMEOUT)
      {
        printf("Timeout in DtsProcOutput. Accum Bytes: %d\n", bytesRead);
      }
      else if (ret == BC_STS_IO_XFR_ERROR)
      {
        printf("I/O Transfer Error.\n");
      }
      else if (ret == BC_STS_IO_ERROR)
      {
        printf("I/O Error.\n");
      }
      else if (ret == BC_STS_BUSY)
      {
        printf("Busy.\n");
      }
      else
      {
        printf("DtsProcOutput return an unknown status: %d.\n", ret);
        return 0;
      }
    }
  }
  catch(const char* msg)
  {
    printf("%s\n", msg);
  }
  catch (...)
  {
    printf("An unknown exception was thrown\n");
  }

  inFile.close();
  DtsStopDecoder(device);
  DtsCloseDecoder(device);
  DtsDeviceClose(device);
  return 0;
}


