/*
* MPD SACD Decoder plugin
* Copyright (c) 2011-2021 Maxim V.Anisiutkin <maxim.anisiutkin@gmail.com>
*
* This program 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.
*
* This program 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 "config.h"

#include "tag/Id3Scan.hxx"

#ifdef ENABLE_ID3TAG
#include <id3tag.h>
#endif

#include "sacd_dsdiff.h"

#define MARK_TIME(m) ((double)m.hours * 60 * 60 + (double)m.minutes * 60 + (double)m.seconds + ((double)m.samples + (double)m.offset) / (double)samplerate)

sacd_dsdiff_t::sacd_dsdiff_t() {
	current_track = 0;
	is_emaster = false;
	is_dst_encoded = false;
}

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

uint32_t sacd_dsdiff_t::get_tracks() {
	return get_tracks(track_area);
}

uint32_t sacd_dsdiff_t::get_tracks(area_id_e area_id) {
	if ((area_id == AREA_TWOCH && channel_count == 2) || (area_id == AREA_MULCH && channel_count > 2) || area_id == AREA_BOTH) {
		return track_index.size();
	}
	return 0;
}

uint32_t sacd_dsdiff_t::get_channels() {
	return channel_count;
}

uint32_t sacd_dsdiff_t::get_loudspeaker_config() {
	return loudspeaker_config;
}

uint32_t sacd_dsdiff_t::get_samplerate() {
	return samplerate;
}

uint16_t sacd_dsdiff_t::get_framerate() {
	return framerate;
}

uint64_t sacd_dsdiff_t::get_size() {
	return current_size;
}

uint64_t sacd_dsdiff_t::get_offset() {
	return sacd_media->get_position() - current_offset;
}

double sacd_dsdiff_t::get_duration() {
	return get_duration(current_track);
}

double sacd_dsdiff_t::get_duration(uint32_t _track_index) {
	if (_track_index < track_index.size()) {
		double stop_time = is_emaster ? track_index[_track_index].stop_time2 : track_index[_track_index].stop_time1;
		return stop_time - track_index[_track_index].start_time;
	}
	return 0.0;
}

bool sacd_dsdiff_t::is_dst() {
	return is_dst_encoded;
}

bool sacd_dsdiff_t::open(sacd_media_t* _sacd_media, open_mode_e _mode) {
	sacd_media = _sacd_media;
	mode = _mode;
	dsti_size = 0;
	Chunk ck;
	ID id;
	bool skip_emaster_chunks = (mode & MODE_SINGLE_TRACK) == MODE_SINGLE_TRACK;
	uint32_t start_mark_count = 0;
	id3tags_t t_old;
	track_index.resize(0);
	id3tags.resize(0);
	if (!sacd_media->seek(0)) {
		return false;
	}
	if (!(sacd_media->read(&ck, sizeof(ck)) == sizeof(ck) && ck.has_id("FRM8"))) {
		return false;
	}
	if (!(sacd_media->read(&id, sizeof(id)) == sizeof(id) && id.has_id("DSD "))) {
		return false;
	}
	frm8_size = ck.get_size();
	id3_offset = sizeof(ck) + ck.get_size();
	while ((uint64_t)sacd_media->get_position() < frm8_size + sizeof(ck)) {
		if (!(sacd_media->read(&ck, sizeof(ck)) == sizeof(ck))) {
			return false;
		}
		if (ck.has_id("FVER") && ck.get_size() == 4) {
			if (!(sacd_media->read(&version, sizeof(version)) == sizeof(version))) {
				return false;
			}
			version = hton32(version);
		}
		else if (ck.has_id("PROP")) {
			if (!(sacd_media->read(&id, sizeof(id)) == sizeof(id) && id.has_id("SND "))) {
				return false;
			}
			int64_t id_prop_end = sacd_media->get_position() - sizeof(id) + ck.get_size();
			while (sacd_media->get_position() < id_prop_end) {
				if (!(sacd_media->read(&ck, sizeof(ck)) == sizeof(ck))) {
					return false;
				}
				if (ck.has_id("FS  ") && ck.get_size() == 4) {
					if (!(sacd_media->read(&samplerate, sizeof(samplerate)) == sizeof(samplerate))) {
						return false;
					}
					samplerate = hton32(samplerate);
				}
				else if (ck.has_id("CHNL")) {
					if (!(sacd_media->read(&channel_count, sizeof(channel_count)) == sizeof(channel_count))) {
						return false;
					}
					channel_count = hton16(channel_count);
					switch (channel_count) {
					case 2:
						loudspeaker_config = 0;
						break;
					case 5:
						loudspeaker_config = 3;
						break;
					case 6:
						loudspeaker_config = 4;
						break;
					default:
						loudspeaker_config = 65535;
						break;
					}
					sacd_media->skip(ck.get_size() - sizeof(channel_count));
				}
				else if (ck.has_id("CMPR")) {
					if (!(sacd_media->read(&id, sizeof(id)) == sizeof(id))) {
						return false;
					}
					if (id.has_id("DSD ")) {
						is_dst_encoded = false;
					}
					if (id.has_id("DST ")) {
						is_dst_encoded = true;
					}
					sacd_media->skip(ck.get_size() - sizeof(id));
				}
				else if (ck.has_id("LSCO")) {
					if (!(sacd_media->read(&loudspeaker_config, sizeof(loudspeaker_config)) == sizeof(loudspeaker_config))) {
						return false;
					}
					loudspeaker_config = hton16(loudspeaker_config);
					sacd_media->skip(ck.get_size() - sizeof(loudspeaker_config));
				}
				else if (ck.has_id("ID3 ")) {
					t_old.index  = 0;
					t_old.offset = sacd_media->get_position();
					t_old.tag_value.resize((uint32_t)ck.get_size());
					sacd_media->read(t_old.tag_value.data(), t_old.tag_value.size());
				}
				else {
					sacd_media->skip(ck.get_size());
				}
				sacd_media->skip(sacd_media->get_position() & 1);
			}
		}
		else if (ck.has_id("DSD ")) {
			data_offset = sacd_media->get_position();
			data_size = ck.get_size();
			framerate = 75;
			dsd_frame_size = samplerate / 8 * channel_count / framerate;
			frame_count = (uint32_t)(data_size / dsd_frame_size);
			sacd_media->skip(ck.get_size());
			track_t s;
			s.start_time = 0.0;
			s.stop_time1 = s.stop_time2 = (double)frame_count / framerate;
			track_index.push_back(s);
		}
		else if (ck.has_id("DST ")) {
			data_offset = sacd_media->get_position();
			data_size = ck.get_size();
			if (!(sacd_media->read(&ck, sizeof(ck)) == sizeof(ck) && ck.has_id("FRTE") && ck.get_size() == 6)) {
				return false;
			}
			data_offset += sizeof(ck) + ck.get_size();
			data_size -= sizeof(ck) + ck.get_size();
			current_offset = data_offset;
			current_size = data_size;
			if (!(sacd_media->read(&frame_count, sizeof(frame_count)) == sizeof(frame_count))) {
				return false;
			}
			frame_count = hton32(frame_count);
			if (!(sacd_media->read(&framerate, sizeof(framerate)) == sizeof(framerate))) {
				return false;
			}
			framerate = hton16(framerate);
			dsd_frame_size = samplerate / 8 * channel_count / framerate;
			sacd_media->seek(data_offset + data_size);
			track_t s;
			s.start_time = 0.0;
			s.stop_time1 = s.stop_time2 = (double)frame_count / framerate;
			track_index.push_back(s);
		}
		else if (ck.has_id("DSTI")) {
			dsti_offset = sacd_media->get_position();
			dsti_size = ck.get_size();
			sacd_media->skip(ck.get_size());
		}
		else if (ck.has_id("DIIN") && !skip_emaster_chunks) {
			int64_t id_diin_end = sacd_media->get_position() + ck.get_size();
			while (sacd_media->get_position() < id_diin_end) {
				if (!(sacd_media->read(&ck, sizeof(ck)) == sizeof(ck))) {
					return false;
				}
				if (ck.has_id("MARK") && ck.get_size() >= sizeof(Marker)) {
					Marker m;
					if (sacd_media->read(&m, sizeof(Marker)) == sizeof(Marker)) {
						m.hours       = hton16(m.hours);
						m.samples     = hton32(m.samples);
						m.offset      = hton32(m.offset);
						m.markType    = hton16(m.markType);
						m.markChannel = hton16(m.markChannel);
						m.TrackFlags  = hton16(m.TrackFlags);
						m.count       = hton32(m.count);
						switch (m.markType) {
						case TrackStart:
							if (start_mark_count > 0) {
								track_t s;
								track_index.push_back(s);
							}
							start_mark_count++;
							if (track_index.size() > 0) {
								track_index[track_index.size() - 1].start_time = MARK_TIME(m);
								track_index[track_index.size() - 1].stop_time2 = (double)frame_count / framerate;
								track_index[track_index.size() - 1].stop_time1 = track_index[track_index.size() - 1].stop_time2;
								if (track_index.size() - 1 > 0) {
									if (track_index[track_index.size() - 2].stop_time2 > track_index[track_index.size() - 1].start_time) {
										track_index[track_index.size() - 2].stop_time2 = track_index[track_index.size() - 1].start_time;
										track_index[track_index.size() - 2].stop_time1 = track_index[track_index.size() - 2].stop_time2;
									}
								}
							}
							break;
						case TrackStop:
							if (track_index.size() > 0) {
								track_index[track_index.size() - 1].stop_time1 = MARK_TIME(m);
							}
							break;
						}
					}
					sacd_media->skip(ck.get_size() - sizeof(Marker));
				}
				else {
					sacd_media->skip(ck.get_size());
				}
				sacd_media->skip(sacd_media->get_position() & 1);
			}
		}
		else if (ck.has_id("ID3 ")) {
			id3_offset = std::min(id3_offset, (uint64_t)sacd_media->get_position() - sizeof(ck));
			id3tags_t t;
			t.index  = id3tags.size();
			t.offset = sacd_media->get_position();
			t.tag_value.resize((uint32_t)ck.get_size());
			sacd_media->read(t.tag_value.data(), t.tag_value.size());
			id3tags.push_back(t);
		}
		else {
			sacd_media->skip(ck.get_size());
		}
		sacd_media->skip(sacd_media->get_position() & 1);
	}
	if (id3tags.size() == 0) {
		if (t_old.tag_value.size() > 0) {
			id3tags.push_back(t_old);
		}
	}
	sacd_media->seek(data_offset);
	set_emaster(false);
	index_id3tags();
	return track_index.size() > 0;
}

bool sacd_dsdiff_t::close() {
	current_track = 0;
	track_index.resize(0);
	id3tags.resize(0);
	dsti_size = 0;
	if (!sacd_media->seek(0)) {
		return false;
	}
	return true;
}

void sacd_dsdiff_t::select_area(area_id_e area_id) {
	track_area = area_id;
}

void sacd_dsdiff_t::set_emaster(bool emaster) {
	is_emaster = emaster;
}

bool sacd_dsdiff_t::select_track(uint32_t _track_index, area_id_e area_id, uint32_t _offset) {
	(void)area_id;
	if (_track_index < track_index.size()) {
		current_track = _track_index;
		double t0 = track_index[current_track].start_time;
		double t1 = is_emaster ? track_index[current_track].stop_time2 : track_index[current_track].stop_time1;
		uint64_t offset = (uint64_t)(t0 * framerate / frame_count * data_size) + _offset;
		uint64_t size = (uint64_t)(t1 * framerate / frame_count * data_size) - offset;
		if (is_dst_encoded) {
			if (dsti_size > 0) {
				if ((uint32_t)(t0 * framerate) < (uint32_t)(dsti_size / sizeof(DSTFrameIndex) - 1)) {
					current_offset = get_dsti_for_frame((uint32_t)(t0 * framerate));
				}
				else {
					current_offset = data_offset + offset;
				}
				if ((uint32_t)(t1 * framerate) < (uint32_t)(dsti_size / sizeof(DSTFrameIndex) - 1)) {
					current_size = get_dsti_for_frame((uint32_t)(t1 * framerate)) - current_offset;
				}
				else {
					current_size = data_size;
				}
			}
			else {
				current_offset = data_offset + offset;
				current_size = size;
			}
		}
		else {
			current_offset = data_offset + (offset / dsd_frame_size) * dsd_frame_size;
			current_size = (size / dsd_frame_size) * dsd_frame_size;
		}
	}
	sacd_media->seek(current_offset);
	return true;
}

bool sacd_dsdiff_t::read_frame(uint8_t* frame_data, size_t* frame_size, frame_type_e* frame_type) {
//static uint64_t s_next_frame = 0;
//if (sacd_media->get_position() != s_next_frame) {
//	console::printf("offset: %d - %d", (uint32_t)sacd_media->get_position(), (uint32_t)s_next_frame);
//}
	if (is_dst_encoded) {
		Chunk ck;
		while ((uint64_t)sacd_media->get_position() < current_offset + current_size && sacd_media->read(&ck, sizeof(ck)) == sizeof(ck)) {
			if (ck.has_id("DSTF") && ck.get_size() <= (uint64_t)*frame_size) {
				if (sacd_media->read(frame_data, (size_t)ck.get_size()) == ck.get_size()) {
					sacd_media->skip(ck.get_size() & 1);
					*frame_size = (size_t)ck.get_size();
					*frame_type = FRAME_DST;
//s_next_frame = sacd_media->get_position();
					return true;
				}
				break;
			}
			else if (ck.has_id("DSTC") && ck.get_size() == 4) {
				uint32_t crc;
				if (ck.get_size() == sizeof(crc)) {
					if (sacd_media->read(&crc, sizeof(crc)) != sizeof(crc)) {
						break;
					}
				}
				else {
					sacd_media->skip(ck.get_size());
					sacd_media->skip(ck.get_size() & 1);
				}
			}
			else {
				sacd_media->seek(sacd_media->get_position() + 1 - (int)sizeof(ck));
			}
		}
	}
	else {
		int64_t position = sacd_media->get_position();
		*frame_size = (size_t)std::min((int64_t)*frame_size, std::max((int64_t)0, (int64_t)(current_offset + current_size) - position));
		if (*frame_size > 0) {
			*frame_size = sacd_media->read(frame_data, *frame_size);
			*frame_size -= *frame_size % channel_count;
			if (*frame_size > 0) {
				*frame_type = FRAME_DSD;
//s_next_frame = sacd_media->get_position();
				return true;
			}
		}
	}
	*frame_type = FRAME_INVALID;
	return false;
}

bool sacd_dsdiff_t::seek(double seconds) {
	uint64_t offset = std::min((uint64_t)(get_size() * seconds / get_duration()), get_size());
	if (is_dst_encoded) {
		if (dsti_size > 0) {
			uint32_t frame = std::min((uint32_t)((track_index[current_track].start_time + seconds) * framerate), frame_count - 1);
			if (frame < (uint32_t)(dsti_size / sizeof(DSTFrameIndex) - 1)) {
				offset = get_dsti_for_frame(frame) - current_offset;
			}
		}
	}
	else {
		offset = (offset / dsd_frame_size) * dsd_frame_size;
	}
	sacd_media->seek(current_offset + offset);
	return true;
}

void sacd_dsdiff_t::get_info(uint32_t _track_index, TagHandler& handler) {
	for (uint32_t i = 0; i < id3tags.size(); i++) {
		if (_track_index == id3tags[i].index) {
			get_id3tags(_track_index, handler);
			break;
		}
	}
}

uint64_t sacd_dsdiff_t::get_dsti_for_frame(uint32_t frame_nr) {
	uint64_t      cur_offset;
	DSTFrameIndex frame_index;
	cur_offset = sacd_media->get_position();
	frame_nr = std::min(frame_nr, (uint32_t)(dsti_size / sizeof(DSTFrameIndex) - 1));
	sacd_media->seek(dsti_offset + frame_nr * sizeof(DSTFrameIndex));
	cur_offset = sacd_media->get_position();
	sacd_media->read(&frame_index, sizeof(DSTFrameIndex));
	sacd_media->seek(cur_offset);
	return hton64(frame_index.offset) - sizeof(Chunk);
}

void sacd_dsdiff_t::get_id3tags(uint32_t _track_index, TagHandler& handler) {
#ifdef ENABLE_ID3TAG
	id3_byte_t* tag_value = static_cast<id3_byte_t*>(id3tags[_track_index].tag_value.data());
	id3_length_t tag_size = static_cast<id3_length_t>(id3tags[_track_index].tag_value.size());
	if (tag_size > 0 && tag_value) {
		struct id3_tag* id3_tag = id3_tag_parse(tag_value, tag_size);
		if (id3_tag != nullptr) {
			if ((mode & MODE_SINGLE_TRACK) == MODE_SINGLE_TRACK) {
				//scan_id3_tag(id3_tag, handler, handler_ctx);
			}
			else {
				scan_id3_tag(id3_tag, handler);
			}
			id3_tag_delete(id3_tag);
		}
	}
#endif
}

void sacd_dsdiff_t::index_id3tags() {
#ifdef ENABLE_ID3TAG
	/*
	for (uint32_t i = 0; i < id3tags.size(); i++) {
		if (id3tags[i].size > 0) {
			id3_byte_t* dsdid3 = (id3_byte_t*)&id3tags[i].data[0];
			const id3_length_t count = id3tags[i].size;
			struct id3_tag* id3_tag = id3_tag_parse(dsdid3, count);
			if (id3_tag != nullptr) {
				const struct id3_frame* frame;
				frame = id3_tag_findframe(id3_tag, ID3_FRAME_TRACK, 0);
				if (frame != nullptr) {
					const id3_field* field = id3_frame_field(frame, 0);
					if (field != nullptr) {
						long tracknumber = id3_field_getint(field);
						id3tags[i].index = tracknumber;
					}
				}
				id3_tag_delete(id3_tag);
			}
		}
	}
	*/
#endif
}

