/******************************************************************************#
#           guvcview              http://guvcview.sourceforge.net              #
#                                                                              #
#           Paulo Assis <pj.assis@gmail.com>                                   #
#                                                                              #
# This is a heavily modified version of the matroska interface from x264       #
#           Copyright (C) 2005 Mike Matsnev                                    #
#                                                                              #
# 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 of the License, 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 this program; if not, write to the Free Software                  #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA    #
#                                                                              #
*******************************************************************************/

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
/* support for internationalization - i18n */
#include <libintl.h>
#include <locale.h>

#include "avi.h"
#include "encoder.h"
#include "file_io.h"
#include "gview.h"
#include "gviewencoder.h"
#include "stream_io.h"

#ifndef O_BINARY
/* win32 wants a binary flag to open(); this sets it to null
   on platforms that don't have it. */
#define O_BINARY 0
#endif

#define INFO_LIST

// #define MAX_INFO_STRLEN 64
// static char id_str[MAX_INFO_STRLEN];

#ifndef PACKAGE
#define PACKAGE "guvcview"
#endif
#ifndef VERSION
#define VERSION "1.0"
#endif

#define AVI_INDEX_CLUSTER_SIZE 16384

#define AVIF_HASINDEX 0x00000010 /* Index at end of file */
#define AVIF_MUSTUSEINDEX 0x00000020
#define AVIF_ISINTERLEAVED 0x00000100
#define AVIF_TRUSTCKTYPE 0x00000800 /* Use CKType to find key frames */
#define AVIF_WASCAPTUREFILE 0x00010000
#define AVIF_COPYRIGHTED 0x00020000

#define AVI_MAX_RIFF_SIZE 0x40000000LL /*1Gb = 0x40000000LL*/
#define AVI_MASTER_INDEX_SIZE 256
#define AVI_MAX_STREAM_COUNT 10

/* index flags */
#define AVIF_INDEX 0x10

// bIndexType codes
//
#define AVI_INDEX_OF_INDEXES                                                   \
  0x00 // when each entry in aIndex array points to
       // an index chunk

#define AVI_INDEX_OF_CHUNKS                                                    \
  0x01 // when each entry in aIndex array points to
       // a chunk in the file

#define AVI_INDEX_IS_DATA                                                      \
  0x80 // when each entry is aIndex is really the
       // data bIndexSubtype codes for
       // INDEX_OF_CHUNKS

#define AVI_INDEX_2FIELD 0x01 // when fields within frames are also indexed

extern int enc_verbosity;

int64_t avi_open_tag(avi_context_t *avi_ctx, const char *tag) {
  io_write_4cc(avi_ctx->writer, tag);
  io_write_wl32(avi_ctx->writer, 0);
  return io_get_offset(avi_ctx->writer);
}

static void avi_close_tag(avi_context_t *avi_ctx, int64_t start_pos) {
  int64_t current_offset = io_get_offset(avi_ctx->writer);
  int32_t size = (int32_t)(current_offset - start_pos);
  io_seek(avi_ctx->writer, start_pos - 4);
  io_write_wl32(avi_ctx->writer, size);
  io_seek(avi_ctx->writer, current_offset);

  if (enc_verbosity > 0)
    printf("ENCODER: (avi) %" PRIu64 " closing tag at %" PRIu64
           " with size %i\n",
           current_offset, start_pos - 4, size);
}

/*
 * Calculate audio sample size from number of bits and number of channels.
 *    This may have to be adjusted for eg. 12 bits and stereo
 */
static int avi_audio_sample_size(stream_io_t *stream) {
  if (stream->type != STREAM_TYPE_AUDIO)
    return -1;

  int s;
  if (stream->a_fmt != WAVE_FORMAT_PCM) {
    s = 4;
  } else {
    s = ((stream->a_bits + 7) / 8) * stream->a_chans;
    if (s < 4)
      s = 4; /* avoid possible zero divisions */
  }
  return s;
}

static char *avi_stream2fourcc(char *tag, stream_io_t *stream) {
  tag[0] = '0' + (stream->id) / 10;
  tag[1] = '0' + (stream->id) % 10;
  switch (stream->type) {
  case STREAM_TYPE_VIDEO:
    tag[2] = 'd';
    tag[3] = 'c';
    break;
  case STREAM_TYPE_SUB:
    // note: this is not an official code
    tag[2] = 's';
    tag[3] = 'b';
    break;
  default: // audio
    tag[2] = 'w';
    tag[3] = 'b';
    break;
  }
  tag[4] = '\0';
  return tag;
}

void avi_put_main_header(avi_context_t *avi_ctx, avi_riff_t *riff) {
  avi_ctx->fps = get_first_video_stream(avi_ctx->stream_list)->fps;
  int width = get_first_video_stream(avi_ctx->stream_list)->width;
  int height = get_first_video_stream(avi_ctx->stream_list)->height;
  int time_base_num = avi_ctx->time_base_num;
  int time_base_den = avi_ctx->time_base_den;

  uint32_t data_rate = 0;
  if (time_base_den > 0 ||
      time_base_num > 0) // these are not set yet so it's always false
    data_rate = (uint32_t)(INT64_C(1000000) * time_base_num / time_base_den);
  else
    fprintf(stderr, "ENCODER: (avi) bad time base (%i/%i): set it later",
            time_base_num, time_base_den);

  /*do not force index yet -only when closing*/
  /*this should prevent bad avi files even if it is not closed properly*/
  // if(hasIndex) flag |= AVIF_HASINDEX;
  // if(hasIndex && avi_ctx->must_use_index) flag |= AVIF_MUSTUSEINDEX;
  avi_ctx->avi_flags = AVIF_WASCAPTUREFILE;

  int64_t avih = avi_open_tag(avi_ctx, "avih"); // main avi header
  riff->time_delay_off = io_get_offset(avi_ctx->writer);
  io_write_wl32(avi_ctx->writer,
                1000000 / FRAME_RATE_SCALE); // time per frame (milisec)
  io_write_wl32(avi_ctx->writer, data_rate); // data rate
  io_write_wl32(avi_ctx->writer, 0);         // Padding multiple size (2048)
  io_write_wl32(avi_ctx->writer, avi_ctx->avi_flags); // parameter Flags
  // riff->frames_hdr_all = io_get_offset(avi_ctx->writer);
  io_write_wl32(avi_ctx->writer, 0); // number of video frames
  io_write_wl32(avi_ctx->writer, 0); // number of preview frames
  io_write_wl32(
      avi_ctx->writer,
      avi_ctx->stream_list_size); // number of data streams (audio + video)*/
  io_write_wl32(avi_ctx->writer,
                1024 * 1024); // suggested playback buffer size (bytes)
  io_write_wl32(avi_ctx->writer, width);  // width
  io_write_wl32(avi_ctx->writer, height); // height
  io_write_wl32(avi_ctx->writer,
                0); // time scale:  unit used to measure time (30)
  io_write_wl32(avi_ctx->writer, 0); // data rate (frame rate * time scale)
  io_write_wl32(avi_ctx->writer, 0); // start time (0)
  io_write_wl32(avi_ctx->writer, 0); // size of avi data chunk (in scale units)
  avi_close_tag(avi_ctx, avih);      // write the chunk size
}

int64_t avi_put_bmp_header(avi_context_t *avi_ctx, stream_io_t *stream) {
  int frate = 15 * FRAME_RATE_SCALE;
  if (stream->fps > 0.001)
    frate = (int)((FRAME_RATE_SCALE * (stream->fps)) + 0.5);

  int64_t strh = avi_open_tag(avi_ctx, "strh");      // video stream header
  io_write_4cc(avi_ctx->writer, "vids");             // stream type
  io_write_4cc(avi_ctx->writer, stream->compressor); // Handler (VIDEO CODEC)
  io_write_wl32(avi_ctx->writer, 0);                 // Flags
  io_write_wl16(avi_ctx->writer, 0);                 // stream priority
  io_write_wl16(avi_ctx->writer, 0);                 // language tag
  io_write_wl32(avi_ctx->writer, 0);                 // initial frames
  io_write_wl32(avi_ctx->writer, FRAME_RATE_SCALE);  // Scale
  stream->rate_hdr_strm =
      io_get_offset(avi_ctx->writer); // store this to set proper fps
  io_write_wl32(avi_ctx->writer,
                frate); // Rate: Rate/Scale == sample/second (fps) */
  io_write_wl32(avi_ctx->writer, 0); // start time
  stream->frames_hdr_strm = io_get_offset(avi_ctx->writer);
  io_write_wl32(avi_ctx->writer, 0);           // lenght of stream
  io_write_wl32(avi_ctx->writer, 1024 * 1024); // suggested playback buffer size
  io_write_wl32(avi_ctx->writer, -1);          // Quality
  io_write_wl32(avi_ctx->writer, 0);           // SampleSize
  io_write_wl16(avi_ctx->writer, 0);           // rFrame (left)
  io_write_wl16(avi_ctx->writer, 0);           // rFrame (top)
  io_write_wl16(avi_ctx->writer, stream->width);  // rFrame (right)
  io_write_wl16(avi_ctx->writer, stream->height); // rFrame (bottom)
  avi_close_tag(avi_ctx, strh);                   // write the chunk size

  return strh;
}

int64_t avi_put_wav_header(avi_context_t *avi_ctx, stream_io_t *stream) {
  int sampsize = avi_audio_sample_size(stream);

  int64_t strh = avi_open_tag(avi_ctx, "strh"); // audio stream header
  io_write_4cc(avi_ctx->writer, "auds");
  io_write_wl32(avi_ctx->writer, 1); // codec tag on strf
  io_write_wl32(avi_ctx->writer, 0); // Flags
  io_write_wl16(avi_ctx->writer, 0); // stream priority
  io_write_wl16(avi_ctx->writer, 0); // language tag
  io_write_wl32(avi_ctx->writer, 0); // initial frames
  stream->rate_hdr_strm = io_get_offset(avi_ctx->writer);
  io_write_wl32(avi_ctx->writer, sampsize / 4); // Scale
  io_write_wl32(avi_ctx->writer,
                stream->mpgrate /
                    8); // Rate: Rate/Scale == sample/second (fps) */
  io_write_wl32(avi_ctx->writer, 0); // start time
  stream->frames_hdr_strm = io_get_offset(avi_ctx->writer);
  io_write_wl32(avi_ctx->writer, 0);         // lenght of stream
  io_write_wl32(avi_ctx->writer, 12 * 1024); // suggested playback buffer size
  io_write_wl32(avi_ctx->writer, -1);        // Quality
  io_write_wl32(avi_ctx->writer, sampsize / 4); // SampleSize
  io_write_wl16(avi_ctx->writer, 0);            // rFrame (left)
  io_write_wl16(avi_ctx->writer, 0);            // rFrame (top)
  io_write_wl16(avi_ctx->writer, 0);            // rFrame (right)
  io_write_wl16(avi_ctx->writer, 0);            // rFrame (bottom)
  avi_close_tag(avi_ctx, strh);                 // write the chunk size

  return strh;
}

void avi_put_vstream_format_header(avi_context_t *avi_ctx,
                                   stream_io_t *stream) {
  int vxd_size = stream->extra_data_size;
  int vxd_size_align = (stream->extra_data_size + 1) & ~1;

  int64_t strf = avi_open_tag(avi_ctx, "strf");   // stream format header
  io_write_wl32(avi_ctx->writer, 40 + vxd_size);  // sruct Size
  io_write_wl32(avi_ctx->writer, stream->width);  // Width
  io_write_wl32(avi_ctx->writer, stream->height); // Height
  io_write_wl16(avi_ctx->writer, 1);              // Planes
  io_write_wl16(avi_ctx->writer, 24); // Count - bitsperpixel - 1,4,8 or 24  32
  if (strncmp(stream->compressor, "DIB", 3) == 0)
    io_write_wl32(avi_ctx->writer, 0); // Compression
  else
    io_write_4cc(avi_ctx->writer, stream->compressor);
  io_write_wl32(avi_ctx->writer,
                stream->width * stream->height * 3); // image size (in bytes?)
  io_write_wl32(avi_ctx->writer, 0);                 // XPelsPerMeter
  io_write_wl32(avi_ctx->writer, 0);                 // YPelsPerMeter
  io_write_wl32(avi_ctx->writer, 0); // ClrUsed: Number of colors used
  io_write_wl32(avi_ctx->writer, 0); // ClrImportant: Number of colors important
  // write extradata (codec private)
  if (vxd_size > 0 && stream->extra_data) {
    io_write_buf(avi_ctx->writer, stream->extra_data, vxd_size);
    if (vxd_size != vxd_size_align) {
      io_write_w8(avi_ctx->writer, 0); // align
    }
  }
  avi_close_tag(avi_ctx, strf); // write the chunk size
}

void avi_put_astream_format_header(avi_context_t *avi_ctx,
                                   stream_io_t *stream) {
  int axd_size = stream->extra_data_size;
  int axd_size_align = (stream->extra_data_size + 1) & ~1;

  int sampsize = avi_audio_sample_size(stream);

  int64_t strf = avi_open_tag(avi_ctx, "strf");        // audio stream format
  io_write_wl16(avi_ctx->writer, stream->a_fmt);       // Format (codec) tag
  io_write_wl16(avi_ctx->writer, stream->a_chans);     // Number of channels
  io_write_wl32(avi_ctx->writer, stream->a_rate);      // SamplesPerSec
  io_write_wl32(avi_ctx->writer, stream->mpgrate / 8); // Average Bytes per sec
  io_write_wl16(avi_ctx->writer, sampsize / 4);        // BlockAlign
  io_write_wl16(avi_ctx->writer, stream->a_bits);      // BitsPerSample
  io_write_wl16(avi_ctx->writer, axd_size);            // size of extra data
  // write extradata (codec private)
  if (axd_size > 0 && stream->extra_data) {
    io_write_buf(avi_ctx->writer, stream->extra_data, axd_size);
    if (axd_size != axd_size_align) {
      io_write_w8(avi_ctx->writer, 0); // align
    }
  }
  avi_close_tag(avi_ctx, strf); // write the chunk size
}

void avi_put_vproperties_header(avi_context_t *avi_ctx, stream_io_t *stream) {
  uint32_t refresh_rate = (uint32_t)lrintf(2.0 * avi_ctx->fps);
  if (avi_ctx->time_base_den > 0 ||
      avi_ctx->time_base_num > 0) // these are not set yet so it's always false
  {
    double time_base = avi_ctx->time_base_num / (double)avi_ctx->time_base_den;
    refresh_rate = lrintf(1.0 / time_base);
  }
  int vprp = avi_open_tag(avi_ctx, "vprp");
  io_write_wl32(avi_ctx->writer, 0);              // video format  = unknown
  io_write_wl32(avi_ctx->writer, 0);              // video standard= unknown
  io_write_wl32(avi_ctx->writer, refresh_rate);   // dwVerticalRefreshRate
  io_write_wl32(avi_ctx->writer, stream->width);  // horizontal pixels
  io_write_wl32(avi_ctx->writer, stream->height); // vertical lines
  io_write_wl16(avi_ctx->writer,
                stream->height); // Active Frame Aspect Ratio (4:3 - 16:9)
  io_write_wl16(avi_ctx->writer, stream->width);  // Active Frame Aspect Ratio
  io_write_wl32(avi_ctx->writer, stream->width);  // Active Frame Height in
                                                  // Pixels
  io_write_wl32(avi_ctx->writer, stream->height); // Active Frame Height in
                                                  // Lines
  io_write_wl32(avi_ctx->writer, 1);              // progressive FIXME
                                                  // Field Framing Information
  io_write_wl32(avi_ctx->writer, stream->height);
  io_write_wl32(avi_ctx->writer, stream->width);
  io_write_wl32(avi_ctx->writer, stream->height);
  io_write_wl32(avi_ctx->writer, stream->width);
  io_write_wl32(avi_ctx->writer, 0);
  io_write_wl32(avi_ctx->writer, 0);
  io_write_wl32(avi_ctx->writer, 0);
  io_write_wl32(avi_ctx->writer, 0);

  avi_close_tag(avi_ctx, vprp);
}

int64_t avi_create_riff_tags(avi_context_t *avi_ctx, avi_riff_t *riff) {
  int64_t off = 0;
  riff->riff_start = avi_open_tag(avi_ctx, "RIFF");

  if (riff->id == 1) {
    io_write_4cc(avi_ctx->writer, "AVI ");
    off = avi_open_tag(avi_ctx, "LIST");
    io_write_4cc(avi_ctx->writer, "hdrl");
  } else {
    io_write_4cc(avi_ctx->writer, "AVIX");
    off = avi_open_tag(avi_ctx, "LIST");
    io_write_4cc(avi_ctx->writer, "movi");

    riff->movi_list = off; // update movi list pos for this riff
  }

  return off;
}

// only for riff id = 1
void avi_create_riff_header(avi_context_t *avi_ctx, avi_riff_t *riff) {
  int64_t list1 = avi_create_riff_tags(avi_ctx, riff);

  avi_put_main_header(avi_ctx, riff);

  int i, j = 0;

  for (j = 0; j < avi_ctx->stream_list_size; j++) {
    stream_io_t *stream = get_stream(avi_ctx->stream_list, j);

    int64_t list2 = avi_open_tag(avi_ctx, "LIST");
    io_write_4cc(avi_ctx->writer, "strl"); // stream list

    if (stream->type == STREAM_TYPE_VIDEO) {
      avi_put_bmp_header(avi_ctx, stream);
      avi_put_vstream_format_header(avi_ctx, stream);
    } else {
      avi_put_wav_header(avi_ctx, stream);
      avi_put_astream_format_header(avi_ctx, stream);
    }
    /* Starting to lay out AVI OpenDML master index.
     * We want to make it JUNK entry for now, since we'd
     * like to get away without making AVI an OpenDML one
     * for compatibility reasons.
     */
    char tag[5];
    avi_index_t *indexes = (avi_index_t *)stream->indexes;
    indexes->entry = indexes->ents_allocated = 0;
    indexes->indx_start = io_get_offset(avi_ctx->writer);
    int64_t ix = avi_open_tag(avi_ctx, "JUNK"); // ’ix##’
    io_write_wl16(
        avi_ctx->writer,
        4); // wLongsPerEntry must be 4 (size of each entry in aIndex array)
    io_write_w8(avi_ctx->writer,
                0); // bIndexSubType must be 0 (frame index) or AVI_INDEX_2FIELD
    io_write_w8(avi_ctx->writer,
                AVI_INDEX_OF_INDEXES); // bIndexType (0 == AVI_INDEX_OF_INDEXES)
    io_write_wl32(avi_ctx->writer, 0); // nEntriesInUse (will fill out later on)
    io_write_4cc(avi_ctx->writer, avi_stream2fourcc(tag, stream)); // dwChunkId
    io_write_wl32(avi_ctx->writer, 0); // dwReserved[3] must be 0
    io_write_wl32(avi_ctx->writer, 0);
    io_write_wl32(avi_ctx->writer, 0);
    for (i = 0; i < AVI_MASTER_INDEX_SIZE; i++) {
      io_write_wl64(avi_ctx->writer,
                    0); // absolute file offset, offset 0 is unused entry
      io_write_wl32(avi_ctx->writer,
                    0); // dwSize - size of index chunk at this offset
      io_write_wl32(avi_ctx->writer,
                    0); // dwDuration - time span in stream ticks
    }
    avi_close_tag(avi_ctx, ix); // write the chunk size

    if (stream->type == STREAM_TYPE_VIDEO)
      avi_put_vproperties_header(avi_ctx, stream);

    avi_close_tag(avi_ctx, list2); // write the chunk size
  }

  avi_ctx->odml_list = avi_open_tag(avi_ctx, "JUNK");
  io_write_4cc(avi_ctx->writer, "odml");
  io_write_4cc(avi_ctx->writer, "dmlh");
  io_write_wl32(avi_ctx->writer, 248);
  for (i = 0; i < 248; i += 4)
    io_write_wl32(avi_ctx->writer, 0);
  avi_close_tag(avi_ctx, avi_ctx->odml_list);

  avi_close_tag(avi_ctx, list1); // write the chunk size

  /* some padding for easier tag editing */
  int64_t list3 = avi_open_tag(avi_ctx, "JUNK");
  for (i = 0; i < 1016; i += 4)
    io_write_wl32(avi_ctx->writer, 0);
  avi_close_tag(avi_ctx, list3); // write the chunk size

  riff->movi_list = avi_open_tag(avi_ctx, "LIST");
  io_write_4cc(avi_ctx->writer, "movi");
}

avi_riff_t *avi_get_last_riff(avi_context_t *avi_ctx) {
  avi_riff_t *last_riff = avi_ctx->riff_list;
  while (last_riff->next != NULL)
    last_riff = last_riff->next;

  return last_riff;
}

avi_riff_t *avi_get_riff(avi_context_t *avi_ctx, int index) {
  avi_riff_t *riff = avi_ctx->riff_list;

  if (!riff)
    return NULL;

  int j = 1;

  while (riff->next != NULL && (j < index)) {
    riff = riff->next;
    j++;
  }

  if (j != index)
    return NULL;

  return riff;
}

static void clean_indexes(avi_context_t *avi_ctx) {
  int i = 0, j = 0;

  for (i = 0; i < avi_ctx->stream_list_size; i++) {
    stream_io_t *stream = get_stream(avi_ctx->stream_list, i);

    avi_index_t *indexes = (avi_index_t *)stream->indexes;
    for (j = 0; j < indexes->ents_allocated / AVI_INDEX_CLUSTER_SIZE; j++)
      free(indexes->cluster[j]);
    av_freep(&indexes->cluster);
    indexes->ents_allocated = indexes->entry = 0;
  }
}

// call this after adding all the streams
avi_riff_t *avi_add_new_riff(avi_context_t *avi_ctx) {
  avi_riff_t *riff = calloc(1, sizeof(avi_riff_t));

  if (riff == NULL) {
    fprintf(stderr,
            "ENCODER: FATAL memory allocation failure (avi_add_new_riff): %s\n",
            strerror(errno));
    exit(-1);
  }

  riff->next = NULL;
  riff->id = avi_ctx->riff_list_size + 1;

  if (riff->id == 1) {
    riff->previous = NULL;
    avi_ctx->riff_list = riff;
    avi_create_riff_header(avi_ctx, riff);
  } else {
    avi_riff_t *last_riff = avi_get_last_riff(avi_ctx);
    riff->previous = last_riff;
    last_riff->next = riff;
    avi_create_riff_tags(avi_ctx, riff);
  }

  avi_ctx->riff_list_size++;

  clean_indexes(avi_ctx);

  if (enc_verbosity > 0)
    printf("ENCODER: (avi) adding new RIFF (%i)\n", riff->id);
  return riff;
}

// second function to get called (add video stream to avi_Context)
stream_io_t *avi_add_video_stream(avi_context_t *avi_ctx, int32_t width,
                                  int32_t height, int32_t fps, int32_t fps_num,
                                  int32_t codec_id) {
  stream_io_t *stream =
      add_new_stream(&avi_ctx->stream_list, &avi_ctx->stream_list_size);
  stream->type = STREAM_TYPE_VIDEO;
  stream->fps = (double)fps / fps_num;
  ;
  stream->width = width;
  stream->height = height;
  stream->codec_id = codec_id;

  stream->indexes = (void *)calloc(1, sizeof(avi_index_t));
  if (stream->indexes == NULL) {
    fprintf(
        stderr,
        "ENCODER: FATAL memory allocation failure (avi_add_video_stream): %s\n",
        strerror(errno));
    exit(-1);
  }

  int codec_ind = get_video_codec_list_index(codec_id);
  strncpy(stream->compressor, encoder_get_video_codec_4cc(codec_ind), 8);

  return stream;
}

// third function to get called (add audio stream to avi_Context)
stream_io_t *avi_add_audio_stream(avi_context_t *avi_ctx, int32_t channels,
                                  int32_t rate, int32_t bits, int32_t mpgrate,
                                  int32_t codec_id, int32_t format) {
  stream_io_t *stream =
      add_new_stream(&avi_ctx->stream_list, &avi_ctx->stream_list_size);
  stream->type = STREAM_TYPE_AUDIO;

  stream->a_chans = channels;
  stream->a_rate = rate;
  stream->a_bits = bits;
  stream->mpgrate = mpgrate;
  stream->a_vbr = 0;
  stream->codec_id = codec_id;
  stream->a_fmt = format;

  stream->indexes = (void *)calloc(1, sizeof(avi_index_t));
  if (stream->indexes == NULL) {
    fprintf(
        stderr,
        "ENCODER: FATAL memory allocation failure (avi_add_audio_stream): %s\n",
        strerror(errno));
    exit(-1);
  }

  return stream;
}

/*
   first function to get called

   avi_create_context:  Open an AVI File and write a bunch
                        of zero bytes as space for the header.
                        Creates a mutex.

   returns a pointer to avi_Context on success, a NULL pointer on error
*/
avi_context_t *avi_create_context(const char *filename) {
  avi_context_t *avi_ctx = calloc(1, sizeof(avi_context_t));

  if (avi_ctx == NULL) {
    fprintf(
        stderr,
        "ENCODER: FATAL memory allocation failure (avi_create_context): %s\n",
        strerror(errno));
    exit(-1);
  }

  avi_ctx->writer = io_create_writer(filename, 0);

  if (avi_ctx->writer == NULL) {
    fprintf(stderr, "ENCODER: (avi) Could not open file (%s) for writing: %s",
            filename, strerror(errno));
    free(avi_ctx);
    return NULL;
  }

  avi_ctx->flags = 0; /*recordind*/

  avi_ctx->riff_list = NULL;
  avi_ctx->riff_list_size = 0;

  avi_ctx->stream_list = NULL;
  avi_ctx->stream_list_size = 0;

  return avi_ctx;
}

void avi_destroy_context(avi_context_t *avi_ctx) {
  // clean up
  io_destroy_writer(avi_ctx->writer);

  avi_riff_t *riff = avi_get_last_riff(avi_ctx);
  while (riff != NULL) // from end to start
  {
    avi_riff_t *prev_riff = riff->previous;
    free(riff);
    riff = prev_riff;
    avi_ctx->riff_list_size--;
  }

  destroy_stream_list(avi_ctx->stream_list, &avi_ctx->stream_list_size);

  // free avi_Context
  free(avi_ctx);
}

avi_I_entry_t *avi_get_ientry(avi_index_t *idx, int ent_id) {
  int cl = ent_id / AVI_INDEX_CLUSTER_SIZE;
  int id = ent_id % AVI_INDEX_CLUSTER_SIZE;
  return &idx->cluster[cl][id];
}

static int avi_write_counters(avi_context_t *avi_ctx, avi_riff_t *riff) {
  int n, nb_frames = 0;
  io_flush_buffer(avi_ctx->writer);

  // int time_base_num = avi_ctx->time_base_num;
  // int time_base_den = avi_ctx->time_base_den;

  int64_t file_size = io_get_offset(avi_ctx->writer); // avi_tell(avi_ctx);
  if (enc_verbosity > 0)
    printf("ENCODER: (avi) file size = %" PRIu64 "\n", file_size);

  for (n = 0; n < avi_ctx->stream_list_size; n++) {
    stream_io_t *stream = get_stream(avi_ctx->stream_list, n);

    if (stream->rate_hdr_strm <= 0) {
      fprintf(stderr, "ENCODER: (avi) stream rate header pos not valid\n");
    } else {
      io_seek(avi_ctx->writer, stream->rate_hdr_strm);

      if (stream->type == STREAM_TYPE_VIDEO && avi_ctx->fps > 0.001) {
        uint32_t rate = (uint32_t)FRAME_RATE_SCALE * lrintf(avi_ctx->fps);
        if (enc_verbosity > 0)
          fprintf(stderr, "ENCODER: (avi) storing rate(%i)\n", rate);
        io_write_wl32(avi_ctx->writer, rate);
      }
    }

    if (stream->frames_hdr_strm <= 0) {
      fprintf(stderr, "ENCODER: (avi) stream frames header pos not valid\n");
    } else {
      io_seek(avi_ctx->writer, stream->frames_hdr_strm);

      if (stream->type == STREAM_TYPE_VIDEO) {
        io_write_wl32(avi_ctx->writer, stream->packet_count);
        nb_frames = MAX(nb_frames, stream->packet_count);
      } else {
        int sampsize = avi_audio_sample_size(stream);
        io_write_wl32(avi_ctx->writer,
                      4 * stream->audio_strm_length / sampsize);
      }
    }
  }

  avi_riff_t *riff_1 = avi_get_riff(avi_ctx, 1);
  if (riff_1->id == 1) /*should always be true*/
  {
    if (riff_1->time_delay_off <= 0) {
      fprintf(stderr, "ENCODER: (avi) riff main header pos not valid\n");
    } else {
      uint32_t us_per_frame = 1000; // us
      if (avi_ctx->fps > 0.001)
        us_per_frame = (uint32_t)lrintf(1000000.0 / avi_ctx->fps);

      avi_ctx->avi_flags |= AVIF_HASINDEX;

      io_seek(avi_ctx->writer, riff_1->time_delay_off);
      io_write_wl32(avi_ctx->writer, us_per_frame); // time_per_frame
      io_write_wl32(avi_ctx->writer, 0);            // data rate
      io_write_wl32(avi_ctx->writer, 0); // Padding multiple size (2048)
      io_write_wl32(avi_ctx->writer, avi_ctx->avi_flags); // parameter Flags
      // io_seek(avi_ctx->writer, riff_1->frames_hdr_all);
      io_write_wl32(avi_ctx->writer, nb_frames);
    }
  }

  // return to position (EOF)
  io_seek(avi_ctx->writer, file_size);

  return 0;
}

static int avi_write_ix(avi_context_t *avi_ctx) {
  char tag[5];
  char ix_tag[] = "ix00";
  int i, j;

  avi_riff_t *riff = avi_get_last_riff(avi_ctx);

  if (riff->id > AVI_MASTER_INDEX_SIZE)
    return -1;

  for (i = 0; i < avi_ctx->stream_list_size; i++) {
    stream_io_t *stream = get_stream(avi_ctx->stream_list, i);
    int64_t ix, pos;

    avi_stream2fourcc(tag, stream);

    ix_tag[3] = '0' + i; /*only 10 streams supported*/

    /* Writing AVI OpenDML leaf index chunk */
    ix = io_get_offset(avi_ctx->writer);
    io_write_4cc(avi_ctx->writer, ix_tag); /* ix?? */
    avi_index_t *indexes = (avi_index_t *)stream->indexes;
    io_write_wl32(avi_ctx->writer, indexes->entry * 8 + 24);
    /* chunk size */
    io_write_wl16(avi_ctx->writer, 2); /* wLongsPerEntry */
    io_write_w8(avi_ctx->writer, 0);   /* bIndexSubType (0 == frame index) */
    io_write_w8(
        avi_ctx->writer,
        AVI_INDEX_OF_CHUNKS); /* bIndexType (1 == AVI_INDEX_OF_CHUNKS) */
    io_write_wl32(avi_ctx->writer, indexes->entry);
    /* nEntriesInUse */
    io_write_4cc(avi_ctx->writer, tag);              /* dwChunkId */
    io_write_wl64(avi_ctx->writer, riff->movi_list); /* qwBaseOffset */
    io_write_wl32(avi_ctx->writer, 0); /* dwReserved_3 (must be 0) */

    for (j = 0; j < indexes->entry; j++) {
      avi_I_entry_t *ie = avi_get_ientry(indexes, j);
      io_write_wl32(avi_ctx->writer, ie->pos + 8);
      io_write_wl32(avi_ctx->writer, ((uint32_t)ie->len & ~0x80000000) |
                                         (ie->flags & 0x10 ? 0 : 0x80000000));
    }
    io_flush_buffer(avi_ctx->writer);
    pos = io_get_offset(avi_ctx->writer); // current position
    if (enc_verbosity > 0)
      printf("ENCODER: (avi) wrote ix %s with %i entries\n", tag,
             indexes->entry);

    /* Updating one entry in the AVI OpenDML master index */
    io_seek(avi_ctx->writer, indexes->indx_start);
    io_write_4cc(avi_ctx->writer, "indx"); /* enabling this entry */
    io_skip(avi_ctx->writer, 8);
    io_write_wl32(avi_ctx->writer, riff->id); /* nEntriesInUse */
    io_skip(avi_ctx->writer, 16 * (riff->id));
    io_write_wl64(avi_ctx->writer, ix);             /* qwOffset */
    io_write_wl32(avi_ctx->writer, pos - ix);       /* dwSize */
    io_write_wl32(avi_ctx->writer, indexes->entry); /* dwDuration */

    // return to position
    io_seek(avi_ctx->writer, pos);
  }
  return 0;
}

static int avi_write_idx1(avi_context_t *avi_ctx, avi_riff_t *riff) {

  int64_t idx_chunk;
  int i;
  char tag[5];

  stream_io_t *stream;
  avi_I_entry_t *ie = 0, *tie;
  int empty, stream_id = -1;

  idx_chunk = avi_open_tag(avi_ctx, "idx1");
  for (i = 0; i < avi_ctx->stream_list_size; i++) {
    stream = get_stream(avi_ctx->stream_list, i);
    stream->entry = 0;
  }

  do {
    empty = 1;
    for (i = 0; i < avi_ctx->stream_list_size; i++) {
      stream = get_stream(avi_ctx->stream_list, i);
      avi_index_t *indexes = (avi_index_t *)stream->indexes;
      if (indexes->entry <= stream->entry)
        continue;

      tie = avi_get_ientry(indexes, stream->entry);
      if (empty || tie->pos < ie->pos) {
        ie = tie;
        stream_id = i;
      }
      empty = 0;
    }

    if (!empty) {
      stream = get_stream(avi_ctx->stream_list, stream_id);
      avi_stream2fourcc(tag, stream);
      io_write_4cc(avi_ctx->writer, tag);
      io_write_wl32(avi_ctx->writer, ie->flags);
      io_write_wl32(avi_ctx->writer, ie->pos);
      io_write_wl32(avi_ctx->writer, ie->len);
      stream->entry++;
    }
  } while (!empty);

  avi_close_tag(avi_ctx, idx_chunk);
  if (enc_verbosity > 0)
    printf("ENCODER: (avi) wrote idx1\n");
  avi_write_counters(avi_ctx, riff);

  return 0;
}

int avi_write_packet(avi_context_t *avi_ctx, int stream_index, uint8_t *data,
                     uint32_t size, int64_t dts, int block_align,
                     int32_t flags) {
  char tag[5];
  unsigned int i_flags = 0;

  stream_io_t *stream = get_stream(avi_ctx->stream_list, stream_index);

  avi_riff_t *riff = avi_get_last_riff(avi_ctx);
  // align
  // while(block_align==0 && dts != AV_NOPTS_VALUE && dts >
  // stream->packet_count)
  //     avi_write_packet(avi_ctx, stream_index, NULL, 0, AV_NOPTS_VALUE, 0, 0);

  stream->packet_count++;

  // Make sure to put an OpenDML chunk when the file size exceeds the limits
  if (io_get_offset(avi_ctx->writer) - riff->riff_start > AVI_MAX_RIFF_SIZE) {
    avi_write_ix(avi_ctx);
    avi_close_tag(avi_ctx, riff->movi_list);

    if (riff->id == 1)
      avi_write_idx1(avi_ctx, riff);

    avi_close_tag(avi_ctx, riff->riff_start);

    avi_add_new_riff(avi_ctx);

    riff = avi_get_last_riff(avi_ctx); // update riff
  }

  avi_stream2fourcc(tag, stream);

  if (flags & AV_PKT_FLAG_KEY) // key frame
    i_flags = 0x10;

  if (stream->type == STREAM_TYPE_AUDIO)
    stream->audio_strm_length += size;

  avi_index_t *idx = (avi_index_t *)stream->indexes;
  int cl = idx->entry / AVI_INDEX_CLUSTER_SIZE;
  int id = idx->entry % AVI_INDEX_CLUSTER_SIZE;
  if (idx->ents_allocated <= idx->entry) {
    idx->cluster = realloc(idx->cluster, (cl + 1) * sizeof(void *));
    if (idx->cluster == NULL) {
      fprintf(
          stderr,
          "ENCODER: FATAL memory allocation failure (avi_write_packet): %s\n",
          strerror(errno));
      exit(-1);
    }

    idx->cluster[cl] = calloc(AVI_INDEX_CLUSTER_SIZE, sizeof(avi_I_entry_t));
    if (idx->cluster[cl] == NULL) {
      fprintf(
          stderr,
          "ENCODER: FATAL memory allocation failure (avi_write_packet): %s\n",
          strerror(errno));
      exit(-1);
    }
    idx->ents_allocated += AVI_INDEX_CLUSTER_SIZE;
  }

  idx->cluster[cl][id].flags = i_flags;
  idx->cluster[cl][id].pos = io_get_offset(avi_ctx->writer) - riff->movi_list;
  idx->cluster[cl][id].len = size;
  idx->entry++;

  io_write_4cc(avi_ctx->writer, tag);
  io_write_wl32(avi_ctx->writer, size);
  io_write_buf(avi_ctx->writer, data, size);
  if (size & 1)
    io_write_w8(avi_ctx->writer, 0);

  io_flush_buffer(avi_ctx->writer);

  return 0;
}

int avi_close(avi_context_t *avi_ctx) {
  int res = 0;
  int64_t file_size;

  avi_riff_t *riff = avi_get_last_riff(avi_ctx);

  if (riff->id == 1) {
    avi_close_tag(avi_ctx, riff->movi_list);
    if (enc_verbosity > 0)
      printf("ENCODER: (avi) %" PRIu64 " close movi tag\n",
             io_get_offset(avi_ctx->writer));
    res = avi_write_idx1(avi_ctx, riff);
    avi_close_tag(avi_ctx, riff->riff_start);
  } else {
    avi_write_ix(avi_ctx);
    avi_close_tag(avi_ctx, riff->movi_list);
    avi_close_tag(avi_ctx, riff->riff_start);

    file_size = io_get_offset(avi_ctx->writer);
    io_seek(avi_ctx->writer, avi_ctx->odml_list - 8);
    io_write_4cc(avi_ctx->writer, "LIST"); /* Making this AVI OpenDML one */
    io_skip(avi_ctx->writer, 16);

    int n = 0;
    int nb_frames = 0;

    for (n = nb_frames = 0; n < avi_ctx->stream_list_size; n++) {
      stream_io_t *stream = get_stream(avi_ctx->stream_list, n);

      if (stream->type == STREAM_TYPE_VIDEO) {
        if (nb_frames < stream->packet_count)
          nb_frames = stream->packet_count;
      } else {
        if (stream->codec_id == AV_CODEC_ID_MP2 ||
            stream->codec_id == AV_CODEC_ID_MP3)
          nb_frames += stream->packet_count;
      }
    }
    io_write_wl32(avi_ctx->writer, nb_frames);
    io_seek(avi_ctx->writer, file_size);

    avi_write_counters(avi_ctx, riff);
  }

  clean_indexes(avi_ctx);

  return res;
}