/* Released under MIT License.
 * Copyright (c) 2023-2025 Ladislav Bartos
 *
 * Implementation of functions for jumping in the xdr trajectory files.
 * This file is NOT part of the `xdrfile` library!
 */

#include "xdrfile_jump.h"

/*** PRIVATE FUNCTIONS  ***/

/*!
 * \brief Get the number of bytes required to jump to the next frame in an xtc file.
 *
 * \param 	xdp				pointer to the XDRFILE
 * \param	target_time		time that is to be reached (in ps)
 * \param	time_precision	smallest distinguishable unit of time
 *
 * \return	-1 in case of an error
 * 			0 if the target time has been reached or crossed
 * 			number of bytes to reach the start of the next frame
 *
 * \note	This function was written for the `groan_rs` library.
 */
static int xtc_get_jump_info(XDRFILE *xdp, float target_time, float time_precision)
{
	// read magic number
	int magic_number = 0;
	if (xdrfile_read_int(&magic_number, 1, xdp) != 1)
		return -1;

	// check that the magic number is correct
	if (magic_number != 1995)
		return -1;

	// jump to time information and read it
	if (xdr_jump(xdp, 8) != 0)
		return -1;
	float time = 0.0;
	if (xdrfile_read_float(&time, 1, xdp) != 1)
		return -1;

	// check whether the time is higher than or equal to the target time
	if (time >= target_time - time_precision)
		return 0;

	// get the number of bytes to the next frame
	if (xdr_jump(xdp, 72) != 0)
		return -1;
	int size = 0;
	if (xdrfile_read_int(&size, 1, xdp) != 1)
		return -1;
	// add padding
	if (size % 4 != 0)
	{
		size += 4 - (size % 4);
	}

	return size;
}

/*!
 * \brief Get the number of bytes required to jump to the next frame in an trr file.
 *
 * \param 	xdp				pointer to the XDRFILE
 * \param	target_time		time that is to be reached (in ps)
 * \param	time_precision	smallest distinguishable unit of time
 * \param   header_size     size of the header in bytes
 *                          (set by the function if `target_time` is reached)
 *
 * \return	-1 in case of an error
 * 			0 if the target time has been reached or crossed
 * 			number of bytes to reach the start of the next frame
 *
 * \note	This function was written for the `groan_rs` library.
 */
static int trr_get_jump_info(XDRFILE *xdp, float target_time, float time_precision, long *header_size)
{
	// read the entire header
	t_trnheader header = {0};
	if (do_trnheader(xdp, 1, &header) != exdrOK)
		return -1;

	// check time
	if (header.td >= target_time - time_precision)
	{
		*header_size = 84;
		// if the file is double precision, we add some more bytes
		if (header.bDouble)
			*header_size += 8;
		return 0;
	}

	// size of the frame should be the sum of the sizes of the individual segments
	// not all sizes are probably currently used; ignoring them could increase the speed of jumping
	// but it might break the library for some trr files...
	int size = header.ir_size + header.e_size + header.box_size + header.vir_size + header.pres_size +
			   header.top_size + header.sym_size + header.x_size + header.v_size + header.f_size;

	return size;
}

/*** PUBLIC FUNCTIONS  ***/

int xtc_jump_to_start(XDRFILE *xdp, float target_time)
{
	int jump = 0;
	// repeatedly jump over frames until target frame is reached
	while ((jump = xtc_get_jump_info(xdp, target_time, 0.001)) > 0)
	{
		if (xdr_jump(xdp, jump) != 0)
			return 1;
	}

	if (jump == -1)
		return 1;

	// jump back to the start of the frame
	if (xdr_jump(xdp, -16) != 0)
		return 1;

	return 0;
}

int trr_jump_to_start(XDRFILE *xdp, float target_time)
{
	int jump = 0;
	// set when target is reached and we need to jump back to the start of the frame
	long back = 0;
	// repeatedly jump over frames until target frame is reached
	while ((jump = trr_get_jump_info(xdp, target_time, 0.001, &back)) > 0)
	{
		if (xdr_jump(xdp, jump) != 0)
			return 1;
	}

	if (jump == -1)
		return 1;

	// jump back to the start of the frame
	if (xdr_jump(xdp, -back) != 0)
		return 1;

	return 0;
}

int xtc_skip_frame(XDRFILE *xdp)
{
	// read magic number
	int magic_number = 0;
	if (xdrfile_read_int(&magic_number, 1, xdp) != 1)
	{
		// assuming this only fails if we have reached the end of the file
		return 2;
	}

	// check that the magic number is correct (check the validity of the frame)
	if (magic_number != 1995)
		return 1;

	// get the number of bytes to the next frame
	if (xdr_jump(xdp, 84) != 0)
		return 1;
	int size = 0;
	if (xdrfile_read_int(&size, 1, xdp) != 1)
		return 1;
	// add padding
	if (size % 4 != 0)
	{
		size += 4 - (size % 4);
	}

	// this should only fail if we have reached the end of the file
	// (but does not have to fail)
	if (xdr_jump(xdp, size) != 0)
		return 2;

	return 0;
}

int xtc_skip_frame_with_time(XDRFILE *xdp, float *time)
{
	// read magic number
	int magic_number = 0;
	if (xdrfile_read_int(&magic_number, 1, xdp) != 1)
		return 2;

	// check that the magic number is correct
	if (magic_number != 1995)
		return 1;

	// jump to time information and read it
	if (xdr_jump(xdp, 8) != 0)
		return 1;
	if (xdrfile_read_float(time, 1, xdp) != 1)
		return 1;

	// get the number of bytes to the next frame
	if (xdr_jump(xdp, 72) != 0)
		return 1;
	int size = 0;
	if (xdrfile_read_int(&size, 1, xdp) != 1)
		return 1;
	// add padding
	if (size % 4 != 0)
	{
		size += 4 - (size % 4);
	}

	// this should only fail if we have reached the end of the file
	// (but does not have to fail)
	if (xdr_jump(xdp, size) != 0)
		return 2;

	return 0;
}

int trr_skip_frame(XDRFILE *xdp)
{
	// read the header of the frame
	t_trnheader header = {0};
	// assuming this only fails if we have reached the end of the file
	if (do_trnheader(xdp, 1, &header) != exdrOK)
		return 2;

	// size of the frame should be the sum of the sizes of the individual segments
	int size = header.ir_size + header.e_size + header.box_size + header.vir_size + header.pres_size +
			   header.top_size + header.sym_size + header.x_size + header.v_size + header.f_size;

	// this should only fail if we have reached the end of the file
	// (but does not have to fail)
	if (xdr_jump(xdp, size) != 0)
		return 2;

	return 0;
}

int trr_skip_frame_with_time(XDRFILE *xdp, float *time)
{
	// read the header of the frame
	t_trnheader header = {0};
	// assuming this only fails if we have reached the end of the file
	if (do_trnheader(xdp, 1, &header) != exdrOK)
		return 2;

	// store simulation time
	*time = header.tf;

	// size of the frame should be the sum of the sizes of the individual segments
	int size = header.ir_size + header.e_size + header.box_size + header.vir_size + header.pres_size +
			   header.top_size + header.sym_size + header.x_size + header.v_size + header.f_size;

	// this should only fail if we have reached the end of the file
	// (but does not have to fail)
	if (xdr_jump(xdp, size) != 0)
		return 2;

	return 0;
}