/*
* SACD Decoder plugin
* Copyright (c) 2011-2022 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 "dop_converter.h"

constexpr t_uint8 DoP_MARKER[2] = { 0x05, 0xFA };

auto DOP_AUDIO_SAMPLE_TO_INT32 = [](auto value) {
	return (int)(value * 0x80000000);
};

dop_converter_t::dop_converter_t() {
	m_dop_marker_n.set_size(0);
	m_dop_data.set_size(0);
}

void dop_converter_t::set_inp_spec(const audio_chunk::spec_t& p_spec) {
	m_inp_spec = p_spec;
}

void dop_converter_t::set_out_spec(const audio_chunk::spec_t& p_spec) {
	m_out_spec = p_spec;
}

bool dop_converter_t::is_dop(const audio_chunk& p_chunk) {
	bool isDoP1 = true, isDoP2 = true;
	auto chunk_data = p_chunk.get_data();
	auto chunk_channels = p_chunk.get_channels();
	auto chunk_samples = p_chunk.get_sample_count();
	for (auto sample = 0u; sample < min(chunk_samples, 32) - 1; sample += 2) {
		uint8_t marker0 = DOP_AUDIO_SAMPLE_TO_INT32(chunk_data[(sample + 0) * chunk_channels]) >> 24;
		uint8_t marker1 = DOP_AUDIO_SAMPLE_TO_INT32(chunk_data[(sample + 1) * chunk_channels]) >> 24;
		if (!(marker0 == DoP_MARKER[0] && marker1 == DoP_MARKER[1])) {
			isDoP1 = false;
		}
		if (!(marker0 == DoP_MARKER[1] && marker1 == DoP_MARKER[0])) {
			isDoP2 = false;
		}
	}
	return isDoP1 || isDoP2;
}

void dop_converter_t::dop_to_dsd(const audio_chunk& p_dop_chunk, array_t<t_uint8>& p_dsd_data) {
	bool isDoP1 = true, isDoP2 = true;
	auto dop_data = p_dop_chunk.get_data();
	auto dop_channels = p_dop_chunk.get_channels();
	auto dop_samples = p_dop_chunk.get_sample_count();
	p_dsd_data.set_size(2 * dop_channels * dop_samples);
	for (auto sample = 0u; sample < dop_samples; sample++) {
		for (auto ch = 0u; ch < dop_channels; ch++) {
			auto value = DOP_AUDIO_SAMPLE_TO_INT32(dop_data[sample * dop_channels + ch]);
			p_dsd_data[(2 * sample + 0) * dop_channels + ch] = value >> 16;
			p_dsd_data[(2 * sample + 1) * dop_channels + ch] = value >> 8;
		}
	}
}

void dop_converter_t::dsd_to_dop(const t_uint8* p_dsd_data, t_size p_dsd_samples, audio_chunk& p_dop_chunk) {
	unsigned out_samplerate = m_inp_spec.sampleRate / 16;
	auto inp_channels = m_inp_spec.chanCount;
	auto out_channels = m_out_spec.chanCount;
	auto out_samples = p_dsd_samples / 2;
	auto out_bytes = 3 * out_samples * out_channels;
	m_dop_data.set_size(out_bytes);
	auto dop_data = m_dop_data.get_ptr();
	auto o = 0;
	if (m_dop_marker_n.get_size() != out_channels) {
		m_dop_marker_n.set_size(out_channels);
		for (auto ch = 0u; ch < m_dop_marker_n.get_size(); ch++) {
			m_dop_marker_n[ch] = 0;
		}
	}
	for (auto sample = 0u; sample < out_samples; sample++) {
		for (auto ch = 0u; ch < out_channels; ch++) {
			auto b_msb = p_dsd_data[(2 * sample + 0) * inp_channels + ch % inp_channels];
			auto b_lsb = p_dsd_data[(2 * sample + 1) * inp_channels + ch % inp_channels];
			dop_data[o + 0] = b_lsb;
			dop_data[o + 1] = b_msb;
			dop_data[o + 2] = DoP_MARKER[m_dop_marker_n[ch]];
			o += 3;
			m_dop_marker_n[ch] = ++m_dop_marker_n[ch] & 1;
		}
	}
	p_dop_chunk.set_data_fixedpoint(dop_data, out_bytes, out_samplerate, out_channels, 24, m_out_spec.chanMask);
}

void dop_converter_t::set_silence(audio_chunk& p_dop_chunk) {
	constexpr unsigned char DSD_SILENCE_BYTE = 0x69;
	auto spec = p_dop_chunk.get_spec();
	auto dop_samples = p_dop_chunk.get_sample_count();
	auto dop_bytes = 3 * dop_samples * spec.chanCount;
	m_dop_data.set_size(dop_bytes);
	auto dop_data = m_dop_data.get_ptr();
	auto o = 0;
	if (m_dop_marker_n.get_size() != spec.chanCount) {
		m_dop_marker_n.set_size(spec.chanCount);
		for (auto ch = 0u; ch < m_dop_marker_n.get_size(); ch++) {
			m_dop_marker_n[ch] = 0;
		}
	}
	for (auto sample = 0u; sample < dop_samples; sample++) {
		for (auto ch = 0u; ch < spec.chanCount; ch++) {
			dop_data[o + 0] = DSD_SILENCE_BYTE;
			dop_data[o + 1] = DSD_SILENCE_BYTE;
			dop_data[o + 2] = DoP_MARKER[m_dop_marker_n[ch]];
			o += 3;
			m_dop_marker_n[ch] = ++m_dop_marker_n[ch] & 1;
		}
	}
	p_dop_chunk.set_data_fixedpoint(dop_data, dop_bytes, spec.sampleRate, spec.chanCount, 24, spec.chanMask);
}
