#ifndef COSMOPOLITAN_DSP_MPEG_MPEG_H_
#define COSMOPOLITAN_DSP_MPEG_MPEG_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_

struct FILE;

typedef struct plm_t plm_t;
typedef struct plm_buffer_t plm_buffer_t;
typedef struct plm_demux_t plm_demux_t;
typedef struct plm_video_t plm_video_t;
typedef struct plm_audio_t plm_audio_t;

/**
 * Demuxed MPEG PS packet
 *
 * The type maps directly to the various MPEG-PES start codes. pts is
 * the presentation time stamp of the packet in seconds. Not all packets
 * have a pts value.
 */
typedef struct plm_packet_t {
  int type;
  double pts;
  size_t length;
  uint8_t *data;
} plm_packet_t;

/**
 * Decoded Video Plane
 *
 * The byte length of the data is width * height. Note that different
 * planes have different sizes: the Luma plane (Y) is double the size of
 * each of the two Chroma planes (Cr, Cb) - i.e. 4 times the byte
 * length. Also note that the size of the plane does *not* denote the
 * size of the displayed frame. The sizes of planes are always rounded
 * up to the nearest macroblock (16px).
 */
typedef struct plm_plane_t {
  unsigned int width;
  unsigned int height;
  uint8_t *data;
} plm_plane_t;

/**
 * Decoded Video Frame
 *
 * Width and height denote the desired display size of the frame. This
 * may be different from the internal size of the 3 planes.
 */
typedef struct plm_frame_t {
  double time;
  unsigned int width;
  unsigned int height;
  plm_plane_t y;
  plm_plane_t cr;
  plm_plane_t cb;
} plm_frame_t;

/**
 * Callback function type for decoded video frames used by the high-level
 * plm_* interface
 */
typedef void (*plm_video_decode_callback)(plm_t *self, plm_frame_t *frame,
                                          void *user);

/**
 * Decoded Audio Samples
 *
 * Samples are stored as normalized (-1, 1) float either interleaved, or if
 * PLM_AUDIO_SEPARATE_CHANNELS is defined, in two separate arrays.
 * The `count` is always PLM_AUDIO_SAMPLES_PER_FRAME and just there for
 * convenience.
 */
#define PLM_AUDIO_SAMPLES_PER_FRAME 1152

struct plm_samples_t {
  double time;
  unsigned int count;
#ifdef PLM_AUDIO_SEPARATE_CHANNELS
  float left[PLM_AUDIO_SAMPLES_PER_FRAME] forcealign(32);
  float right[PLM_AUDIO_SAMPLES_PER_FRAME] forcealign(32);
#else
  float interleaved[PLM_AUDIO_SAMPLES_PER_FRAME * 2] forcealign(32);
#endif
} forcealign(32);

typedef struct plm_samples_t plm_samples_t;

/**
 * Callback function type for decoded audio samples used by the high-level
 * plm_* interface
 */
typedef void (*plm_audio_decode_callback)(plm_t *self, plm_samples_t *samples,
                                          void *user);

/**
 * Callback function for plm_buffer when it needs more data
 */
typedef void (*plm_buffer_load_callback)(plm_buffer_t *self, void *user);

/**
 * -----------------------------------------------------------------------------
 * plm_* public API
 * High-Level API for loading/demuxing/decoding MPEG-PS data
 *
 * Create a plmpeg instance with a filename. Returns NULL if the file could not
 * be opened.
 */
plm_t *plm_create_with_filename(const char *filename);

/**
 * Create a plmpeg instance with file handle. Pass true to close_when_done
 * to let plmpeg call fclose() on the handle when plm_destroy() is
 * called.
 */
plm_t *plm_create_with_file(struct FILE *fh, int close_when_done);

/**
 * Create a plmpeg instance with pointer to memory as source. This assumes the
 * whole file is in memory. Pass true to free_when_done to let plmpeg call
 * free() on the pointer when plm_destroy() is called.
 */
plm_t *plm_create_with_memory(uint8_t *bytes, size_t length,
                              int free_when_done);

/**
 * Create a plmpeg instance with a plm_buffer as source. This is also
 * called internally by all the above constructor functions.
 */
plm_t *plm_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done);

/**
 * Destroy a plmpeg instance and free all data
 */
void plm_destroy(plm_t *self);

/**
 * Get or set whether video decoding is enabled.
 */
int plm_get_video_enabled(plm_t *self);
void plm_set_video_enabled(plm_t *self, int enabled);

/**
 * Get or set whether audio decoding is enabled. When enabling, you can set the
 * desired audio stream (0-3) to decode.
 */
int plm_get_audio_enabled(plm_t *self);
void plm_set_audio_enabled(plm_t *self, int enabled, int stream_index);

/**
 * Get the display width/height of the video stream
 */
int plm_get_width(plm_t *self);
int plm_get_height(plm_t *self);

double plm_get_pixel_aspect_ratio(plm_t *);

/**
 * Get the framerate of the video stream in frames per second
 */
double plm_get_framerate(plm_t *self);

/**
 * Get the number of available audio streams in the file
 */
int plm_get_num_audio_streams(plm_t *self);

/**
 * Get the samplerate of the audio stream in samples per second
 */
int plm_get_samplerate(plm_t *self);

/**
 * Get or set the audio lead time in seconds - the time in which audio samples
 * are decoded in advance (or behind) the video decode time. Default 0.
 */
double plm_get_audio_lead_time(plm_t *self);
void plm_set_audio_lead_time(plm_t *self, double lead_time);

/**
 * Get the current internal time in seconds
 */
double plm_get_time(plm_t *self);

/**
 * Rewind all buffers back to the beginning.
 */
void plm_rewind(plm_t *self);

/**
 * Get or set looping. Default false.
 */
int plm_get_loop(plm_t *self);
void plm_set_loop(plm_t *self, int loop);

/**
 * Get whether the file has ended. If looping is enabled, this will always
 * return false.
 */
int plm_has_ended(plm_t *self);

/**
 * Set the callback for decoded video frames used with plm_decode(). If no
 * callback is set, video data will be ignored and not be decoded.
 */
void plm_set_video_decode_callback(plm_t *self, plm_video_decode_callback fp,
                                   void *user);

/**
 * Set the callback for decoded audio samples used with plm_decode(). If no
 * callback is set, audio data will be ignored and not be decoded.
 */
void plm_set_audio_decode_callback(plm_t *self, plm_audio_decode_callback fp,
                                   void *user);

/**
 * Advance the internal timer by seconds and decode video/audio up to
 * this time. Returns true/false whether anything was decoded.
 */
int plm_decode(plm_t *self, double seconds);

/**
 * Decode and return one video frame. Returns NULL if no frame could be decoded
 * (either because the source ended or data is corrupt). If you only want to
 * decode video, you should disable audio via plm_set_audio_enabled().
 * The returned plm_frame_t is valid until the next call to
 * plm_decode_video call or until the plm_destroy is called.
 */
plm_frame_t *plm_decode_video(plm_t *self);

/**
 * Decode and return one audio frame. Returns NULL if no frame could be decoded
 * (either because the source ended or data is corrupt). If you only want to
 * decode audio, you should disable video via plm_set_video_enabled().
 * The returned plm_samples_t is valid until the next call to
 * plm_decode_video or until the plm_destroy is called.
 */
plm_samples_t *plm_decode_audio(plm_t *self);

/* -----------------------------------------------------------------------------
 * plm_buffer public API
 * Provides the data source for all other plm_* interfaces
 *
 * The default size for buffers created from files or by the high-level API
 */
#ifndef PLM_BUFFER_DEFAULT_SIZE
#define PLM_BUFFER_DEFAULT_SIZE (128 * 1024)
#endif

/**
 * Create a buffer instance with a filename. Returns NULL if the file could not
 * be opened.
 */
plm_buffer_t *plm_buffer_create_with_filename(const char *filename);

/**
 * Create a buffer instance with file handle. Pass true to close_when_done
 * to let plmpeg call fclose() on the handle when plm_destroy() is
 * called.
 */
plm_buffer_t *plm_buffer_create_with_file(struct FILE *fh, int close_when_done);

/**
 * Create a buffer instance with a pointer to memory as source. This assumes
 * the whole file is in memory. Pass 1 to free_when_done to let plmpeg call
 * free() on the pointer when plm_destroy() is called.
 */
plm_buffer_t *plm_buffer_create_with_memory(uint8_t *bytes, size_t length,
                                            int free_when_done);

/**
 * Create an empty buffer with an initial capacity. The buffer will grow
 * as needed.
 */
plm_buffer_t *plm_buffer_create_with_capacity(size_t capacity);

/**
 * Destroy a buffer instance and free all data
 */
void plm_buffer_destroy(plm_buffer_t *self);

/**
 * Copy data into the buffer. If the data to be written is larger than the
 * available space, the buffer will realloc() with a larger capacity.
 * Returns the number of bytes written. This will always be the same as the
 * passed in length, except when the buffer was created _with_memory() for
 * which _write() is forbidden.
 */
size_t plm_buffer_write(plm_buffer_t *self, uint8_t *bytes, size_t length);

/**
 * Set a callback that is called whenever the buffer needs more data
 */
void plm_buffer_set_load_callback(plm_buffer_t *self,
                                  plm_buffer_load_callback fp, void *user);

/**
 * Rewind the buffer back to the beginning. When loading from a file handle,
 * this also seeks to the beginning of the file.
 */
void plm_buffer_rewind(plm_buffer_t *self);

/**
 * -----------------------------------------------------------------------------
 * plm_demux public API
 * Demux an MPEG Program Stream (PS) data into separate packages
 *
 * Various Packet Types
 */
#define PLM_DEMUX_PACKET_PRIVATE 0xBD
#define PLM_DEMUX_PACKET_AUDIO_1 0xC0
#define PLM_DEMUX_PACKET_AUDIO_2 0xC1
#define PLM_DEMUX_PACKET_AUDIO_3 0xC2
#define PLM_DEMUX_PACKET_AUDIO_4 0xC2
#define PLM_DEMUX_PACKET_VIDEO_1 0xE0

/**
 * Create a demuxer with a plm_buffer as source
 */
plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done);

/**
 * Destroy a demuxer and free all data
 */
void plm_demux_destroy(plm_demux_t *self);

/**
 * Returns the number of video streams found in the system header.
 */
int plm_demux_get_num_video_streams(plm_demux_t *self);

/**
 * Returns the number of audio streams found in the system header.
 */
int plm_demux_get_num_audio_streams(plm_demux_t *self);

/**
 * Rewinds the internal buffer. See plm_buffer_rewind().
 */
void plm_demux_rewind(plm_demux_t *self);

/**
 * Decode and return the next packet. The returned packet_t is valid until
 * the next call to plm_demux_decode() or until the demuxer is destroyed.
 */
plm_packet_t *plm_demux_decode(plm_demux_t *self);

/* -----------------------------------------------------------------------------
 * plm_video public API
 * Decode MPEG1 Video ("mpeg1") data into raw YCrCb frames
 */

/**
 * Create a video decoder with a plm_buffer as source
 */
plm_video_t *plm_video_create_with_buffer(plm_buffer_t *buffer,
                                          int destroy_when_done);

/**
 * Destroy a video decoder and free all data
 */
void plm_video_destroy(plm_video_t *self);

/**
 * Get the framerate in frames per second
 */
double plm_video_get_framerate(plm_video_t *);

double plm_video_get_pixel_aspect_ratio(plm_video_t *);

/**
 * Get the display width/height
 */
int plm_video_get_width(plm_video_t *);
int plm_video_get_height(plm_video_t *);

/**
 * Set "no delay" mode. When enabled, the decoder assumes that the video does
 * *not* contain any B-Frames. This is useful for reducing lag when streaming.
 */
void plm_video_set_no_delay(plm_video_t *self, int no_delay);

/**
 * Get the current internal time in seconds
 */
double plm_video_get_time(plm_video_t *self);

/**
 * Rewinds the internal buffer. See plm_buffer_rewind().
 */
void plm_video_rewind(plm_video_t *self);

/**
 * Decode and return one frame of video and advance the internal time by
 * 1/framerate seconds. The returned frame_t is valid until the next call of
 * plm_video_decode() or until the video decoder is destroyed.
 */
plm_frame_t *plm_video_decode(plm_video_t *self);

/**
 * Convert the YCrCb data of a frame into an interleaved RGB buffer. The buffer
 * pointed to by *rgb must have a size of (frame->width * frame->height * 3)
 * bytes.
 */
void plm_frame_to_rgb(plm_frame_t *frame, uint8_t *rgb);

/* -----------------------------------------------------------------------------
 * plm_audio public API
 * Decode MPEG-1 Audio Layer II ("mp2") data into raw samples
 */

/**
 * Create an audio decoder with a plm_buffer as source.
 */
plm_audio_t *plm_audio_create_with_buffer(plm_buffer_t *buffer,
                                          int destroy_when_done);

/**
 * Destroy an audio decoder and free all data
 */
void plm_audio_destroy(plm_audio_t *self);

/**
 * Get the samplerate in samples per second
 */
int plm_audio_get_samplerate(plm_audio_t *self);

/**
 * Get the current internal time in seconds
 */
double plm_audio_get_time(plm_audio_t *self);

/**
 * Rewinds the internal buffer. See plm_buffer_rewind().
 */
void plm_audio_rewind(plm_audio_t *self);

/**
 * Decode and return one "frame" of audio and advance the internal time by
 * (PLM_AUDIO_SAMPLES_PER_FRAME/samplerate) seconds. The returned samples_t
 * is valid until the next call of plm_audio_decode() or until the audio
 * decoder is destroyed.
 */
plm_samples_t *plm_audio_decode(plm_audio_t *self);

extern long plmpegdecode_latency_;

COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_DSP_MPEG_MPEG_H_ */
