/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8   -*-â
âvi: set et ft=c ts=4 sw=4 fenc=utf-8                                       :viâ
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¡
â  PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer           â
â  Dominic Szablewski - https://phoboslab.org                                  â
â                                                                              â
â  The MIT License(MIT)                                                        â
â  Copyright(c) 2019 Dominic Szablewski                                        â
â                                                                              â
â  Permission is hereby granted, free of charge, to any person obtaining       â
â  a copy of this software and associated documentation files(the              â
â  "Software"), to deal in the Software without restriction, including         â
â  without limitation the rights to use, copy, modify, merge, publish,         â
â  distribute, sublicense, and / or sell copies of the Software, and to        â
â  permit persons to whom the Software is furnished to do so, subject to       â
â  the following conditions:                                                   â
â                                                                              â
â    The above copyright notice and this permission notice shall be            â
â    included in all copies or substantial portions of the Software.           â
â                                                                              â
â  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,             â
â  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF          â
â  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND                       â
â  NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE       â
â  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN             â
â  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN           â
â  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE            â
â  SOFTWARE.                                                                   â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ*/
#include "dsp/mpeg/buffer.h"
#include "dsp/mpeg/demux.h"
#include "dsp/mpeg/mpeg.h"
#include "libc/mem/mem.h"
#include "libc/str/str.h"

asm(".ident\t\"\\n\\n\
PL_MPEG (MIT License)\\n\
Copyright(c) 2019 Dominic Szablewski\\n\
https://phoboslab.org\"");
asm(".include \"libc/disclaimer.inc\"");

/* clang-format off */
// ----------------------------------------------------------------------------
// plm_demux implementation

plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done) {
	plm_demux_t *self = (plm_demux_t *)malloc(sizeof(plm_demux_t));
	memset(self, 0, sizeof(plm_demux_t));

	self->buffer = buffer;
	self->destroy_buffer_when_done = destroy_when_done;

	if (plm_buffer_find_start_code(self->buffer, START_PACK) != -1) {
		plm_demux_decode_pack_header(self);
	}
	if (plm_buffer_find_start_code(self->buffer, START_SYSTEM) != -1) {
		plm_demux_decode_system_header(self);
	}
	return self;
}

void plm_demux_destroy(plm_demux_t *self) {
	if (self->destroy_buffer_when_done) {
		plm_buffer_destroy(self->buffer);
	}
	free(self);
}

int plm_demux_get_num_video_streams(plm_demux_t *self) {
	return self->num_video_streams;
}

int plm_demux_get_num_audio_streams(plm_demux_t *self) {
	return self->num_audio_streams;
}

void plm_demux_rewind(plm_demux_t *self) {
	plm_buffer_rewind(self->buffer);
}

plm_packet_t *plm_demux_decode(plm_demux_t *self) {
	if (self->current_packet.length) {
		size_t bits_till_next_packet = self->current_packet.length << 3;
		if (!plm_buffer_has(self->buffer, bits_till_next_packet)) {
			return NULL;
		}
		plm_buffer_skip(self->buffer, bits_till_next_packet);
		self->current_packet.length = 0;
	}

	if (!self->has_pack_header) {
		if (plm_buffer_find_start_code(self->buffer, START_PACK) != -1) {
			plm_demux_decode_pack_header(self);
		}
		else {
			return NULL;
		}
	}

	if (!self->has_system_header) {
		if (plm_buffer_find_start_code(self->buffer, START_SYSTEM) != -1) {
			plm_demux_decode_system_header(self);
		}
		else {
			return NULL;
		}
	}

	// pending packet just waiting for data?
	if (self->next_packet.length) {
		return plm_demux_get_packet(self);
	}

	int code;
	do {
		code = plm_buffer_next_start_code(self->buffer);
		if (
			code == PLM_DEMUX_PACKET_VIDEO_1 ||
			code == PLM_DEMUX_PACKET_PRIVATE ||
			(code >= PLM_DEMUX_PACKET_AUDIO_1 && code <= PLM_DEMUX_PACKET_AUDIO_4)
		) {
			return plm_demux_decode_packet(self, code);
		}
	} while (code != -1);

	return NULL;
}

double plm_demux_read_time(plm_demux_t *self) {
	int64_t clock = plm_buffer_read(self->buffer, 3) << 30;
	plm_buffer_skip(self->buffer, 1);
	clock |= plm_buffer_read(self->buffer, 15) << 15;
	plm_buffer_skip(self->buffer, 1);
	clock |= plm_buffer_read(self->buffer, 15);
	plm_buffer_skip(self->buffer, 1);
	return (double)clock / 90000.0;
}

void plm_demux_decode_pack_header(plm_demux_t *self) {
	if (plm_buffer_read(self->buffer, 4) != 0x02) {
		return; // invalid
	}
	self->system_clock_ref = plm_demux_read_time(self);
	plm_buffer_skip(self->buffer, 1);
	plm_buffer_skip(self->buffer, 22); // mux_rate * 50
	plm_buffer_skip(self->buffer, 1);

	self->has_pack_header = true;
}

void plm_demux_decode_system_header(plm_demux_t *self) {
	plm_buffer_skip(self->buffer, 16); // header_length
	plm_buffer_skip(self->buffer, 24); // rate bound
	self->num_audio_streams = plm_buffer_read(self->buffer, 6);
	plm_buffer_skip(self->buffer, 5); // misc flags
	self->num_video_streams = plm_buffer_read(self->buffer, 5);

	self->has_system_header = true;
}

plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int start_code) {
	if (!plm_buffer_has(self->buffer, 8 << 3)) {
		return NULL;
	}

	self->next_packet.type = start_code;
	self->next_packet.length = plm_buffer_read(self->buffer, 16);
	self->next_packet.length -= plm_buffer_skip_bytes(self->buffer, 0xff); // stuffing

	// skip P-STD
	if (plm_buffer_read(self->buffer, 2) == 0x01) {
		plm_buffer_skip(self->buffer, 16);
		self->next_packet.length -= 2;
	}

	int pts_dts_marker = plm_buffer_read(self->buffer, 2);
	if (pts_dts_marker == 0x03) {
		self->next_packet.pts = plm_demux_read_time(self);
		plm_buffer_skip(self->buffer, 40); // skip dts
		self->next_packet.length -= 10;
	}
	else if (pts_dts_marker == 0x02) {
		self->next_packet.pts = plm_demux_read_time(self);
		self->next_packet.length -= 5;
	}
	else if (pts_dts_marker == 0x00) {
		self->next_packet.pts = 0;
		plm_buffer_skip(self->buffer, 4);
		self->next_packet.length -= 1;
	}
	else {
		return NULL; // invalid
	}

	return plm_demux_get_packet(self);
}

plm_packet_t *plm_demux_get_packet(plm_demux_t *self) {
	if (!plm_buffer_has(self->buffer, self->next_packet.length << 3)) {
		return NULL;
	}
	self->current_packet.data = self->buffer->bytes + (self->buffer->bit_index >> 3);
	self->current_packet.length = self->next_packet.length;
	self->current_packet.type = self->next_packet.type;
	self->current_packet.pts = self->next_packet.pts;
	self->next_packet.length = 0;
	return &self->current_packet;
}
