/* A byte source/sink .. it can be a pipe, file descriptor, memory area, 
 * socket, node.js stream, etc.
 * 
 * J.Cupitt, 19/6/14
 *
 * 26/11/20
 * 	- use _setmode() on win to force binary write for previously opened
 * 	  descriptors
 */

/*

    This file is part of VIPS.
    
    VIPS 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 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 this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/*
#define VIPS_DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /*HAVE_UNISTD_H*/
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <vips/vips.h>

#ifdef G_OS_WIN32
#include <io.h>
#endif /*G_OS_WIN32*/

#include <vips/debug.h>
#include <vips/internal.h>

/* libtiff needs to be able to seek and read back output files, unfortunately,
 * so we must open read-write.
 */
#define MODE_READWRITE CLOEXEC (BINARYIZE (O_RDWR | O_CREAT | O_TRUNC))

G_DEFINE_TYPE( VipsTarget, vips_target, VIPS_TYPE_CONNECTION );

static void
vips_target_finalize( GObject *gobject )
{
	VipsTarget *target = VIPS_TARGET( gobject );

	VIPS_DEBUG_MSG( "vips_target_finalize:\n" );

	if( target->memory_buffer ) {
		g_string_free( target->memory_buffer, TRUE ); 
		target->memory_buffer = NULL;
	}

	if( target->blob ) { 
		vips_area_unref( VIPS_AREA( target->blob ) ); 
		target->blob = NULL;
	}

	if( target->delete_on_close &&
		target->delete_on_close_filename )
		g_unlink( target->delete_on_close_filename );

	VIPS_FREE( target->delete_on_close_filename );

	G_OBJECT_CLASS( vips_target_parent_class )->finalize( gobject );
}

static int
vips_target_build( VipsObject *object )
{
	VipsConnection *connection = VIPS_CONNECTION( object );
	VipsTarget *target = VIPS_TARGET( object );

	VIPS_DEBUG_MSG( "vips_target_build: %p\n", connection );

	if( VIPS_OBJECT_CLASS( vips_target_parent_class )->build( object ) )
		return( -1 );

	if( vips_object_argument_isset( object, "filename" ) &&
		vips_object_argument_isset( object, "descriptor" ) ) { 
		vips_error( vips_connection_nick( connection ), 
			"%s", _( "don't set 'filename' and 'descriptor'" ) ); 
		return( -1 ); 
	}

	if( connection->filename ) { 
		const char *filename = connection->filename;

		int fd;

		/* 0644 is rw user, r group and other.
		 */
		if( (fd = vips_tracked_open( filename, 
			MODE_READWRITE, 0644 )) == -1 ) {
			vips_error_system( errno, 
				vips_connection_nick( connection ), 
				"%s", _( "unable to open for write" ) ); 
			return( -1 ); 
		}

		connection->tracked_descriptor = fd;
		connection->descriptor = fd;
	}
	else if( vips_object_argument_isset( object, "descriptor" ) ) {
		connection->descriptor = dup( connection->descriptor );
		connection->close_descriptor = connection->descriptor;

#ifdef G_OS_WIN32
		/* Windows will create eg. stdin and stdout in text mode.
		 * We always write in binary mode.
		 */
		_setmode( connection->descriptor, _O_BINARY );
#endif /*G_OS_WIN32*/
	}
	else if( target->memory ) 
		target->memory_buffer = 
			g_string_sized_new( VIPS_TARGET_BUFFER_SIZE );

	return( 0 );
}

static gint64 
vips_target_write_real( VipsTarget *target, const void *data, size_t length )
{
	VipsConnection *connection = VIPS_CONNECTION( target );

	gint64 result;

	VIPS_DEBUG_MSG( "vips_target_write_real: %zd bytes\n", length );

	if( target->memory_buffer ) {
		VIPS_DEBUG_MSG( "vips_target_write_real: to position %zd\n", 
			target->position );

		g_string_overwrite_len( target->memory_buffer, target->position,
			data, length );
		target->position += length;
		result = length;
	}
	else
		result = write( connection->descriptor, data, length );

	return( result );
}

static off_t 
vips_target_seek_real( VipsTarget *target, off_t offset, int whence )
{
	VipsConnection *connection = VIPS_CONNECTION( target );
	const char *nick = vips_connection_nick( connection );

	off_t new_position;

	VIPS_DEBUG_MSG( "vips_target_seek_real: offset = %ld, whence = %d\n", 
		offset, whence );

	if( target->memory_buffer ) {
		switch( whence ) {
		case SEEK_SET:
			new_position = offset;
			break;

		case SEEK_CUR:
			new_position = target->position + offset;
			break;

		case SEEK_END:
			new_position = target->memory_buffer->len + offset;
			break;

		default:
			vips_error( nick, "%s", _( "bad 'whence'" ) );
			return( -1 );
		}

		if( new_position > target->memory_buffer->len )
			g_string_set_size( target->memory_buffer, 
				new_position );

		target->position = new_position;
	}
	else
		new_position = lseek( connection->descriptor, offset, whence );

	return( new_position );
}

static gint64 
vips_target_read_real( VipsTarget *target, void *data, size_t length )
{
	gint64 bytes_read;

	VIPS_DEBUG_MSG( "vips_target_read_real: %zd bytes\n", length );

	if( target->memory_buffer ) {
		bytes_read = VIPS_MIN( length, 
			target->memory_buffer->len - target->position );

		VIPS_DEBUG_MSG( "    %zd bytes from memory\n", bytes_read );
		memcpy( data, 
			target->memory_buffer->str + 
				target->position, 
			bytes_read );
		target->position += bytes_read;
	}
	else {
		VipsConnection *connection = VIPS_CONNECTION( target );
		int fd = connection->descriptor;

		do { 
			bytes_read = read( fd, data, length );
		} while( bytes_read < 0 && errno == EINTR );
	}

	VIPS_DEBUG_MSG( "  read %zd bytes\n", bytes_read );

	return( bytes_read );
}

static int
vips_target_end_real( VipsTarget *target ) 
{
	VIPS_DEBUG_MSG( "vips_target_finish_real:\n" );

	return( 0 );
}

static void
vips_target_finish_real( VipsTarget *target ) 
{
	VIPS_DEBUG_MSG( "vips_target_finish_real:\n" );
}

static void
vips_target_class_init( VipsTargetClass *class )
{
	GObjectClass *gobject_class = G_OBJECT_CLASS( class );
	VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class );

	gobject_class->finalize = vips_target_finalize;
	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "target";
	object_class->description = _( "Target" );

	object_class->build = vips_target_build;

	class->write = vips_target_write_real;
	class->read = vips_target_read_real;
	class->seek = vips_target_seek_real;
	class->end = vips_target_end_real;
	class->finish = vips_target_finish_real;

	VIPS_ARG_BOOL( class, "memory", 3, 
		_( "Memory" ), 
		_( "File descriptor should output to memory" ),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET( VipsTarget, memory ),
		FALSE );

	/* SET_ALWAYS means that blob is set by C and the obj system is not
	 * involved in creation or destruction. It can be read at any time.
	 */
	VIPS_ARG_BOXED( class, "blob", 4, 
		_( "Blob" ),
		_( "Blob to save to" ),
		VIPS_ARGUMENT_SET_ALWAYS, 
		G_STRUCT_OFFSET( VipsTarget, blob ),
		VIPS_TYPE_BLOB );

}

static void
vips_target_init( VipsTarget *target )
{
	target->blob = vips_blob_new( NULL, NULL, 0 );
	target->write_point = 0;
}

/**
 * vips_target_new_to_descriptor:
 * @descriptor: write to this file descriptor
 *
 * Create a target attached to a file descriptor.
 * @descriptor is kept open until the target is finalized.
 *
 * See also: vips_target_new_to_file().
 *
 * Returns: a new target.
 */
VipsTarget *
vips_target_new_to_descriptor( int descriptor )
{
	VipsTarget *target;

	VIPS_DEBUG_MSG( "vips_target_new_to_descriptor: %d\n", 
		descriptor );

	target = VIPS_TARGET( g_object_new( VIPS_TYPE_TARGET, 
		"descriptor", descriptor,
		NULL ) );

	if( vips_object_build( VIPS_OBJECT( target ) ) ) {
		VIPS_UNREF( target );
		return( NULL );
	}

	return( target ); 
}

/**
 * vips_target_new_to_file:
 * @filename: write to this file 
 *
 * Create a target attached to a file.
 *
 * Returns: a new target.
 */
VipsTarget *
vips_target_new_to_file( const char *filename )
{
	VipsTarget *target;

	VIPS_DEBUG_MSG( "vips_target_new_to_file: %s\n", 
		filename );

	target = VIPS_TARGET( g_object_new( VIPS_TYPE_TARGET, 
		"filename", filename,
		NULL ) );

	if( vips_object_build( VIPS_OBJECT( target ) ) ) {
		VIPS_UNREF( target );
		return( NULL );
	}

	return( target ); 
}

/**
 * vips_target_new_to_memory:
 *
 * Create a target which will write to a memory area. Read from @blob to get
 * memory.
 *
 * See also: vips_target_new_to_file().
 *
 * Returns: a new #VipsConnection
 */
VipsTarget *
vips_target_new_to_memory( void )
{
	VipsTarget *target;

	VIPS_DEBUG_MSG( "vips_target_new_to_memory:\n" ); 

	target = VIPS_TARGET( g_object_new( VIPS_TYPE_TARGET,
		"memory", TRUE,
		NULL ) );

	if( vips_object_build( VIPS_OBJECT( target ) ) ) {
		VIPS_UNREF( target );
		return( NULL );
	}

	return( target ); 
}

/**
 * vips_target_new_temp:
 * @based_on: base the temporary target on this target
 *
 * Create a temporary target -- either a temporary file on disc, or an area in
 * memory, depending on what sort of target @based_on is.
 *
 * See also: vips_target_new_to_file().
 *
 * Returns: a new target.
 */
VipsTarget *
vips_target_new_temp( VipsTarget *based_on )
{
	VipsTarget *target;

	VIPS_DEBUG_MSG( "vips_target_new_temp: %p\n", based_on );

	if( vips_connection_filename( VIPS_CONNECTION( based_on ) ) ) {
		int descriptor;
		char *filename;

		if( !(filename = vips__temp_name( "%s.target" )) )
			return( NULL );
		if( (descriptor = 
			vips__open_image_write( filename, TRUE )) < 0 ) {
			g_free( filename );
			return( NULL );
		}
		if( !(target = vips_target_new_to_descriptor( descriptor )) ) {
			g_free( filename );
			vips_tracked_close( descriptor );
			return( NULL );
		}
		vips_tracked_close( descriptor );
		target->delete_on_close = TRUE;
		target->delete_on_close_filename = filename;
	}
	else
		target = vips_target_new_to_memory();

	return( target ); 
}

static int
vips_target_write_unbuffered( VipsTarget *target, 
	const void *data, size_t length )
{
	VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target );

	VIPS_DEBUG_MSG( "vips_target_write_unbuffered:\n" );

	if( target->ended )
		return( 0 );

	while( length > 0 ) { 
		gint64 bytes_written;

		bytes_written = class->write( target, data, length );

		/* n == 0 isn't strictly an error, but we treat it as 
		 * one to make sure we don't get stuck in this loop.
		 */
		if( bytes_written <= 0 ) {
			vips_error_system( errno, 
				vips_connection_nick( 
					VIPS_CONNECTION( target ) ),
				"%s", _( "write error" ) ); 
			return( -1 ); 
		}

		length -= bytes_written;
		data += bytes_written;
	}

	return( 0 );
}

static int
vips_target_flush( VipsTarget *target )
{
	g_assert( target->write_point >= 0 );
	g_assert( target->write_point <= VIPS_TARGET_BUFFER_SIZE );

	VIPS_DEBUG_MSG( "vips_target_flush:\n" );

	if( target->write_point > 0 ) {
		if( vips_target_write_unbuffered( target, 
			target->output_buffer, target->write_point ) )
			return( -1 );
		target->write_point = 0;
	}

	return( 0 );
}

/**
 * vips_target_write:
 * @target: target to operate on
 * @buffer: bytes to write
 * @length: length of @buffer in bytes
 *
 * Write @length bytes from @buffer to the output. 
 *
 * Returns: 0 on success, -1 on error.
 */
int
vips_target_write( VipsTarget *target, const void *buffer, size_t length )
{
	VIPS_DEBUG_MSG( "vips_target_write: %zd bytes\n", length );

	if( length > VIPS_TARGET_BUFFER_SIZE - target->write_point &&
		vips_target_flush( target ) )
		return( -1 );

	if( length > VIPS_TARGET_BUFFER_SIZE - target->write_point ) {
		/* Still too large? Do an unbuffered write.
		 */
		if( vips_target_write_unbuffered( target, buffer, length ) )
			return( -1 );
	}
	else {
		memcpy( target->output_buffer + target->write_point, 
			buffer, length );
		target->write_point += length;
	}

	return( 0 );
}

/**
 * vips_target_read:
 * @target: target to operate on
 * @buffer: store bytes here
 * @length: length of @buffer in bytes
 *
 * Read up to @length bytes from @target and store the bytes in @buffer.
 * Return the number of bytes actually read. If all bytes have been read from 
 * the file, return 0.
 *
 * Arguments exactly as read(2).
 *
 * Reading froma  target sounds weird, but libtiff needs this for
 * multi-page writes. This method will fail for targets like pipes.
 *
 * Returns: the number of bytes read, 0 on end of file, -1 on error.
 */
gint64
vips_target_read( VipsTarget *target, void *buffer, size_t length )
{
	VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target );

	VIPS_DEBUG_MSG( "vips_target_read: %zd bytes\n", length );

	if( vips_target_flush( target ) )
		return( -1 );

	return( class->read( target, buffer, length ) );
}

/**
 * vips_target_seek:
 * @target: target to operate on
 * @position: position to seek to
 * @whence: seek relative to beginning, offset, or end
 *
 * Seek the target. This behaves exactly as lseek(2).
 *
 * Seeking a target sounds weird, but libtiff needs this. This method will 
 * fail for targets like pipes.
 *
 * Returns: the new seek position, -1 on error.
 */
off_t
vips_target_seek( VipsTarget *target, off_t position, int whence )
{
	VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target );

	off_t new_position;

	VIPS_DEBUG_MSG( "vips_target_seek: pos = %ld, whence = %d\n", 
		position, whence );

	if( vips_target_flush( target ) )
		return( -1 );

	new_position = class->seek( target, position, whence );

	VIPS_DEBUG_MSG( "vips_target_seek: new_position = %ld\n", 
		new_position );

	return( new_position );
}

/**
 * vips_target_end:
 * @target: target to operate on
 * @buffer: bytes to write
 * @length: length of @buffer in bytes
 *
 * Call this at the end of write to make the target do any cleaning up. You
 * can call it many times. 
 *
 * After a target has been finished, further writes will do nothing.
 *
 * Returns: 0 on success, -1 on error.
 */
int
vips_target_end( VipsTarget *target )
{
	VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target );

	VIPS_DEBUG_MSG( "vips_target_end:\n" );

	if( target->ended )
		return( 0 );

	if( vips_target_flush( target ) )
		return( -1 );

	/* Move the target buffer into the blob so it can be read out.
	 */
	if( target->memory_buffer ) {
		const char *data;
		size_t length;

		length = target->memory_buffer->len;
		data = g_string_free( target->memory_buffer, FALSE );
		target->memory_buffer = NULL;
		vips_blob_set( target->blob,
			(VipsCallbackFn) vips_area_free_cb, data, length );
	}
	else {
		if( class->end( target ) )
			return( -1 );
	}

	target->ended = TRUE;

	return( 0 );
}

/**
 * vips_target_finish:
 * @target: target to operate on
 * @buffer: bytes to write
 * @length: length of @buffer in bytes
 *
 * Deprecated in favour of vips_target_end().
 */
void
vips_target_finish( VipsTarget *target )
{
	(void) vips_target_end( target );
}

/**
 * vips_target_steal: 
 * @target: target to operate on
 * @length: return number of bytes of data
 *
 * Memory targets only (see vips_target_new_to_memory()). Steal all data
 * written to the target so far, and finish it.
 *
 * You must free the returned pointer with g_free().
 *
 * The data is NOT automatically null-terminated. vips_target_putc() a '\0' 
 * before calling this to get a null-terminated string.
 *
 * Returns: (array length=length) (element-type guint8) (transfer full): the 
 * data
 */
unsigned char *
vips_target_steal( VipsTarget *target, size_t *length )
{
	const char *data;

	(void) vips_target_flush( target );

	if( !target->memory_buffer ||
		target->ended ) {
		if( length )
			*length = target->memory_buffer->len;

		return( NULL );
	}

	if( length )
		*length = target->memory_buffer->len;
	data = g_string_free( target->memory_buffer, FALSE );
	target->memory_buffer = NULL;

	/* We must have a valid byte array or end will fail.
	 */
	target->memory_buffer = g_string_sized_new( 0 );

	if( vips_target_end( target ) )
		return( NULL );

	return( (unsigned char *) data );
}

/**
 * vips_target_steal_text: 
 * @target: target to operate on
 *
 * As vips_target_steal_text(), but return a null-terminated string.
 *
 * Returns: (transfer full): target contents as a null-terminated string.
 */
char *
vips_target_steal_text( VipsTarget *target )
{
	vips_target_putc( target, '\0' );  

	return( (char *) vips_target_steal( target, NULL ) ); 
}

/**
 * vips_target_putc:
 * @target: target to operate on
 * @ch: character to write
 *
 * Write a single character @ch to @target. See the macro VIPS_TARGET_PUTC()
 * for a faster way to do this.
 *
 * Returns: 0 on success, -1 on error.
 */
int
vips_target_putc( VipsTarget *target, int ch )
{
	VIPS_DEBUG_MSG( "vips_target_putc: %d\n", ch );

	if( target->write_point >= VIPS_TARGET_BUFFER_SIZE && 
		vips_target_flush( target ) )
		return( -1 );

	target->output_buffer[target->write_point++] = ch;

	return( 0 );
}

/**
 * vips_target_writes:
 * @target: target to operate on
 * @str: string to write
 *
 * Write a null-terminated string to @target.
 * 
 * Returns: 0 on success, and -1 on error.
 */
int
vips_target_writes( VipsTarget *target, const char *str )
{
	return( vips_target_write( target, 
		(unsigned char *) str, strlen( str ) ) );
}

/**
 * vips_target_writef:
 * @target: target to operate on
 * @fmt: <function>printf()</function>-style format string
 * @...: arguments to format string
 *
 * Format the string and write to @target. 
 * 
 * Returns: 0 on success, and -1 on error.
 */
int
vips_target_writef( VipsTarget *target, const char *fmt, ... )
{
	va_list ap;
	char *line;
	int result;

        va_start( ap, fmt );
	line = g_strdup_vprintf( fmt, ap ); 
        va_end( ap );

	result = vips_target_writes( target, line ); 

	g_free( line ); 

	return( result ); 
}

/**
 * vips_target_write_amp: 
 * @target: target to operate on
 * @str: string to write
 *
 * Write @str to @target, but escape stuff that xml hates in text. Our
 * argument string is utf-8.
 *
 * XML rules:
 *
 * - We must escape &<> 
 * - Don't escape \n, \t, \r
 * - Do escape the other ASCII codes. 
 *
 * Returns: 0 on success, -1 on error.
 */
int
vips_target_write_amp( VipsTarget *target, const char *str )
{
	const char *p;

	for( p = str; *p; p++ ) 
		if( *p < 32 &&
			*p != '\n' &&
			*p != '\t' &&
			*p != '\r' ) {
			/* You'd think we could output "&#x02%x;", but xml
			 * 1.0 parsers barf on that. xml 1.1 allows this, but
			 * there are almost no parsers. 
			 *
			 * U+2400 onwards are unicode glyphs for the ASCII 
			 * control characters, so we can use them -- thanks
			 * electroly.
			 */
			if( vips_target_writef( target, 
				"&#x%04x;", 0x2400 + *p ) )
				return( -1 );	
		}
		else if( *p == '<' ) {
			if( vips_target_writes( target, "&lt;" ) )
				return( -1 );
		}
		else if( *p == '>' ) {
			if( vips_target_writes( target, "&gt;" ) )
				return( -1 );
		}
		else if( *p == '&' ) {
			if( vips_target_writes( target, "&amp;" ) )
				return( -1 );
		}
		else  {
			if( VIPS_TARGET_PUTC( target, *p ) )
				return( -1 );
		}

	return( 0 ); 
}

