/*
* DVD-Audio Decoder plugin
* Copyright (c) 2009-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 "b2n.h"
#include "dvda_zone.h"
#include <algorithm>
#include <cmath>
#include <string>

auto PTS_TO_SEC = [](auto pts) {
	return pts / 90000.0;
};

dvda_sector_pointer_t::dvda_sector_pointer_t(dvda_track_t& track, ats_track_sector_t& p_ats_track_sector, int sp_index) : dvda_track(track) {
	index = sp_index;
	first = p_ats_track_sector.first;
	last  = p_ats_track_sector.last;
}

double dvda_sector_pointer_t::get_time() {
	return PTS_TO_SEC(get_length_pts());
}

uint32_t dvda_sector_pointer_t::get_length_pts() {
	auto denom = dvda_track.get_last() - dvda_track.get_first() + 1u;
	if (denom) {
		auto pts = (double)dvda_track.get_length_pts() * (double)(last - first + 1u) / (double)denom;
		return (uint32_t)pts;
	}
	return 0u;
}

dvda_track_t::dvda_track_t(ats_track_timestamp_t& ats_track_timestamp, int track_no) {
	track          = track_no;
	index          = ats_track_timestamp.n;
	first_pts      = ats_track_timestamp.first_pts;
	length_pts     = ats_track_timestamp.len_in_pts;
	downmix_matrix = ats_track_timestamp.downmix_matrix < DOWNMIX_MATRICES ? ats_track_timestamp.downmix_matrix : -1;
}

double dvda_track_t::get_time() const {
	return PTS_TO_SEC(length_pts);
}

uint32_t dvda_track_t::get_first() {
	auto sector = (get_sector_pointers().size() > 0) ? get_sector_pointer(0).get_first() : 0u;
	for (auto i = 1u; i < get_sector_pointers().size(); i++) {
		sector = std::min(sector, get_sector_pointer(i).get_first());
	}
	return sector; 
};

uint32_t dvda_track_t::get_last() {
	auto sector = (get_sector_pointers().size() > 0) ? get_sector_pointer(0).get_last() : 0u;
	for (auto i = 1u; i < get_sector_pointers().size(); i++) {
		sector = std::max(sector, get_sector_pointer(i).get_last());
	}
	return sector; 
};

dvda_title_t::dvda_title_t(ats_title_t* p_ats_title, ats_title_idx_t* p_ats_title_idx) {
	title    = p_ats_title_idx->title_nr;
	indexes  = p_ats_title->indexes;
	tracks   = p_ats_title->tracks;
	length_pts = p_ats_title->len_in_pts;
}

double dvda_title_t::get_time() const {
	return PTS_TO_SEC(length_pts);
}

dvda_downmix_channel_t* dvda_downmix_matrix_t::get_downmix_channel(int channel, int dmx_channel) {
	if (channel >= 0 && channel < DOWNMIX_CHANNELS && dmx_channel >= 0 && dmx_channel < 2) {
		return &LR_dmx[channel][dmx_channel];
	}
	return nullptr;
}

double dvda_downmix_matrix_t::get_downmix_coef(int channel, int dmx_channel) {
	auto dmx_coef{ 0.0 };
	dvda_downmix_channel_t*	p_dmx_channel = get_downmix_channel(channel, dmx_channel);
	if (p_dmx_channel) {
		auto coef = p_dmx_channel->coef;
		if (coef < 200) {
			auto L_db = -0.2007 * coef;
			dmx_coef = std::pow(10.0, L_db / 20.0);
			if (p_dmx_channel->inv_phase) {
				dmx_coef = -dmx_coef;
			}
		}
		else if (coef < 255) {
			auto L_db = -(2.0 * 0.2007 * (coef - 200) + 0.2007 * 200);
			dmx_coef = std::pow(10.0, L_db / 20.0);
			if (p_dmx_channel->inv_phase) {
				dmx_coef = -dmx_coef;
			}
		}
	}
	return dmx_coef;
}

bool dvda_titleset_t::open(size_t titleset) {
	dvda_titleset = titleset;
	dvda_titleset_type = dvda_titleset_e::DVDTitlesetUnknown;
	char file_name[13];
	snprintf(file_name, sizeof(file_name), "ATS_%02d_0.IFO", (int)dvda_titleset);
	auto atsi_file = dvda_zone.get_filesystem().open(file_name);
	if (!atsi_file) {
		snprintf(file_name, sizeof(file_name), "ATS_%02d_0.BUP", (int)dvda_titleset);
		atsi_file = dvda_zone.get_filesystem().open(file_name);
		if (!atsi_file) {
			return is_open;
		}
	}
	auto atsi_size = atsi_file->get_size();
	if (atsi_size >= 0x0800) {
		atsi_mat_t atsi_mat;
		if (atsi_file->read((char*)&atsi_mat, sizeof(atsi_mat_t)) == sizeof(atsi_mat_t)) {
			if (memcmp("DVDAUDIO-ATS", atsi_mat.ats_identifier, 12) == 0) {
				uint32_t aob_offset{ 0 };
				for (auto i = 0; i < 9; i++) {
					snprintf(aobs[i].file_name, sizeof(aobs[i].file_name), "ATS_%02d_%01d.AOB", (int)dvda_titleset, i + 1);
					aobs[i].dvda_fileobject = dvda_zone.get_filesystem().open(aobs[i].file_name);
					if (aobs[i].dvda_fileobject) {
						auto aob_size = aobs[i].dvda_fileobject->get_size();
						aobs[i].block_first = aob_offset;
						aobs[i].block_last = (uint32_t)(aobs[i].block_first + aob_size / DVD_BLOCK_SIZE + (aob_size % DVD_BLOCK_SIZE > 0 ? 1 : 0) - 1);
					}
					else {
						aobs[i].block_first = aob_offset;
						aobs[i].block_last = aobs[i].block_first + (1024 * 1024 - 32) * 1024 / DVD_BLOCK_SIZE - 1;
					}
					aob_offset = aobs[i].block_last + 1;
				}
				B2N_32(atsi_mat.ats_last_sector);
				B2N_32(atsi_mat.atsi_last_sector);
				B2N_32(atsi_mat.ats_category);
				B2N_32(atsi_mat.atsi_last_byte);
				B2N_32(atsi_mat.atsm_vobs);
				B2N_32(atsi_mat.atstt_vobs);
				B2N_32(atsi_mat.ats_ptt_srpt);
				B2N_32(atsi_mat.ats_pgcit);
				B2N_32(atsi_mat.atsm_pgci_ut);
				B2N_32(atsi_mat.ats_tmapt);
				B2N_32(atsi_mat.atsm_c_adt);
				B2N_32(atsi_mat.atsm_vobu_admap);
				B2N_32(atsi_mat.ats_c_adt);
				B2N_32(atsi_mat.ats_vobu_admap);
				for (auto i = 0; i < 8; i++) {
					B2N_16(atsi_mat.ats_audio_format[i].audio_type);
				}
				for (auto m = 0; m < DOWNMIX_MATRICES; m++) {
					for (auto ch = 0; ch < DOWNMIX_CHANNELS; ch++) {
						downmix_matrices[m].get_downmix_channel(ch, 0)->inv_phase = ((atsi_mat.ats_downmix_matrices[m].phase.L >> (DOWNMIX_CHANNELS - ch - 1)) & 1) == 1;
						downmix_matrices[m].get_downmix_channel(ch, 0)->coef = atsi_mat.ats_downmix_matrices[m].coef[ch].L;
						downmix_matrices[m].get_downmix_channel(ch, 1)->inv_phase = ((atsi_mat.ats_downmix_matrices[m].phase.R >> (DOWNMIX_CHANNELS - ch - 1)) & 1) == 1;
						downmix_matrices[m].get_downmix_channel(ch, 1)->coef = atsi_mat.ats_downmix_matrices[m].coef[ch].R;
					}
				}
				if (atsi_mat.atsm_vobs == 0) {
					dvda_titleset_type = dvda_titleset_e::DVDTitlesetAudio;
				}
				else {
					dvda_titleset_type = dvda_titleset_e::DVDTitlesetVideo;
				}
				aobs_last_sector = atsi_mat.ats_last_sector - 2 * (atsi_mat.atsi_last_sector + 1);
				uint32_t ats_len = (uint32_t)atsi_size - 0x0800;
				atsi_file->seek(0x0800);
				std::vector<uint8_t> ats_buf(ats_len, 0);
				uint8_t* ats_end = ats_buf.data() + ats_len;
				atsi_file->read((char*)ats_buf.data(), ats_len);
				audio_pgcit_t* p_audio_pgcit = (audio_pgcit_t*)ats_buf.data();
				ats_title_idx_t* p_ats_title_idx = nullptr;
				if ((uint8_t*)p_audio_pgcit + AUDIO_PGCIT_SIZE > ats_end) {
					goto error_exit;
				}
				B2N_16(p_audio_pgcit->nr_of_titles);
				B2N_32(p_audio_pgcit->last_byte);
				ats_end = ats_buf.data() + ((ats_len < p_audio_pgcit->last_byte + 1) ? ats_len : p_audio_pgcit->last_byte + 1);
				p_ats_title_idx = (ats_title_idx_t*)((uint8_t*)p_audio_pgcit + AUDIO_PGCIT_SIZE);
				for (auto i = 0u; i < p_audio_pgcit->nr_of_titles; i++) {
					if ((uint8_t*)&p_ats_title_idx[i] + ATS_TITLE_IDX_SIZE > ats_end) {
						goto error_exit;
					}
					B2N_32(p_ats_title_idx[i].title_table_offset);
					ats_title_t* p_ats_title = (ats_title_t*)((uint8_t*)p_audio_pgcit + p_ats_title_idx[i].title_table_offset);
					if ((uint8_t*)p_ats_title + ATS_TITLE_SIZE > ats_end) {
						goto error_exit;
					}
					B2N_32(p_ats_title->len_in_pts);
					B2N_16(p_ats_title->track_sector_table_offset);
					auto p_ats_track_timestamp = (ats_track_timestamp_t*)((uint8_t*)p_ats_title + ATS_TITLE_SIZE);
					auto p_ats_track_sector = (ats_track_sector_t*)((uint8_t*)p_ats_title + p_ats_title->track_sector_table_offset);
					auto&& dvda_title = get_titles().emplace_back(p_ats_title, &p_ats_title_idx[i]);
					for (auto j = 0; j < p_ats_title->tracks; j++) {
						if ((uint8_t*)&p_ats_track_timestamp[j] + ATS_TRACK_TIMESTAMP_SIZE > ats_end) {
							goto error_exit;
						}
						B2N_32(p_ats_track_timestamp[j].first_pts);
						B2N_32(p_ats_track_timestamp[j].len_in_pts);
						dvda_title.get_tracks().emplace_back(p_ats_track_timestamp[j], j + 1);
					}
					for (auto j = 0; j < p_ats_title->indexes; j++) {
						if ((uint8_t*)&p_ats_track_sector[j] + ATS_TRACK_SECTOR_SIZE > ats_end) {
							goto error_exit;
						}
						B2N_32(p_ats_track_sector[j].first);
						B2N_32(p_ats_track_sector[j].last);
						for (auto k = 0u; k < dvda_title.get_tracks().size(); k++) {
							int track_curr_idx, track_next_idx;
							auto&& dvda_track = dvda_title.get_track(k);
							track_curr_idx = dvda_track.get_index();
							track_next_idx = (k < dvda_title.get_tracks().size() - 1) ? dvda_title.get_track(k + 1).get_index() : 0;
							if (j + 1 >= track_curr_idx && (j + 1 < track_next_idx || track_next_idx == 0)) {
								dvda_track.get_sector_pointers().emplace_back(dvda_track, p_ats_track_sector[j], j + 1);
							}
						}
					}
					for (auto j = 0u; j < dvda_title.get_tracks().size(); j++) {
						auto dvda_track = dvda_title.get_track(j);
					}
				}
				is_open = true;
			error_exit:
				ats_buf.clear();
			}
		}
	}
	return is_open;
}

dvda_titleset_t::dvda_titleset_t(dvda_zone_t& zone) : dvda_zone(zone) {
	is_open = false;
}

dvda_titleset_t::~dvda_titleset_t() {
	close_aobs();
}

DVDAERROR dvda_titleset_t::get_block(uint32_t block, uint8_t* buf_ptr) {
	for (auto i = 0; i < 9; i++) {
		if (aobs[i].dvda_fileobject && block >= aobs[i].block_first && block <= aobs[i].block_last) {
			if (!aobs[i].dvda_fileobject->seek((block - aobs[i].block_first) * DVD_BLOCK_SIZE)) {
				return DVDAERR_CANNOT_SEEK_ATS_XX_X_AOB;
			}
			if (aobs[i].dvda_fileobject->read((char*)buf_ptr, DVD_BLOCK_SIZE) != DVD_BLOCK_SIZE) {
				return DVDAERR_CANNOT_READ_ATS_XX_X_AOB;
			}
			return DVDAERR_OK;
		}
	}
	return DVDAERR_AOB_BLOCK_NOT_FOUND;
}

size_t dvda_titleset_t::get_blocks(uint32_t block_first, uint32_t block_last, uint8_t* buf_ptr) {
	auto blocks_read{ 0 };
	auto aob_index{ -1 };
	for (auto i = 0; i < 9; i++) {
		if (block_first >= aobs[i].block_first && block_first <= aobs[i].block_last) {
			aob_index = i;
			break;
		} 
	} 
	if (aob_index >= 0) {
		if (aobs[aob_index].dvda_fileobject) {
			if (aobs[aob_index].dvda_fileobject->seek((block_first - aobs[aob_index].block_first) * DVD_BLOCK_SIZE)) {
				if (block_last <= aobs[aob_index].block_last) {
					int bytes_to_read = (block_last + 1 - block_first) * DVD_BLOCK_SIZE;
					int bytes_read = (int)aobs[aob_index].dvda_fileobject->read((char*)buf_ptr, bytes_to_read);
					blocks_read += bytes_read / DVD_BLOCK_SIZE;
				}
				else {
					int bytes_to_read = (aobs[aob_index].block_last + 1 - block_first) * DVD_BLOCK_SIZE;
					int bytes_read = (int)aobs[aob_index].dvda_fileobject->read((char*)buf_ptr, bytes_to_read);
					blocks_read += bytes_read / DVD_BLOCK_SIZE;
					if (aob_index + 1 < 9) {
						if (aobs[aob_index + 1].dvda_fileobject) {
							if (aobs[aob_index + 1].dvda_fileobject->seek(0)) {
								bytes_to_read = (block_last + 1 - aobs[aob_index + 1].block_first) * DVD_BLOCK_SIZE;
								bytes_read = (int)aobs[aob_index + 1].dvda_fileobject->read((char*)buf_ptr + blocks_read * DVD_BLOCK_SIZE, bytes_to_read);
								blocks_read += bytes_read / DVD_BLOCK_SIZE;
							}
						}
					}
				}
			}
		}
	}
	return blocks_read;
}

void dvda_titleset_t::close_aobs() {
	for (auto i = 0; i < 9; i++) {
		aobs[i].dvda_fileobject.reset();
	}
}

bool dvda_zone_t::open() {
	close();
	auto is_open{ false };
	audio_titlesets = 99;
	video_titlesets = 99;
	auto amgi_file = dvda_filesystem.open("AUDIO_TS.IFO");
	if (!amgi_file) {
		amgi_file = dvda_filesystem.open("AUDIO_TS.BUP");
	}
	if (amgi_file) {
		amgi_mat_t amgi_mat;
		if (amgi_file->read((char*)&amgi_mat, sizeof(amgi_mat_t)) == sizeof(amgi_mat_t)) {
			if (memcmp("DVDAUDIO-AMG", amgi_mat.amg_identifier, 12) == 0) {
				B2N_32(amgi_mat.amg_last_sector);
				B2N_32(amgi_mat.amgi_last_sector);
				B2N_32(amgi_mat.amg_category);
				B2N_16(amgi_mat.amg_nr_of_volumes);
				B2N_16(amgi_mat.amg_this_volume_nr);
				B2N_32(amgi_mat.amg_asvs);
				B2N_64(amgi_mat.amg_pos_code);
				B2N_32(amgi_mat.amgi_last_byte);
				B2N_32(amgi_mat.first_play_pgc);
				B2N_32(amgi_mat.amgm_vobs);
				B2N_32(amgi_mat.att_srpt);
				B2N_32(amgi_mat.aott_srpt);
				B2N_32(amgi_mat.amgm_pgci_ut);
				B2N_32(amgi_mat.ats_atrt);
				B2N_32(amgi_mat.txtdt_mgi);
				B2N_32(amgi_mat.amgm_c_adt);
				B2N_32(amgi_mat.amgm_vobu_admap);
				B2N_16(amgi_mat.amgm_audio_attr.lang_code);
				B2N_16(amgi_mat.amgm_subp_attr.lang_code);
				audio_titlesets = (audio_titlesets < amgi_mat.amg_nr_of_audio_title_sets) ? audio_titlesets : amgi_mat.amg_nr_of_audio_title_sets;
				video_titlesets = (video_titlesets < amgi_mat.amg_nr_of_video_title_sets) ? video_titlesets : amgi_mat.amg_nr_of_video_title_sets;
				for (auto i = 0u; i < audio_titlesets; i++) {
					auto& dvda_titleset = get_titlesets().emplace_back(*this);
					if (!dvda_titleset.open(i + 1)) {
						get_titlesets().pop_back();
					}
				}
				for (auto titleset_index = 0u; titleset_index < get_titlesets().size(); titleset_index++) {
					auto dvda_titleset = get_titleset(titleset_index);
					for (auto title_index = 0u; title_index < dvda_titleset.get_titles().size(); title_index++) {
						auto dvda_title = dvda_titleset.get_title(title_index);
						for (auto track_index = 0u; track_index < dvda_title.get_tracks().size(); track_index++) {
							auto dvda_track = dvda_title.get_track(track_index);
							for (auto sector_pointer_index = 0u; sector_pointer_index < dvda_track.get_sector_pointers().size(); sector_pointer_index++) {
								[[maybe_unused]] auto dvda_sector_pointer = dvda_track.get_sector_pointer(sector_pointer_index);
							}
						}
					}
				}
				is_open = true;
			}
		}
	}
	return is_open;
}

void dvda_zone_t::close() {
	dvda_titlesets.clear();
}

DVDAERROR dvda_zone_t::get_block(size_t titleset, uint32_t block_no, uint8_t* buf_ptr) {
	DVDAERROR err = get_titleset(titleset).get_block(block_no, buf_ptr);
	return err;
}

size_t dvda_zone_t::get_blocks(size_t titleset, uint32_t block_no, size_t blocks, uint8_t* buf_ptr) {
	blocks = get_titleset(titleset).get_blocks(block_no, (int)(block_no + blocks - 1), buf_ptr);
	return blocks;
}
