/*
Copyright 2019-2022, James R.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdlib.h>
#include <string.h>

#include "apng.h"

#define APNG_INFO_acTL 0x20000U

#define APNG_WROTE_acTL 0x10000U

struct apng_info_def
{
	png_uint_32 mode;
	png_uint_32 valid;

	png_uint_32 num_frames;
	png_uint_32 num_plays;

	long start_acTL;/* acTL is written here */

	png_flush_ptr output_flush_fn;
	apng_seek_ptr output_seek_fn;
	apng_tell_ptr output_tell_fn;

	apng_set_acTL_ptr set_acTL_fn;
};

/* PROTOS (FUCK COMPILER) */
void   apng_seek  (png_structp, apng_const_infop, size_t);
size_t apng_tell  (png_structp, apng_const_infop);
#ifdef PNG_WRITE_FLUSH_SUPPORTED
void   apng_flush (png_structp, apng_infop);
#ifdef PNG_STDIO_SUPPORTED
void   apng_default_flush (png_structp);
#endif/* PNG_STDIO_SUPPORTED */
#endif/* PNG_WRITE_FLUSH_SUPPORTED */
#ifdef PNG_STDIO_SUPPORTED
void   apng_default_seek  (png_structp, size_t);
size_t apng_default_tell  (png_structp);
#endif/* PNG_STDIO_SUPPORTED */
void   apng_write_IEND (png_structp);
void   apng_write_acTL (png_structp, png_uint_32, png_uint_32);
#ifndef PNG_WRITE_APNG_SUPPORTED
png_uint_32 apng_set_acTL_dummy (png_structp, png_infop,
		png_uint_32, png_uint_32);
#endif/* PNG_WRITE_APNG_SUPPORTED */

apng_infop
apng_create_info_struct (png_structp pngp)
{
	apng_infop ainfop;
	(void)pngp;
	if (( ainfop = calloc(1, sizeof (apng_info)) ))
	{
		apng_set_write_fn(pngp, ainfop, 0, 0, 0, 0, 0);
		apng_set_set_acTL_fn(pngp, ainfop, 0);
	}
	return ainfop;
}

void
apng_destroy_info_struct (png_structp pngp, apng_infopp ainfopp)
{
	(void)pngp;
	if (!( pngp && ainfopp ))
		return;

	free((*ainfopp));
}

void
apng_seek (png_structp pngp, apng_const_infop ainfop, size_t l)
{
	(*(ainfop->output_seek_fn))(pngp, l);
}

size_t
apng_tell (png_structp pngp, apng_const_infop ainfop)
{
	return (*(ainfop->output_tell_fn))(pngp);
}

#ifdef PNG_WRITE_FLUSH_SUPPORTED
void
apng_flush (png_structp pngp, apng_infop ainfop)
{
	if (ainfop->output_flush_fn)
		(*(ainfop->output_flush_fn))(pngp);
}

#ifdef PNG_STDIO_SUPPORTED
void
apng_default_flush (png_structp pngp)
{
	if (!( pngp ))
		return;

	fflush((png_FILE_p)png_get_io_ptr(pngp));
}
#endif/* PNG_STDIO_SUPPORTED */
#endif/* PNG_WRITE_FLUSH_SUPPORTED */

#ifdef PNG_STDIO_SUPPORTED
void
apng_default_seek (png_structp pngp, size_t l)
{
	if (!( pngp ))
		return;

	if (fseek((png_FILE_p)png_get_io_ptr(pngp), (long)l, SEEK_SET) == -1)
		png_error(pngp, "Seek Error");
}

size_t
apng_default_tell (png_structp pngp)
{
	long l;

	if (!( pngp ))
	{
		png_error(pngp, "Call to apng_default_tell with NULL pngp failed");
	}

	if (( l = ftell((png_FILE_p)png_get_io_ptr(pngp)) ) == -1)
		png_error(pngp, "Tell Error");

	return (size_t)l;
}
#endif/* PNG_STDIO_SUPPORTED */

void
apng_set_write_fn (png_structp pngp, apng_infop ainfop, png_voidp iop,
		png_rw_ptr write_f, png_flush_ptr flush_f,
		apng_seek_ptr seek_f, apng_tell_ptr tell_f)
{
	if (!( pngp && ainfop ))
		return;

	png_set_write_fn(pngp, iop, write_f, flush_f);

#ifdef PNG_WRITE_FLUSH_SUPPORTED
#ifdef PNG_STDIO_SUPPORTED
	if (!flush_f)
		ainfop->output_flush_fn = &apng_default_flush;
	else
#endif/* PNG_STDIO_SUPPORTED */
		ainfop->output_flush_fn = flush_f;
#endif/* PNG_WRITE_FLUSH_SUPPORTED */
#ifdef PNG_STDIO_SUPPORTED
	if (!seek_f)
		ainfop->output_seek_fn = &apng_default_seek;
	else
#endif/* PNG_STDIO_SUPPORTED */
		ainfop->output_seek_fn  = seek_f;
#ifdef PNG_STDIO_SUPPORTED
	if (!seek_f)
		ainfop->output_tell_fn  = apng_default_tell;
	else
#endif/* PNG_STDIO_SUPPORTED */
		ainfop->output_tell_fn  = tell_f;
}

void
apng_write_IEND (png_structp pngp)
{
	png_byte chunkc[] = "IEND";
	png_write_chunk(pngp, chunkc, 0, 0);
}

void
apng_write_acTL (png_structp pngp, png_uint_32 frames, png_uint_32 plays)
{
	png_byte chunkc[] = "acTL";
	png_byte buf[8];
	png_save_uint_32(buf, frames);
	png_save_uint_32(buf + 4, plays);
	png_write_chunk(pngp, chunkc, buf, 8);
}

png_uint_32
apng_set_acTL (png_structp pngp, png_infop infop, apng_infop ainfop,
		png_uint_32 frames, png_uint_32 plays)
{
	(void)pngp;
	(void)infop;
	if (!( pngp && infop && ainfop ))
		return 0;

	ainfop->num_frames = frames;
	ainfop->num_plays  = plays;

	ainfop->valid |= APNG_INFO_acTL;

	return 1;
}

void
apng_write_info_before_PLTE (png_structp pngp, png_infop infop,
		apng_infop ainfop)
{
	if (!( pngp && infop && ainfop ))
		return;

	png_write_info_before_PLTE(pngp, infop);

	if (( ainfop->valid & APNG_INFO_acTL )&&!( ainfop->mode & APNG_WROTE_acTL ))
	{
		ainfop->start_acTL = apng_tell(pngp, ainfop);

		apng_write_acTL(pngp, 0, 0);
		/* modified for runtime dynamic linking */
		(*(ainfop->set_acTL_fn))(pngp, infop, PNG_UINT_31_MAX, 0);

		ainfop->mode |= APNG_WROTE_acTL;
	}
}

void
apng_write_info (png_structp pngp, png_infop infop,
		apng_infop ainfop)
{
	apng_write_info_before_PLTE(pngp, infop, ainfop);
	png_write_info(pngp, infop);
}

void
apng_write_end (png_structp pngp, png_infop infop, apng_infop ainfop)
{
	(void)infop;
	apng_write_IEND(pngp);
	apng_seek(pngp, ainfop, ainfop->start_acTL);
	apng_write_acTL(pngp, ainfop->num_frames, ainfop->num_plays);

#ifdef PNG_WRITE_FLUSH_SUPPORTED
#ifdef PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED
	apng_flush(pngp, infop);
#endif/* PNG_WRITE_FLUSH_SUPPORTED */
#endif/* PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED */
}

#ifndef PNG_WRITE_APNG_SUPPORTED
png_uint_32
apng_set_acTL_dummy (png_structp pngp, png_infop infop,
		png_uint_32 frames, png_uint_32 plays)
{
	(void)pngp;
	(void)infop;
	(void)frames;
	(void)plays;
	return 0;
}
#endif/* PNG_WRITE_APNG_SUPPORTED */

/* Dynamic runtime linking capable! (Hopefully.) */
void
apng_set_set_acTL_fn (png_structp pngp, apng_infop ainfop,
		apng_set_acTL_ptr set_acTL_f)
{
	(void)pngp;
	if (!ainfop->set_acTL_fn)
#ifndef PNG_WRITE_APNG_SUPPORTED
		ainfop->set_acTL_fn = &apng_set_acTL_dummy;
#else
		ainfop->set_acTL_fn = &png_set_acTL;
#endif/* PNG_WRITE_APNG_SUPPORTED */
	else
		ainfop->set_acTL_fn = set_acTL_f;
}
