#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include <stdio.h>
#include <fcntl.h>
#include <time.h>

#include "qt.h"

#ifdef __cplusplus
extern "C" {
#endif

#define MAX_QT_TRACKS				64
#define MAX_QT_STSD_ENTRIES			64

/* [doc] quicktime_reader_stsc_entry [struct]
 *
 * one entry of the quicktime_reader sample-to-chunk array. This is not the raw
 * data from the file but is interpreted during parsing of the file structure,
 * no byte swapping on your part is necessary.
 *
 *     first_sample =           first sample number contained in the chunk [gen. by interpreter for app. use]
 *     samples_per_chunk =      number of samples in the chunk
 *     sample_description_id =  sample description ID, which determines the encoder
 *
 */
typedef struct quicktime_reader_stsc_entry {
	uint32_t				first_sample;
	uint32_t				samples_per_chunk;
	uint32_t				sample_description_id;
} quicktime_reader_stsc_entry;

/* [doc] quicktime_reader_stts_entry [struct]
 *
 * one entry of the quicktime_reader sample-to-time array. This is not the raw
 * data from the file but is interpreted during parsing of the file structure,
 * no byte swapping on your part is necessary.
 *
 *     count =                  length of the time period, in samples
 *     duration =               duration of a sample in this time period
 *     start =                  starting time of the time period (in timeline units) [gen. by interpreter for app. use]
 *
 */
#pragma pack(push,1)
typedef struct quicktime_reader_stts_entry {
	uint32_t				count,duration;
	uint64_t				start;
} quicktime_reader_stts_entry;
#pragma pack(pop)

/* [doc] quicktime_reader_track [struct]
 *
 * quicktime reader track object. the structure maintains a lot of variables
 * for your tracking and for it's use. This includes copies of the contents
 * of certain atoms. 
 *
 * Please note that while atom contents are declared as structures here,
 * the contents are *NOT* byte-swapped before being read into the structures.
 * You will need to pass the address of each member of the struct to the
 * __be_u...() macros to ensure they are read in the correct byte order.
 *
 * Atom tracking:
 *
 *     main =                   main 'trak' atom
 *     tkhd =                   'tkhd' atom
 *     tkhd_header =            tkhd_header structure [not interpreted]
 *     mdia =                   'mdia' atom
 *     mdhd =                   'mdhd' atom
 *     mdia_header =            media header structure [not interpreted]
 *     minf =                   'minf' atom
 *     stbl =                   'stbl' atom
 *     mhlr_header =            media handler structure [not interpreted] which describes the format and codec to use
 *     stsd =                   'stsd' atom
 *     stsd_content =           stsd atom contents [not interpreted]
 *     stsd_header =            stsd header [interpreted and byteswapped]
 *     stsd_content[] =         pointer to each entry in the stsd contents area [not interpreted]
 *                              stsd_header.number_of_entries lists the number of entries in the array.
 *                              having the array makes parsing sample descriptors cleaner than having
 *                              to interpret entries manually
 *     stts =                   'stts' atom
 *     stss =                   'stss' atom
 *     stsc =                   'stsc' atom
 *     stsz =                   'stsz' atom
 *     stco =                   'stco' atom
 *     co64 =                   'co64' atom (typically, mov files >= 2GB)
 *     udta =                   'udta' atom
 *     edts =                   'edts' atom
 *
 * Loaded and parsed data: chunk offset table
 *
 *     struct {
 *         unsigned long         total;
 *         uint64_t*             list;
 *     } chunk;
 *
 *     QuickTime bunches samples together throughout the file in "chunks".
 *     This table, an interpreted version of the co64 or stco table in the
 *     file, maps a chunk index to a file offset where the data can be found.
 *
 *         total = total chunks in the list
 *         list = array of uint64_t values, file offsets of each chunk
 *
 *     Warning:
 *
 *         What is not well explained by Apple documentation is that chunk
 *         indexes are 1-based, not 0-based. You are expected to look up the
 *         table as follows:
 *
 *             if (chunkidx == 0)
 *                 (no offset, no chunk)
 *             else if (chunkidx > chunk.total)
 *                 (no offset, no chunk)
 *             else
 *                 file_offset = chunk.list[chunkidx-1]
 *
 *         If such a table was not found, or not generated by the interpreter
 *         yet, then chunk.list == NULL
 *
 * Loaded and parsed data: sample size table
 *
 *    	struct {
 *          unsigned long                       constval;
 *          unsigned long                       entries;
 *          uint32_t*                           list;
 *      } samplesize;
 *
 *      QuickTime bunches data together in chunks. To help make sense of the
 *      chunks, you have to know how much data per sample. This table is an
 *      interpreted copy of the 'stsz' chunk used by QuickTime to describe this
 *
 *          constval =               nonzero if all samples are the
 *                                   same size in the entire track
 *          entries =                number of samples in the track
 *          list =                   array of uint32_t values, size of each
 *                                   sample in the file. This is NULL if
 *                                   constval is nonzero.
 *
 *      Warning:
 *
 *          Remember that the software authoring the QuickTime movie is free
 *          to choose whatever sample-to-chunk mapping it wants. Therefore,
 *          the contents of this table are not useful until you know what
 *          chunk the sample resides in, and how many samples you must read
 *          past to get to the one you want.
 *
 * Loaded and parsed data: sample to chunk table
 *
 *      struct {
 *          unsigned long                    entries;
 *          quicktime_reader_stsc_entry*     list;
 *      } sampletochunk;
 *
 *      QuickTime uses a compressed table mapping samples to chunks. The table
 *      is "compressed" in that redundant entries are not written. This table
 *      is read by the interpreter and make into the list here.
 *
 *      entries =                     number of entries in the table
 *      list =                        array of stsc entries
 *
 *      How to use:
 *
 *      Scan through the table until you find an entry that covers the sample
 *      range you want (first_sample <= x < (first_sample+samples_per_chunk)).
 *      The index you land at is the chunk number.
 *
 *      Warnings:
 *
 *          Again, remember that the table is zero-index based, while chunk
 *          indexes in QuickTime are 1-based!
 *
 * Loaded and parsed data: sample to time table
 *
 *      struct {
 *          unsigned long                       entries;
 *          quicktime_reader_stts_entry*        list;
 *      } timetosample;
 *
 *      This table is stored in the file as a list of time periods where each
 *      entry describes the length of the period in samples, and the duration
 *      of each sample in track units. This design is the reason QuickTime is
 *      capable of representing variable frame rates on one timeline (such as,
 *      one time period of 24000/1001fps video followed by 30000/1001fps video)
 *      and to properly maintain sync we have to follow this table.
 *
 *      Despite the name it is not possible to directly use sample or time to
 *      index the table, you must search.
 *
 * Loaded and parsed data: keyframes aka "sync samples"
 *
 *      struct {
 *          unsigned long                       entries;
 *          uint32_t*                           list;
 *      } syncsample;
 *
 *      Each entry of the table holds a uint32_t value. The value is the index
 *      of a sample considered to be a "keyframe". Some entries may be zero
 *      if no information provided.
 *
 *      If all samples are keyframes, then entries == 0 and list == NULL.
 *
 *      Warnings:
 *
 *          Not mentioned well in Apple's documentation is the fact that the
 *          sample indexes in this array are 1-based, NOT 0-based. Therefore,
 *          an index of "0" is not valid and is to be ignored.
 */

typedef struct {
	quicktime_atom				main;		/* the trak atom */
	quicktime_atom				tkhd;
	quicktime_tkhd_header			tkhd_header;
	quicktime_atom				mdia;
	quicktime_atom				mdhd;
	quicktime_mdhd_header			mdhd_header;
	quicktime_atom				minf;
	quicktime_atom				stbl;
	quicktime_hdlr_header			mhlr_header;	/* media handler header */
	quicktime_atom				stsd;
	quicktime_atom				stts;
	quicktime_atom				stss;
	quicktime_atom				stsc;
	quicktime_atom				stsz;
	quicktime_atom				stco;
	quicktime_atom				co64;
	quicktime_atom				udta;
	quicktime_atom				edts;
	/* loaded and parsed data */
	struct {
		unsigned long			total;
		uint64_t*			list;
	} chunk;
	struct {
		unsigned long			constval;
		unsigned long			entries;
		uint32_t*			list;
	} samplesize;
	/* NOTE TO SELF: this list is based on the DECODED sample to chunk table. The
	 *               copy in the file is "compressed" in that redundant entries
	 *               are not written (detectable by a jump in "first chunk"),
	 *               we can then assume the not-written entries are repetitions
	 *               of the last written entry.
	 *
	 *               Doing this makes it easier for the calling app using this
	 *               API to map samples to chunks */
	struct {
		unsigned long			entries;
		quicktime_reader_stsc_entry*	list;
	} sampletochunk;
	/* this is a copy of the 'stsd' chunk, and an array pointing to each entry,
	 * to make the caller's job easier */
	void*					stsd_content;
	size_t					stsd_content_length;
	quicktime_stsd_header			stsd_header;
	/* these point into stsd_content. NTS: Not byte swapped */
	quicktime_stsd_entry_general*		stsd_entry[MAX_QT_STSD_ENTRIES];
	/* sample to time tables. NTS: most quicktime files have one entry, it's more
	 * efficient to let the caller run through the tables per frame */
	struct {
		unsigned long			entries;
		quicktime_reader_stts_entry*	list;
	} timetosample;
	/* sync sample table AKA "keyframes" */
	struct {
		unsigned long			entries;	/* NTS: if == 0 then all samples are sync tables */
		uint32_t*			list;
	} syncsample;
} quicktime_reader_track;

/* [doc] quicktime_reader [struct]
 *
 * quicktime reader object. the structure maintains a lot of variables for
 * your tracking and for it's use. This includes copies of the contents of
 * certain atoms. 
 *
 * Please note that while atom contents are declared as structures here,
 * the contents are *NOT* byte-swapped before being read into the structures.
 * You will need to pass the address of each member of the struct to the
 * __be_u...() macros to ensure they are read in the correct byte order.
 *
 * debug output control:
 *
 *     emit_atoms =            if set, atoms are printed to STDERR
 *     emit_error =            if set, errors are printed to STDERR
 *     emit_debug =            if set, debug messages are sent to STDERR
 *     emit_warning =          if set, warning messages are sent to STDERR
 *
 * quicktime atom stack:
 *
 *     qt =                    quicktime stack
 *     qt_own =                whether or not the reader owns the stack
 *     r_moov =                structure holding the atom and stack values you
 *                             are supposed to use when reading the header.
 *                             This will be equal to moov and st for normal
 *                             files and cmov and the cmvd->qt stack for
 *                             compressed movie header files
 *
 * quicktime tracks:
 *
 *     alloc_track =           allocated length of the tracks array
 *     max_track =             total tracks read in from the file
 *     track =                 array of reader track objects
 *
 * atom tracking:
 *
 *     cmvd =                  cmvd copy object (if compressed movie header)
 *     mdat =                  mdat atom
 *     moov =                  moov atom
 *     cmov =                  cmov atom
 *
 * copied info
 *
 *     mvhd_header =           read copy of the mvhd atom contents [not interpreted]
 *
 */
typedef struct {
	int				alloc_track;
	int				max_track;
	struct {
		quicktime_stack*	qt;
		quicktime_atom		atom;
		quicktime_atom		mvhd;
		quicktime_atom		udta;
	} r_moov;
	quicktime_reader_track*		track;
	quicktime_cmvd_copy*		cmvd;
	quicktime_atom			mdat,moov,cmov;
	quicktime_mvhd_header		mvhd_header;
	quicktime_stack*		qt;
	unsigned char			qt_own;
	unsigned char			echo_atoms;
	unsigned char			echo_error;
	unsigned char			echo_debug;
	unsigned char			echo_warning;
} quicktime_reader;

void quicktime_reader_shut_up(quicktime_reader *qr);
void quicktime_reader_echo_default(quicktime_reader *qr);
void quicktime_reader_be_verbose(quicktime_reader *qr);
quicktime_reader *quicktime_reader_create();
int64_t quicktime_reader_track_get_chunk_offset(quicktime_reader_track *t,unsigned long chunkofs);
void quicktime_reader_track_free(quicktime_reader_track *t);
void quicktime_reader_destroy_tracks(quicktime_reader *qr);
int quicktime_reader_alloc_tracks(quicktime_reader *qr,int howmany);
quicktime_reader *quicktime_reader_destroy(quicktime_reader *qr);
void quicktime_reader_reset(quicktime_reader *qr);
int quicktime_reader_assign_stack(quicktime_reader *qr,quicktime_stack *s);
int quicktime_reader_assign_fd(quicktime_reader *qr,int fd);
int quicktime_reader_parse(quicktime_reader *qr);

#ifdef __cplusplus
}
#endif

