/*
* MPD DVD-Audio Decoder plugin
* Copyright (c) 2014-2025 Maxim V.Anisiutkin <maxim.anisiutkin@gmail.com>
*
* DVD-Audio Decoder is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* DVD-Audio Decoder 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "dvda_disc.h"
#include "mlp_audio_stream.h"
#include "pcm_audio_stream.h"

#include "config.h"
#include <malloc.h>
#include <string>
#include "Log.hxx"
#include "tag/Tag.hxx"
#include "util/Domain.hxx"

static constexpr Domain dvdaiso_domain("dvdaiso");

dvda_disc_t::dvda_disc_t() {
	dvda_media = nullptr;
	stream_downmix = false;
	sel_track_index = -1;
}

dvda_disc_t::~dvda_disc_t() {
	close();
}

dvda_filesystem_t* dvda_disc_t::get_filesystem() {
	return dvda_filesystem.get();
}

audio_track_t dvda_disc_t::get_track(uint32_t track_index) {
	return track_list.get_track_by_index(track_index);
}

uint32_t dvda_disc_t::get_tracks() {
	return track_list.size();
}

uint32_t dvda_disc_t::get_channels() {
	auto info = track_list.get_track_by_index(sel_track_index).audio_stream_info;
	return info.group1_channels + info.group2_channels;
}

uint32_t dvda_disc_t::get_loudspeaker_config() {
	return 0;
}

uint32_t dvda_disc_t::get_samplerate() {
	return track_list.get_track_by_index(sel_track_index).audio_stream_info.group1_samplerate;
}

double dvda_disc_t::get_duration() {
	return track_list.get_track_by_index(sel_track_index).duration;
}

double dvda_disc_t::get_duration(uint32_t track_index) {
	if (track_index < (uint32_t)track_list.size()) {
		return track_list.get_track_by_index(track_index).duration;
	}
	return 0.0;
}

bool dvda_disc_t::can_downmix() {
	return track_list.get_track_by_index(sel_track_index).audio_stream_info.can_downmix;
}

void dvda_disc_t::get_info(uint32_t track_index, bool downmix, TagHandler& handler) {
	if (!(track_index < (uint32_t)track_list.size())) {
		return;
	}
	auto info = track_list.get_track_by_index(track_index).audio_stream_info;
	//int ts = track_list[track_index].dvda_titleset;
	//int ti = track_list[track_index].dvda_title;
	auto tr = track_list.get_track_by_index(track_index).dvda_track;

	char disc_label[32];
	bool label_ok = dvda_filesystem->get_name(disc_label);
	disc_label[31] = '\0';

	std::string disc_path = dvda_media->get_name();
	size_t s0 = disc_path.rfind('/');
	size_t s1 = disc_path.rfind('.');
	std::string disc_name;
	if (s0 != std::string::npos && s1 != std::string::npos) {
		disc_name = disc_path.substr(s0 + 1, s1 - s0 - 1);
	}

	std::string tag_value;
	tag_value  = label_ok ? disc_label : "DVD-Audio";
	handler.OnTag(TAG_DISC, tag_value.c_str());

	// Clean album title - no format info appended
	tag_value  = !disc_name.empty() ? disc_name : "Album";
	handler.OnTag(TAG_ALBUM, tag_value.c_str());
	
	// Store channel/format info in comment tag for reference
	std::string comment_value = "DVDA_";
	if (!downmix) {
		comment_value += std::to_string(info.group1_channels + info.group2_channels);
		comment_value += "CH_";
	}
	else {
		comment_value += "DMX_";
	}
	comment_value += info.stream_id == MLP_STREAM_ID ? (info.stream_type == STREAM_TYPE_MLP ? "MLP" : "TrueHD") : "PCM";
	handler.OnTag(TAG_COMMENT, comment_value.c_str());

	tag_value  = "Artist";
	handler.OnTag(TAG_ARTIST, tag_value.c_str());

	char track_number_string[16];
	//sprintf(track_number_string, "%02d.%02d.%02d", ts, ti, tr);
	sprintf(track_number_string, "%02d", tr);
	
	// Clean track title - just track number and name
	tag_value  = track_number_string;
	tag_value += " - ";
	tag_value += "Track " + std::to_string(tr);
	handler.OnTag(TAG_TITLE, tag_value.c_str());

	tag_value  = "Composer";
	handler.OnTag(TAG_COMPOSER, tag_value.c_str());

	tag_value  = "Performer";
	handler.OnTag(TAG_PERFORMER, tag_value.c_str());

	tag_value  = "Genre";
	handler.OnTag(TAG_GENRE, tag_value.c_str());
}

bool dvda_disc_t::open(dvda_media_t* _dvda_media) {
	if (!close()) {
		return false;
	}
	dvda_media = _dvda_media;
	dvda_filesystem = std::make_unique<dvda_filesystem_t>();
	if (!dvda_filesystem) {
		return false;
	}
	if (!dvda_filesystem->mount(dvda_media)) {
		return false;
	}
	dvda_zone = std::make_unique<dvda_zone_t>(*dvda_filesystem);
	if (!dvda_zone) {
		return false;
	}
	if (!dvda_zone->open()) {
		return false;
	}
	if (!(dvda_zone->get_titlesets().size() > 0)) {
		return false;
	}
	track_list.init(*dvda_zone);
	return track_list.size() > 0;
}

bool dvda_disc_t::close() {
	track_list.clear();
	if (dvda_zone) {
		dvda_zone->close();
	}
	dvda_zone.reset();
	dvda_filesystem.reset();
	dvda_media = nullptr;
	sel_track_index = -1;
	return true;
}

bool dvda_disc_t::select_track(uint32_t track_index, size_t offset) {
	sel_track_index = track_index;
	sel_track_offset = offset;
	audio_track = track_list.get_track_by_index(sel_track_index);
	sel_titleset_index = audio_track.dvda_titleset - 1;
	track_stream.init(512 * DVD_BLOCK_SIZE, 4 * DVD_BLOCK_SIZE, 16 * DVD_BLOCK_SIZE);
	ps1_data.resize(16 * DVD_BLOCK_SIZE);
	stream_block_current = audio_track.block_first;
	stream_size = (audio_track.block_last + 1 - audio_track.block_first) * DVD_BLOCK_SIZE;
	stream_ps1_info.header.stream_id = UNK_STREAM_ID;
	stream_duration = audio_track.duration;
	stream_needs_reinit = false;
	major_sync_0 = false;
	return true;
}

bool dvda_disc_t::get_downmix() {
	return stream_downmix;
}

bool dvda_disc_t::set_downmix(bool downmix) {
	if (downmix && !audio_track.audio_stream_info.can_downmix) {
		return false;
	}
	stream_downmix = downmix;
	return true;
}

bool dvda_disc_t::read_frame(uint8_t* frame_data, size_t* frame_size) {
	decode_run_read_stream_start:
	if (track_stream.is_ready_to_write() && !stream_needs_reinit) {
		stream_buffer_read();
	}
	int data_size = *frame_size, bytes_decoded = 0;
	bytes_decoded = (audio_stream ? audio_stream->decode(frame_data, &data_size, track_stream.get_read_ptr(), track_stream.get_read_size()) : 0);
	if (bytes_decoded <= 0) {
		track_stream.move_read_ptr(0);
		if (bytes_decoded == audio_stream_t::RETCODE_EXCEPT) {
			LogFmt(LogLevel::ERROR, dvdaiso_domain, "Exception occured in DVD-Audio Decoder");
			return false;
		}
		bool decoder_needs_reinit = (bytes_decoded == audio_stream_t::RETCODE_REINIT);
		if (decoder_needs_reinit) {
			audio_stream.reset();
			LogFmt(LogLevel::WARNING, dvdaiso_domain, "Reinitializing DVD-Audio Decoder: MLP/TrueHD");
			goto decode_run_read_stream_start;
		}
		if (track_stream.get_read_size() == 0) {
			if (stream_needs_reinit) {
				stream_needs_reinit = false;
				audio_stream.reset();
				stream_ps1_info.header.stream_id = UNK_STREAM_ID;
				LogFmt(LogLevel::WARNING, dvdaiso_domain, "Reinitializing DVD-Audio Decoder: PCM");
				goto decode_run_read_stream_start;
			}
			else {
				return false;
			}
		}
		if (audio_stream) {
			int major_sync = audio_stream->resync(track_stream.get_read_ptr(), track_stream.get_read_size());
			if (major_sync == 0) {
				if (major_sync_0) {
					if (track_stream.get_read_size() > 4)
						major_sync = audio_stream->resync(track_stream.get_read_ptr() + 1, track_stream.get_read_size() - 1);
				}
				else
					major_sync_0 = true;
			}
			if (major_sync < 0) {
				if (stream_needs_reinit)
					major_sync = track_stream.get_read_size();
				else
					major_sync = track_stream.get_read_size() > 4 ? track_stream.get_read_size() - 4 : 0;
				if (major_sync <= 0)
					return false;
			}
			if (major_sync > 0) {
				track_stream.move_read_ptr(major_sync);
				LogFmt(LogLevel::ERROR, dvdaiso_domain, "DVD-Audio Decoder is out of sync: %d bytes skipped", major_sync);
			}
			goto decode_run_read_stream_start;
		}
		else {
			create_audio_stream(stream_ps1_info, track_stream.get_read_ptr(), track_stream.get_read_size(), stream_downmix);
			if (audio_stream) {
				if (audio_stream->get_downmix()) {
					audio_stream->set_downmix_coef(audio_track.LR_dmx_coef);
				}
				audio_stream->set_check(false);
				track_stream.move_read_ptr(audio_stream->get_info().sync_offset);
			}
			else {
				track_stream.move_read_ptr(DVD_BLOCK_SIZE);
				stream_ps1_info.header.stream_id = UNK_STREAM_ID;
				LogFmt(LogLevel::ERROR, dvdaiso_domain, "DVD-Audio Decoder initialization failed");
			}
			goto decode_run_read_stream_start;
		}
		return false;
	}
	major_sync_0 = false;
	track_stream.move_read_ptr(bytes_decoded);
	*frame_size = data_size;
	return true;
}

bool dvda_disc_t::seek(double seconds) {
	track_stream.reinit();
	audio_stream.reset();
	uint32_t offset = (uint32_t)((seconds / (audio_track.duration + 1.0)) * (double)(audio_track.block_last + 1 - audio_track.block_first));
	if (offset > audio_track.block_last - audio_track.block_first - 1) {
		offset = audio_track.block_last - audio_track.block_first - 1;
	}
	stream_block_current = audio_track.block_first + offset;
	stream_ps1_info.header.stream_id = UNK_STREAM_ID;
	return true;
}

bool dvda_disc_t::create_audio_stream(sub_header_t& p_ps1_info, uint8_t* p_buf, int p_buf_size, bool p_downmix) {
	audio_stream.reset();
	int init_code = -1;
	switch (stream_ps1_info.header.stream_id) {
	case MLP_STREAM_ID:
		audio_stream = std::make_unique<mlp_audio_stream_t>();
		if (audio_stream) {
			init_code = audio_stream->init(p_buf, p_buf_size, p_downmix);
		}
		break;
	case PCM_STREAM_ID:
		audio_stream = std::make_unique<pcm_audio_stream_t>();
		if (audio_stream) {
			init_code = audio_stream->init((uint8_t*)&stream_ps1_info.extra_header, p_ps1_info.header.extra_header_length, p_downmix);
		}
		break;
	default:
		break;
	}
	if (!audio_stream) {
		return false;
	}
	if (init_code < 0) {
		audio_stream.reset();
		return false;
	}
	auto info = audio_stream->get_info();
	stream_samplerate = info.group1_samplerate;
	stream_bits = info.group1_bits > 16 ? 32 : 16;
	if (audio_stream->get_downmix()) {
		stream_channels = 2;
		//stream_channel_map = audio_chunk_t::g_guess_channel_config(pcm_out_channels);
	}
	else {
		stream_channels = info.group1_channels + info.group2_channels;
		//stream_channel_map = audio_chunk_t::g_channel_config_from_wfx(audio_stream->get_wfx_channels());
	}
	return true;
}

void dvda_disc_t::stream_buffer_read() {
	sub_header_t ps1_info;
	int blocks_to_read, blocks_read, bytes_written = 0;
	blocks_to_read = track_stream.get_write_size() / DVD_BLOCK_SIZE;
	if (stream_block_current <= audio_track.block_last) {
		if (stream_block_current + blocks_to_read > audio_track.block_last + 1) {
			blocks_to_read = audio_track.block_last + 1 - stream_block_current;
		}
		blocks_read = dvda_zone->get_blocks(sel_titleset_index, stream_block_current, blocks_to_read, track_stream.get_write_ptr());
		dvda_block_t::get_ps1(track_stream.get_write_ptr(), blocks_read, ps1_data.data(), &bytes_written, &ps1_info);
		memcpy(track_stream.get_write_ptr(), ps1_data.data(), bytes_written);
		track_stream.move_write_ptr(bytes_written);
		if (stream_ps1_info.header.stream_id == UNK_STREAM_ID) {
			stream_ps1_info = ps1_info;
		}
		if (blocks_read < blocks_to_read) {
			LogFmt(LogLevel::ERROR, dvdaiso_domain, "DVD-Audio Decoder cannot read track data: titleset = %d, block_number = %d, blocks_to_read = %d", sel_titleset_index, stream_block_current + blocks_read, blocks_to_read - blocks_read);
		}
		stream_block_current += blocks_to_read;
		if (stream_block_current > audio_track.block_last) {
			int blocks_after_last = dvda_zone->get_titleset(sel_titleset_index).get_last() - audio_track.block_last;
			int blocks_to_sync = blocks_after_last < 8 ? blocks_after_last : 8;
			if (stream_block_current <= audio_track.block_last + blocks_to_sync) {
				if (stream_block_current + blocks_to_read > audio_track.block_last + 1 + blocks_to_sync) {
					blocks_to_read = audio_track.block_last + 1 + blocks_to_sync - stream_block_current;
				}
				blocks_read = dvda_zone->get_blocks(sel_titleset_index, stream_block_current, blocks_to_read, track_stream.get_write_ptr());
				bytes_written = 0;
				dvda_block_t::get_ps1(track_stream.get_write_ptr(), blocks_read, ps1_data.data(), &bytes_written, nullptr);
				memcpy(track_stream.get_write_ptr(), ps1_data.data(), bytes_written);
				if (audio_stream) {
					int major_sync = audio_stream->resync(track_stream.get_write_ptr(), bytes_written);
					if (major_sync > 0) {
						track_stream.move_write_ptr(major_sync);
					}
				}
				if (blocks_read < blocks_to_read) {
					LogFmt(LogLevel::ERROR, dvdaiso_domain, "DVD-Audio Decoder cannot read track tail: titleset = %d, block_number = %d, blocks_to_read = %d", sel_titleset_index, stream_block_current + blocks_read, blocks_to_read - blocks_read);
				}
				stream_block_current += blocks_to_read;
			}
		}
	}
}
