#include "ka_demux.h"

#include <string.h>
#include <assert.h>

#include "config.h"
#include "ka_codecs.h"
#include "ka_log.h"
#include "ka_mem.h"

typedef struct
{
  uint32_t MicroSecPerFrame;
  uint32_t MaxBytesPerSec;
  uint32_t Reserved;
  uint32_t Flags;
  uint32_t TotalFrames;
  uint32_t InitialFrames;
  uint32_t Streams;
  uint32_t SuggestedBufferSize;
  uint32_t Width;
  uint32_t Height;
  uint32_t Scale;
  uint32_t Rate;
  uint32_t Start;
  uint32_t Length;
} AVI_Header;

typedef struct
{
  char     Type[4];
  char     Handler[4];
  uint32_t Flags;
  uint32_t Reserved1;
  uint32_t InitialFrames;
  uint32_t Scale;
  uint32_t Rate;
  uint32_t Start;
  uint32_t Length;
  uint32_t SuggestedBufferSize;
  uint32_t Quality;
  uint32_t SampleSize;
} AVI_StreamHeader;

typedef struct
{
  uint32_t size;
  uint32_t width;
  uint32_t height;
  uint16_t planes;
  uint16_t bitcount;
  char     compression[4];
  uint32_t sizeimage;
  uint32_t xpels;
  uint32_t ypels;
  uint32_t clrused;
  uint32_t clrimportant;
} AVI_BIH;

typedef struct
{
  char     format[2];
  uint16_t channels;
  uint32_t samplerate;
  uint32_t bytespersec;
  uint16_t blocksize;
  uint16_t bitspersample;
} AVI_WAVE;

typedef struct
{
  uint32_t     tag;
  uint32_t     flags;
  uint32_t     pos;
  uint32_t     len;
} index_t;

typedef union
{
  uint32_t     typ;
  ka_vparams_t vparams;
  ka_aparams_t aparams;
} params_t;

typedef union
{
  AVI_StreamHeader shdr;
  AVI_BIH          vhdr;
  AVI_WAVE         ahdr;
} work_t;

typedef enum
{ AVI_STATE_MAIN
, AVI_STATE_AVIHDR
, AVI_STATE_STREAMDEFS
, AVI_STATE_PACKETS
} AVI_STATE;

typedef struct
{
  uint32_t tag;
  uint32_t cursize;
  uint32_t totalsize;
} level_t;

typedef struct
{
  ka_demux_t hdr;
  ka_input_t* input;
  AVI_STATE  state;
  uint32_t   riffsize;
  int        datasize;
  int        skipsize;
  int        curstrh;
  int        curstrf;
  int        curtyp;
  AVI_Header avihdr;
  uint32_t   indexcount;
  index_t*   indexes;
  work_t     work;
  params_t   streams[8];
  level_t    levels[8];
  level_t*   curlevel;
  uint32_t   movioffset;
  level_t    movilevel;
} data_t;

/**
 * Creates a new demuxer.
 *
 * @returns Pointer to demuxer data or NULL if creation failed.
 */
static ka_demux_t* new_demux_avi(const kav_demux_t* vptr, ka_error_t* pErrorBlock, uint32_t size, ka_input_t* input)
{
  data_t* data = ka_mem_calloc(sizeof(*data));

  if (!data)
  {
    ka_error_fill(pErrorBlock, ka_error_nomem);
    return NULL;
  }

  data->hdr.vptr = vptr;
  data->hdr.pErrorBlock = pErrorBlock;

  data->input = input;
  data->riffsize = size;
  data->curlevel = data->levels;

  return &data->hdr;
}

/**
 * Deletes a demuxer.
 *
 * @param  ppdemux  Address of pointer to demuxer data.
 */
static void delete_demux_avi(ka_demux_t** ppdemux)
{
  data_t* data = (data_t*) *ppdemux;
  *ppdemux = NULL;

  if (!data)
    return;

  if (data->indexes) ka_mem_free(data->indexes);
  ka_mem_free(data);
}

/**
 * Resets demuxer data after a restart.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  stack   Pointer to stack.
 */
static void demux_avi_reset(ka_demux_t* pdemux, ka_stack_t* stack)
{
  data_t* data = (data_t*) pdemux;

  stack = stack; // unused

  ka_input_seek(data->input, 0);
  data->state = AVI_STATE_MAIN;
  data->curlevel = data->levels;
  data->datasize = 0;
  data->skipsize = 0;
  data->curstrh = 0;
  data->curstrf = 0;
  data->curtyp = 0;
  memset(&data->avihdr, 0, sizeof(data->avihdr));
  memset(&data->work, 0, sizeof(data->work));
  memset(&data->streams, 0, sizeof(data->streams));
  memset(&data->levels, 0, sizeof(data->levels));

  data->hdr.demuxSection = 0;
}

/**
 * Clears demuxer data after a seek.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  stack   Pointer to stack.
 */
static int demux_avi_clear(ka_demux_t* pdemux, ka_stack_t* stack)
{
  data_t* data = (data_t*) pdemux;

  stack = stack; // unused

  data->curlevel = data->levels;
  data->datasize = 0;
  data->skipsize = 0;
  data->hdr.demuxSection++;

  return 0;
}

/**
 * Logs chunk.
 */
static void log_chunk(data_t* data, const uint8_t* buf, int offset, int n, int used)
{
  if (config.debug & cfg_printdemuxstats)
  {
    uint32_t ipos = offset + (uint32_t) data->hdr.unstackedInputPos;
    ka_log(ka_log_note | ka_log_demux
         , "%.*sChunk %c%c%c%c, pos %08x size %08x%s"
         , data->curlevel - data->levels, "        "
         , buf[0], buf[1], buf[2], buf[3]
         , ipos
         , n, used ? "" : ", skipped");
  }
}

static inline uint32_t avi_uint32(const uint8_t* b)
{
  uint32_t n =  b[0]
             | (b[1] << 8)
             | (b[2] << 16)
             | (b[3] << 24);

  return n;
}

/**
 * Parses the input stream to determine positioning points.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  stack   Pointer to stack.
 *
 * @returns 0 if OK, -1 if error and error block is filled.
 */
static int demux_avi_parse(ka_demux_t* pdemux, ka_stack_t* stack)
{
  data_t* data = (data_t*) pdemux;
  uint64_t curpos = ka_input_tell(data->input);
  uint64_t pos = 12;
  int read = 0;
  uint8_t buf[16];
  uint32_t i, n, nr, tag;

  stack = stack; //Unused

  while (1)
  {
    // Move to offset
    ka_input_seek(data->input, pos);
    // Read 8 bytes
    if ((read = ka_input_read(data->input, &buf, 8)) < 8)
      break;

    nr = n = avi_uint32(buf + 4);
    if (nr & 1) nr++;

    tag = MKTAG(buf[0], buf[1], buf[2], buf[3]);

    if (tag == MKTAG('L','I','S','T'))
    {
      if ((read = ka_input_read(data->input, &buf, 4)) < 4)
        break;
      if (MKTAG('m','o','v','i') == MKTAG(buf[0], buf[1], buf[2], buf[3]))
        data->movioffset = ((uint32_t) pos) + 8;
    }
    else if (tag == MKTAG('i','d','x','1'))
    {
      data->indexcount = n / 16;
      data->indexes = ka_mem_alloc(data->indexcount * sizeof(*data->indexes));
      if (!data->indexes)
      {
        data->indexcount = 0;
        ka_error_fill(pdemux->pErrorBlock, ka_error_nomem);
        return -1;
      }

      read = n = ka_input_read(data->input, data->indexes, 16*data->indexcount);
      if (read < 0)
        return -1;
      // all may not have been downloaded
      data->indexcount = n / 16;
    }

    pos += (uint64_t) (uint32_t) (nr + 8);
  }

  for (i = 0; i < data->indexcount; i++)
  {
    data->indexes[i].pos += data->movioffset;
  }

  ka_input_seek(data->input, curpos);

  return 0;
}

/**
 * Returns current/max position.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  pbuffer Pointer to stack buffer.
 * @param  curpos  Pointer to current position to fill.
 * @param  endpos  Pointer to max position to fill.
 */

static void demux_avi_getPos(const ka_demux_t* pdemux, ka_buffer_t* pbuffer, ka_demux_pos_t* pos)
{
  const data_t* data = (data_t*) pdemux;

  pos->titleNr = 0;
  pos->chapterNr = 0;
  pos->curPos = ka_input_tell(data->input) - (uint64_t) ka_buffer_getByteCount(pbuffer);
  pos->endPos = ka_input_getLen(data->input);
}

/**
 * Set position.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  pos     Raw offset in input.
 *
 * @returns Offset in input aligned to nearest block
 *          or -1 if not possible to set/determine a position.
 */
static int64_t demux_avi_setPos(ka_demux_t* pdemux, int64_t pos)
{
  data_t* data = (data_t*) pdemux;
  int i;

  if (!data->indexcount)
    return -1;

  for (i = 0; i < data->indexcount; i++)
  {
    if ((pos <= (uint64_t) data->indexes[i].pos) && (data->indexes[i].tag > 0x30300000))
    {
      pos = (uint64_t) data->indexes[i].pos;
      ka_input_seek(data->input, pos);

      data->curlevel = data->levels;
      *data->curlevel = data->movilevel;
      data->curlevel->cursize = data->curlevel->totalsize - (data->indexes[i].pos - data->movioffset);

      return pos;
    }
  }

  return -1;
}

/**
 * Returns current/max angles.
 *
 * @param  pdemux     Pointer to demuxer data.
 * @param  pCurAngle  Pointer to current angle to fill.
 * @param  pMaxAngles Pointer to max angle to fill.
 */

static void demux_avi_getAngle(const ka_demux_t* pdemux, ka_demux_angle_info_t* pInfo)
{
  pdemux = pdemux; // Unused

  pInfo->angleId = 1;
  pInfo->nrAngles = 1;
}

/**
 * Set current angleo.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  angle   New angle.
 */
static int demux_avi_setAngle(ka_demux_t* pdemux, uint32_t angle)
{
  pdemux = pdemux; // Unused
  angle = angle;   // Unused

  return 1;
}

/**
 * Fill in buffer.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  buffer  Buffer handle.
 * @param  pdone   Pointer to EOF to fill on exit.
 *
 * Returns number of bytes read.
 */
static int demux_avi_fillBuffer(ka_demux_t* pdemux, ka_buffer_t* buffer, int* pdone)
{
  data_t* data = (data_t*) pdemux;

  return ka_input_buffer(data->input, buffer, pdone);
}

/**
 * Analyses part of the input buffer and put it on stack.
 *
 * @param  pdemux  Pointer to demuxer data.
 * @param  stack   Pointer to stack on which to push data block.
 * @param  buf     Pointer to start of data to parse.
 * @param  end     Pointer to end of data to parse.
 *
 * @returns Pointer to remaining unused data or ka_demux_error on error.
 */
static const uint8_t* demux_avi_push(ka_demux_t* pdemux, ka_stack_t* stack
                                   , const uint8_t* start, const uint8_t* end)
{
  data_t* data = (data_t*) pdemux;
  const uint8_t* buf = start;
  int stop = 0;
  int size;

  ka_stack_setProperties(stack, ps_stack_prop_allowvideo
                       | ps_stack_prop_allowaudio
                       | ps_stack_prop_notimestamp);

  while ((buf < end) && !stop)
  {
    size = end - buf;

    // data packet?
    if (data->datasize)
    {
      if (ka_stack_isFull(stack))
        break;

      if (size >= data->datasize)
      {
        // Enough data to skip
        ka_stack_idInfo(stack, data->curtyp, data->curtyp);
        ka_stack_push(stack, data->curtyp, data->hdr.demuxSection, 0, 0, 0, buf, buf + data->datasize);
        data->state = AVI_STATE_PACKETS;
        buf += data->datasize;
        size -= data->datasize;
        data->datasize = 0;
      }
      else
      {
        // Skip present data and retry later
        ka_stack_idInfo(stack, data->curtyp, data->curtyp);
        ka_stack_push(stack, data->curtyp, data->hdr.demuxSection, 0, 0, 0, buf, buf + size);
        buf += size;
        data->datasize -= size;
        break;
      }
    }

    // skip data?
    if (data->skipsize)
    {
      if (size >= data->skipsize)
      {
        // Enough data to skip
        buf += data->skipsize;
        size -= data->skipsize;
        data->skipsize = 0;
      }
      else
      {
        // Skip present data and retry later
        buf += size;
        data->skipsize -= size;
        break;
      }
    }

    if (data->state == AVI_STATE_MAIN)
    {
      uint32_t n;

      // wait till enough data in buffer
      if (size < 12)
        break;

      n = avi_uint32(buf + 4);
      if (n & 1) n += 1;
      data->curlevel->totalsize = data->curlevel->cursize = n - 4;
      data->curlevel->tag = MKTAG(buf[8], buf[9], buf[10], buf[11]);

      if (config.debug & cfg_printdemuxstats)
      {
        uint32_t ipos = (buf - start) + (uint32_t) data->hdr.unstackedInputPos;
        ka_log(ka_log_note | ka_log_demux
             , "Chunk %c%c%c%c %c%c%c%c, pos% 08x, size %08x"
             , buf[0], buf[1], buf[2], buf[3]
             , buf[8], buf[9], buf[10], buf[11]
             , ipos
             , n + 8);
      }

      buf += 12;
      data->state = AVI_STATE_AVIHDR;
      continue;
    }
    else
    {
      uint32_t n, nr, tag = 0;

      // wait till enough data in buffer
      if (size < 8)
        break;

      nr = n = avi_uint32(buf + 4);
      if (nr & 1) nr++;

      if ((nr + 8) > data->curlevel->cursize)
      {
        if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
          ka_log(ka_log_error | ka_log_demux
               , "Size for %c%c%c%c chunk exceeds remaining size of parent chunk (%x of %x)"
               , buf[0], buf[1], buf[2], buf[3]
               , data->curlevel->cursize
               , data->curlevel->totalsize
               );
        return ka_demux_error;
      }

      tag = MKTAG(buf[0], buf[1], buf[2], buf[3]);

      switch(tag)
      {
        case MKTAG('L', 'I', 'S', 'T'): // container
        {
          // wait till enough data in buffer
          if (size < 12)
          {
            stop = 1;
            continue;
          }

          if (n < 4)
          {
            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_error | ka_log_demux
                   , "Invalid size for LIST chunk");
            return ka_demux_error;
          }

          if (n > 4)
          {
            if (config.debug & cfg_printdemuxstats)
            {
              uint32_t ipos = (buf - start) + (uint32_t) data->hdr.unstackedInputPos;
              ka_log(ka_log_note | ka_log_demux
                   , "%.*sChunk LIST %c%c%c%c, pos %08x size %08x"
                   , data->curlevel - data->levels, "        "
                   , buf[8], buf[9], buf[10], buf[11]
                   , ipos
                   , nr);
            }

            data->curlevel->cursize -= 12;
            data->curlevel++;
            data->curlevel->tag = MKTAG(buf[8], buf[9], buf[10], buf[11]);
            data->curlevel->totalsize = data->curlevel->cursize = nr - 4;

			if (data->curlevel->tag == MKTAG('m', 'o', 'v', 'i'))
			{
				data->movilevel = *data->curlevel;
	            if (config.debug & cfg_printdemuxstats)
					ka_log(ka_log_note | ka_log_demux, "saved 'movi' size");
			}

            buf += 12;
            continue;
          }

          buf += 8;
          data->skipsize = nr;
        }
        break;
        case MKTAG('a', 'v', 'i', 'h'):
        {
          const int len = sizeof(data->avihdr);

          if (n < len)
          {
            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_error | ka_log_demux
                   , "Invalid size for avih chunk");
            return ka_demux_error;
          }

          // only treat the first occurence
          if (data->state == AVI_STATE_AVIHDR)
          {
            if (size < 8 + len)
            {
              stop = 1;
              continue;
            }

            log_chunk(data, buf, buf - start, n, 1);

            memcpy(&data->avihdr, buf + 8, len);
            data->state = AVI_STATE_STREAMDEFS;
            data->curstrh = 0;
            data->curstrf = 0;
          }
          else
          {
            log_chunk(data, buf, buf - start, n, 0);

            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_warning | ka_log_demux
                   , "Extra avih chunk ignored");
          }

          buf += 8;
          data->skipsize = nr;
        }
        break;
        case MKTAG('s', 't', 'r', 'h'):
        {
          const int len = sizeof(data->work.shdr);

          if (n < len)
          {
            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_error | ka_log_demux
                   , "Invalid size for strh chunk");
            return ka_demux_error;
          }

          // only treat as many occurences as there are streams
          if ((data->state == AVI_STATE_STREAMDEFS)
          &&  (data->curstrh < data->avihdr.Streams))
          {
            if (size < 8 + len)
            {
              stop = 1;
              continue;
            }

            log_chunk(data, buf, buf - start, n, 1);

            memcpy(&data->work.shdr, buf + 8, len);
            {
              char* tag = data->work.shdr.Type;

              if (!strncmp(tag, "vids", 4))
              {
                ka_vparams_t* params = &data->streams[data->curstrh].vparams;
                tag = data->work.shdr.Handler;

                params->typ = ka_tag_to_video(MKTAG(tag[0], tag[1], tag[2], tag[3]));
                if (data->work.shdr.Scale && data->work.shdr.Rate)
                  params->period = (int) ((double) (90000.*data->work.shdr.Scale)
                                         /(double) data->work.shdr.Rate);
                else
                  params->period = 9000;
              }
              else if (!strncmp(tag, "auds", 4))
              {
                ka_aparams_t* params = &data->streams[data->curstrh].aparams;

                params->typ = KA_ABLOCK_UNKNOWN;
              }
              else
              {
                data->streams[data->curstrh].typ = 0;

                if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
                  ka_log(ka_log_warning | ka_log_demux
                       , "strh chunk of type %c%c%c%c ignored"
                       , tag[0], tag[1], tag[2], tag[3]);
              }
            }
            data->curstrh++;
          }
          else
          {
            log_chunk(data, buf, buf - start, n, 0);

            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_warning | ka_log_demux
                   , "Extra strh chunk ignored");
          }

          buf += 8;
          data->skipsize = nr;
        }
        break;
        case MKTAG('s', 't', 'r', 'f'):
        {
/*          const int len = sizeof(data->shdr[0]);

          if (n < len)
          {
            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_error | ka_log_demux
                   , "Invalid size for strh chunk");
            return ka_demux_error;
          }
*/
          // only treat the first occurence
          if ((data->state == AVI_STATE_STREAMDEFS)
          &&  (data->curstrf < data->curstrh))
          {
            if (size < n)
            {
              stop = 1;
              continue;
            }

            log_chunk(data, buf, buf - start, n, 1);

            if (data->streams[data->curstrf].typ & KA_BLOCK_TYPE_AUDIO)
            {
              ka_aparams_t* params = &data->streams[data->curstrf].aparams;
              int len = sizeof(data->work.ahdr);
              char* tag = data->work.ahdr.format;

              if (n < len)
              {
                if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
                  ka_log(ka_log_error | ka_log_demux
                       , "Invalid size for strh chunk");
                return ka_demux_error;
              }

              memcpy(&data->work.ahdr, buf + 8, len);
              params->typ = ka_tag_to_audio(MKTAG(tag[0], tag[1], 0, 0));
              params->channels = data->work.ahdr.channels;
              params->samplerate = data->work.ahdr.samplerate;
              params->bitrate = 8*data->work.ahdr.bytespersec;
              params->blocksize = data->work.ahdr.blocksize;
              params->bitspersample = data->work.ahdr.bitspersample;
              if (params->typ == KA_ABLOCK_LPCM_LE)
              {
                if (params->bitspersample & 7)
                {
                  params->bitspersample &= ~7;
                  params->bitspersample += 8;
                }
                params->blocksize = 0;
                if (params->bitspersample <= 8)
                  params->typ = KA_ABLOCK_LPCM_LE_U;
              }
            }
            else if (data->streams[data->curstrf].typ & KA_BLOCK_TYPE_VIDEO)
            {
              ka_vparams_t* params = &data->streams[data->curstrf].vparams;
              int len = sizeof(data->work.vhdr);
              char* tag = data->work.vhdr.compression;

              if (n < len)
              {
                if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
                  ka_log(ka_log_error | ka_log_demux
                       , "Invalid size for strh chunk");
                return ka_demux_error;
              }

              memcpy(&data->work.vhdr, buf + 8, len);
              params->typ = ka_tag_to_video(MKTAG(tag[0], tag[1], tag[2], tag[3]));
              params->width = data->work.vhdr.width;
              params->height = data->work.vhdr.height;
//              ka_log(ka_log_error, "Size: %dx%d", params->width, params->height);
            }

            data->curstrf++;
            if (data->curstrf == data->avihdr.Streams)
            {
              data->state = AVI_STATE_PACKETS;
              data->curstrh = 0;
            }
          }
          else
          {
            log_chunk(data, buf, buf - start, n, 0);

            if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
              ka_log(ka_log_warning | ka_log_demux
                   , "Extra strf chunk ignored");
          }

          buf += 8;
          data->skipsize = nr;
        }
        break;
        default:
        {
          // Data packet
          if (((buf[0] >= '0') && (buf[0] <= '9'))
          &&  ((buf[1] >= '0') && (buf[1] <= '9'))
          &&  (   ((buf[2] == 'd') && (buf[3] == 'b'))
               || ((buf[2] == 'd') && (buf[3] == 'c'))
               || ((buf[2] == 'w') && (buf[3] == 'b')))
          &&  (n > 0))
          {
            data->curstrh = (buf[0] - '0') * 10 + (buf[1] - '0');

            if (data->curstrh < data->avihdr.Streams)
            {
              log_chunk(data, buf, buf - start, n, 1);

              data->curtyp = data->streams[data->curstrh].typ;

              // Data is to be stacked
              buf += 8;
              data->datasize = n;
              data->skipsize = nr - n;
            }
            else
            {
              log_chunk(data, buf, buf - start, n, 0);

              if (config.debug & (cfg_printwarnings | cfg_printdemuxstats))
                ka_log(ka_log_warning | ka_log_demux
                     , "Packet with incorrect id ignored");

              buf += 8;
              data->skipsize = nr;
            }
          }
          else
          {
            log_chunk(data, buf, buf - start, n, 0);

            buf += 8;
            data->skipsize = nr;
          }
        }
      }

//      assert(data->curlevel->cursize >= (8 + nr));

      data->curlevel->cursize -= 8 + nr;

      // If not enough size for at least a chunk header at this level
      // drop (possibly recursively from level)
      while ((data->curlevel->cursize < 8)
          && (data->curlevel != data->levels))
      {
        data->curlevel--;

//        assert(data->curlevel->cursize >= data->curlevel[1].totalsize);
        data->curlevel->cursize -= data->curlevel[1].totalsize;
      }
    }
  }

  return buf;
}

/**
 * Returns audio/video stream parameters.
 */
static const void* demux_avi_params(const ka_demux_t* pdemux, uint32_t typ)
{
  const data_t* data = (data_t*) pdemux;
  int i;

  for (i = 0; i < data->avihdr.Streams; i++)
  {
    if (data->streams[i].typ == typ)
      return &data->streams[i];
  }

  return NULL;
}

static const kav_demux_t kav_demux_avi =
{ .name = "AVI"
, .FNdelete = delete_demux_avi
, .FNreset  = demux_avi_reset
, .FNclear  = demux_avi_clear
, .FNparse  = demux_avi_parse
, .FNgetPos = demux_avi_getPos
, .FNsetPos = demux_avi_setPos
, .FNgetAngle = demux_avi_getAngle
, .FNsetAngle = demux_avi_setAngle
, .FNfillBuffer = demux_avi_fillBuffer
, .FNpush   = demux_avi_push
, .FNparams = demux_avi_params
};

/**
 * Checks for AVI header.
 *
 * @param  ppdemux  Pointer where to fill in with address of AVI demuxer.
 * @param  param    Pameters (error block, input source, filename, stack, work buffer).
 *
 * Returns
 *   ka_demux_select_found if AVI
 *   ka_demux_select_none  if not an AVI
 *   ka_demux_select_error if error occured
 */
ka_demux_select demux_selector_avi(ka_demux_t** ppdemux, ka_demux_params_t* params)
{
  const uint8_t *buf, *end;
  int done;

  ka_input_seek(params->pInput, 0);
  ka_buffer_clear(params->pBuffer);

  // While not enough data
  do
  {
    if (ka_input_buffer(params->pInput, params->pBuffer, &done) < 0)
      return ka_demux_select_error;

    if (done)
      return ka_demux_select_none;

    if (!ka_buffer_getUnstackedBlock(params->pBuffer, &buf, &end))
      continue;
  }
  while ((end - buf) < 12);

  // Check for header
  switch(MKTAG(buf[0], buf[1], buf[2], buf[3]))
  {
    case MKTAG('R','I','F','F'):
    {
      switch(MKTAG(buf[8], buf[9], buf[10], buf[11]))
      {
        case MKTAG('A','V','I',' '):
        case MKTAG('A','V','I','X'):
        {
          uint32_t size = avi_uint32(buf + 4);
          if (size == 8)
            size = (uint32_t) ka_input_getLen(params->pInput);
          *ppdemux = new_demux_avi(&kav_demux_avi, params->pErrorBlock, size, params->pInput);
          if (*ppdemux == NULL)
            return ka_demux_select_error;

          return ka_demux_select_found;
        }
      }
    }
    break;
  }

  return ka_demux_select_none;
}
