/*
 *    Copyright (C) 2016-2021 Grok Image Compression Inc.
 *
 *    This source code is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This source code 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 Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *    This source code incorporates work covered by the BSD 2-clause license.
 *    Please see the LICENSE file in the root directory for details.
 *
 */

#include "grk_includes.h"

namespace grk
{
CodingParams::CodingParams()
	: rsiz(0), pcap(0), tx0(0), ty0(0), t_width(0), t_height(0), num_comments(0), t_grid_width(0),
	  t_grid_height(0), ppm_marker(nullptr), tcps(nullptr), tlm_markers(nullptr),
	  plm_markers(nullptr)
{
	memset(&m_coding_params, 0, sizeof(m_coding_params));
}
CodingParams::~CodingParams()
{
	delete[] tcps;
	for(uint32_t i = 0; i < num_comments; ++i)
		delete[]((uint8_t*)comment[i]);
	num_comments = 0;
	delete plm_markers;
	delete tlm_markers;
	delete ppm_marker;
}

// (canvas coordinates)
grkRectU32 CodingParams::getTileBounds(const GrkImage* p_image, uint32_t tile_x,
									   uint32_t tile_y) const
{
	grkRectU32 rc;

	/* find extent of tile */
	assert(tx0 + (uint64_t)tile_x * t_width < UINT_MAX);
	rc.x0 = std::max<uint32_t>(tx0 + tile_x * t_width, p_image->x0);
	assert(ty0 + (uint64_t)tile_y * t_height < UINT_MAX);
	rc.y0 = std::max<uint32_t>(ty0 + tile_y * t_height, p_image->y0);

	uint64_t temp = tx0 + (uint64_t)(tile_x + 1) * t_width;
	rc.x1 = (temp > p_image->x1) ? p_image->x1 : (uint32_t)temp;

	temp = ty0 + (uint64_t)(tile_y + 1) * t_height;
	rc.y1 = (temp > p_image->y1) ? p_image->y1 : (uint32_t)temp;

	return rc;
}

TileCodingParams::TileCodingParams()
	: csty(0), prg(GRK_PROG_UNKNOWN), numlayers(0), numLayersToDecompress(0), mct(0), numpocs(0),
	  ppt_markers_count(0), ppt_markers(nullptr), ppt_data(nullptr), ppt_buffer(nullptr),
	  ppt_data_size(0), ppt_len(0), main_qcd_qntsty(0), main_qcd_numStepSizes(0), tccps(nullptr),
	  m_tilePartIndexCounter(-1), m_numTileParts(0), m_compressedTileData(nullptr),
	  mct_norms(nullptr), m_mct_decoding_matrix(nullptr), m_mct_coding_matrix(nullptr),
	  m_mct_records(nullptr), m_nb_mct_records(0), m_nb_max_mct_records(0), m_mcc_records(nullptr),
	  m_nb_mcc_records(0), m_nb_max_mcc_records(0), cod(false), ppt(false), m_qcd(nullptr),
	  m_ht(false)
{
	for(auto i = 0; i < maxCompressLayersGRK; ++i)
		rates[i] = 0.0;
	for(auto i = 0; i < maxCompressLayersGRK; ++i)
		distortion[i] = 0;
	for(auto i = 0; i < 32; ++i)
		memset(progressionOrderChange + i, 0, sizeof(grk_progression));
}
TileCodingParams::~TileCodingParams()
{
	if(ppt_markers != nullptr)
	{
		for(uint32_t i = 0U; i < ppt_markers_count; ++i)
			grkFree(ppt_markers[i].m_data);
		grkFree(ppt_markers);
	}

	delete[] ppt_buffer;
	delete[] tccps;
	grkFree(m_mct_coding_matrix);
	grkFree(m_mct_decoding_matrix);
	if(m_mcc_records)
		grkFree(m_mcc_records);
	if(m_mct_records)
	{
		auto mct_data = m_mct_records;
		for(uint32_t i = 0; i < m_nb_mct_records; ++i)
		{
			grkFree(mct_data->m_data);
			++mct_data;
		}
		grkFree(m_mct_records);
	}
	grkFree(mct_norms);
	delete m_compressedTileData;
	delete m_qcd;
}

bool TileCodingParams::copy(const TileCodingParams* rhs, const GrkImage* image)
{
	uint32_t tccp_size = image->numcomps * (uint32_t)sizeof(TileComponentCodingParams);
	uint32_t mct_size = (uint32_t)image->numcomps * image->numcomps * (uint32_t)sizeof(float);

	// cache tccps
	auto cachedTccps = tccps;
	auto cachedQcd = m_qcd;
	*this = *rhs;
	/* Initialize some values of the current tile coding parameters*/
	cod = false;
	ppt = false;
	ppt_data = nullptr;
	/* Remove memory not owned by this tile in case of early error return. */
	m_mct_decoding_matrix = nullptr;
	m_nb_max_mct_records = 0;
	m_mct_records = nullptr;
	m_nb_max_mcc_records = 0;
	m_mcc_records = nullptr;
	// restore tccps
	tccps = cachedTccps;
	m_qcd = cachedQcd;

	/* Get the mct_decoding_matrix of the dflt_tile_cp and copy them into the current tile cp*/
	if(rhs->m_mct_decoding_matrix)
	{
		m_mct_decoding_matrix = (float*)grkMalloc(mct_size);
		if(!m_mct_decoding_matrix)
			return false;
		memcpy(m_mct_decoding_matrix, rhs->m_mct_decoding_matrix, mct_size);
	}

	/* Get the mct_record of the dflt_tile_cp and copy them into the current tile cp*/
	uint32_t mct_records_size = rhs->m_nb_max_mct_records * (uint32_t)sizeof(grk_mct_data);
	m_mct_records = (grk_mct_data*)grkMalloc(mct_records_size);
	if(!m_mct_records)
		return false;
	memcpy(m_mct_records, rhs->m_mct_records, mct_records_size);

	/* Copy the mct record data from dflt_tile_cp to the current tile*/

	for(uint32_t j = 0; j < rhs->m_nb_mct_records; ++j)
	{
		auto src_mct_rec = rhs->m_mct_records + j;
		auto dest_mct_rec = m_mct_records + j;
		if(src_mct_rec->m_data)
		{
			dest_mct_rec->m_data = (uint8_t*)grkMalloc(src_mct_rec->m_data_size);
			if(!dest_mct_rec->m_data)
				return false;
			memcpy(dest_mct_rec->m_data, src_mct_rec->m_data, src_mct_rec->m_data_size);
		}
		/* Update with each pass to free exactly what has been allocated on early return. */
		m_nb_max_mct_records += 1;
	}

	/* Get the mcc_record of the dflt_tile_cp and copy them into the current tile cp*/
	uint32_t mcc_records_size =
		rhs->m_nb_max_mcc_records * (uint32_t)sizeof(grk_simple_mcc_decorrelation_data);
	m_mcc_records = (grk_simple_mcc_decorrelation_data*)grkMalloc(mcc_records_size);
	if(!m_mcc_records)
		return false;
	memcpy(m_mcc_records, rhs->m_mcc_records, mcc_records_size);
	m_nb_max_mcc_records = rhs->m_nb_max_mcc_records;

	/* Copy the mcc record data from dflt_tile_cp to the current tile*/
	for(uint32_t j = 0; j < rhs->m_nb_max_mcc_records; ++j)
	{
		auto src_mcc_rec = rhs->m_mcc_records + j;
		auto dest_mcc_rec = m_mcc_records + j;
		if(src_mcc_rec->m_decorrelation_array)
		{
			uint32_t offset = (uint32_t)(src_mcc_rec->m_decorrelation_array - rhs->m_mct_records);
			dest_mcc_rec->m_decorrelation_array = m_mct_records + offset;
		}
		if(src_mcc_rec->m_offset_array)
		{
			uint32_t offset = (uint32_t)(src_mcc_rec->m_offset_array - rhs->m_mct_records);
			dest_mcc_rec->m_offset_array = m_mct_records + offset;
		}
	}
	memcpy(tccps, rhs->tccps, tccp_size);

	return true;
}

void TileCodingParams::setIsHT(bool ht, bool reversible, uint8_t guardBits)
{
	m_ht = ht;
	if(!m_qcd)
		m_qcd = T1Factory::makeQuantizer(ht, reversible, guardBits);
}

bool TileCodingParams::isHT(void)
{
	return m_ht;
}
uint32_t TileCodingParams::getNumProgressions()
{
	return numpocs + 1;
}
bool TileCodingParams::hasPoc(void)
{
	return numpocs > 0;
}
TileComponentCodingParams::TileComponentCodingParams()
	: csty(0), numresolutions(0), cblkw(0), cblkh(0), cblk_sty(0), qmfbid(0),
	  quantizationMarkerSet(false), fromQCC(false), fromTileHeader(false), qntsty(0),
	  numStepSizes(0), numgbits(0), roishift(0), m_dc_level_shift(0)
{
	for(uint32_t i = 0; i < GRK_J2K_MAXRLVLS; ++i)
	{
		precinctWidthExp[i] = 0;
		precinctHeightExp[i] = 0;
	}
}

DecompressorState::DecompressorState()
	: m_default_tcp(nullptr), m_start_tile_x_index(0), m_start_tile_y_index(0),
	  m_end_tile_x_index(0), m_end_tile_y_index(0), lastSotReadPosition(0),
	  lastTilePartInCodeStream(false), lastTilePartWasRead(false), skipTileData(false),
	  m_state(DECOMPRESS_STATE_NONE)
{}

uint16_t DecompressorState::getState(void)
{
	return m_state;
}
void DecompressorState::setState(uint16_t state)
{
	m_state = state;
}
void DecompressorState::orState(uint16_t state)
{
	m_state |= state;
}
void DecompressorState::andState(uint16_t state)
{
	m_state &= state;
}
bool DecompressorState::findNextTile(CodeStreamDecompress* codeStream)
{
	auto stream = codeStream->getStream();
	lastTilePartWasRead = false;
	andState((uint16_t)(~DECOMPRESS_STATE_DATA));

	// if there is no EOC marker and there is also no data left, then simply return true
	if(stream->numBytesLeft() == 0 && getState() == DECOMPRESS_STATE_NO_EOC)
		return true;

	// if EOC marker has not been read yet, then try to read the next marker
	// (should be EOC or SOT)
	if(getState() != DECOMPRESS_STATE_EOC)
	{
		try
		{
			if(!codeStream->readMarker())
			{
				GRK_WARN("findNextTile: Not enough data to read another marker.\n"
						 "Tile may be truncated.");
				return true;
			}
		}
		catch(InvalidMarkerException& ume)
		{
			GRK_UNUSED(ume);
			setState(DECOMPRESS_STATE_NO_EOC);
			GRK_WARN("findNextTile: expected EOC or SOT "
					 "but found invalid marker 0x%x.",
					 codeStream->getCurrentMarker());
			throw DecodeUnknownMarkerAtEndOfTileException();
		}

		switch(codeStream->getCurrentMarker())
		{
			// we found the EOC marker - set state accordingly and return true;
			// we can ignore all data after EOC
			case J2K_MS_EOC:
				setState(DECOMPRESS_STATE_EOC);
				return true;
				break;
			// start of another tile
			case J2K_MS_SOT:
				return true;
				break;
			default: {
				auto bytesLeft = stream->numBytesLeft();
				setState(DECOMPRESS_STATE_NO_EOC);
				GRK_WARN("findNextTile: expected EOC or SOT "
						 "but found marker 0x%x.\nIgnoring %d bytes "
						 "remaining in the stream.",
						 codeStream->getCurrentMarker(), bytesLeft + 2);
				throw DecodeUnknownMarkerAtEndOfTileException();
			}
			break;
		}
	}

	return true;
}

} // namespace grk
