/* helper functions for Orc
 *
 * 29/10/10
 * 	- from morph hacking
 */

/*

    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

 */

/* 

 	TODO

	- would setting params by index rather than name be any quicker?

 */

/* Verbose messages from Orc (or use ORC_DEBUG=99 on the command-line).
#define DEBUG_ORC
 */

/*
#define DEBUG
 */

/* Trace all orc calls, handy for debugging.
#define DEBUG_TRACE
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/

#include <glib/gi18n-lib.h>

#include <stdlib.h>

#include <vips/vips.h>
#include <vips/vector.h>
#include <vips/internal.h>
#include <vips/thread.h>

/* Cleared by the command-line --vips-novector switch and the VIPS_NOVECTOR env
 * var.
 */
gboolean vips__vector_enabled = TRUE;

static void
vips_vector_error( VipsVector *vector )
{
#ifdef HAVE_ORC
#ifdef HAVE_ORC_PROGRAM_GET_ERROR
	if( vector->program )
		g_warning( "orc error: %s", 
			orc_program_get_error( vector->program ) ); 
#endif /*HAVE_ORC_PROGRAM_GET_ERROR*/
#endif /*HAVE_ORC*/
}

void 
vips_vector_init( void )
{
#ifdef HAVE_ORC
#ifdef DEBUG_TRACE
	printf( "orc_init();\n" );
#endif /*DEBUG_TRACE*/
	orc_init();

#ifdef DEBUG_ORC
	/* You can also do ORC_DEBUG=99 at the command-line.
	 */
#ifdef DEBUG_TRACE
	printf( "orc_debug_set_level( 99 );\n" ); 
#endif /*DEBUG_TRACE*/
	orc_debug_set_level( 99 );
#endif /*DEBUG_ORC*/

	/* Look for the environment variable VIPS_NOVECTOR and use that to turn
	 * off as well.
	 */
	if( g_getenv( "VIPS_NOVECTOR" )
#if ENABLE_DEPRECATED
		|| g_getenv( "IM_NOVECTOR" )
#endif
	)
		vips__vector_enabled = FALSE;

#endif /*HAVE_ORC*/
}

gboolean 
vips_vector_isenabled( void )
{
#ifdef HAVE_ORC
	return( vips__vector_enabled );
#else /*!HAVE_ORC*/
	return( FALSE );
#endif /*HAVE_ORC*/
}

void 
vips_vector_set_enabled( gboolean enabled )
{
	vips__vector_enabled = enabled;
}

void
vips_vector_free( VipsVector *vector )
{
#ifdef HAVE_ORC
	/* orc-0.4.19 will crash if you free programs. Update your orc, or
	 * comment out this line. 
	 *
	 * See https://bugzilla.gnome.org/show_bug.cgi?id=731227
	 *
	 * orc does not set any version variables so we can't disable this
	 * free automatically.
	 */
#ifdef DEBUG_TRACE
	printf( "orc_program_free( %s );\n", vector->unique_name ); 
	printf( "%s = NULL;\n", vector->unique_name ); 
#endif /*DEBUG_TRACE*/
	VIPS_FREEF( orc_program_free, vector->program );
#endif /*HAVE_ORC*/
	VIPS_FREE( vector->unique_name );
	VIPS_FREE( vector );
}

VipsVector *
vips_vector_new( const char *name, int dsize )
{
	static int vector_number = 0;

	VipsVector *vector;
	int i;

	if( !(vector = VIPS_NEW( NULL, VipsVector )) )
		return( NULL );
	vector->name = name;
	vector->unique_name = g_strdup_printf( "p[%d]", vector_number++ );
	vector->n_temp = 0;
	vector->n_scanline = 0;
	vector->n_source = 0;
	vector->n_destination = 0;
	vector->n_constant = 0;
	vector->n_parameter = 0;
	vector->n_instruction = 0;

	for( i = 0; i < VIPS_VECTOR_SOURCE_MAX; i++ ) {
		vector->s[i] = -1;
		vector->sl[i] = -1;
	}

	vector->d1 = -1;

	vector->compiled = FALSE;

#ifdef HAVE_ORC
	vector->program = orc_program_new();
#ifdef DEBUG_TRACE
	printf( "%s = orc_program_new();\n", vector->unique_name );
#endif /*DEBUG_TRACE*/
#endif /*HAVE_ORC*/

	/* We always make d1, our callers make either a single point source, or
	 * for area ops, a set of scanlines.
	 *
	 * Don't check error return. orc uses 0 to mean error, but the first
	 * var you create will have id 0 :-( The first var is unlikely to fail
	 * anyway. 
	 */
	vector->d1 = vips_vector_destination( vector, "d1", dsize ); 

	return( vector );
}

void 
vips_vector_asm2( VipsVector *vector, 
	const char *op, const char *a, const char *b )
{
	vector->n_instruction += 1;

#ifdef DEBUG
	 printf( "  %s %s %s\n", op, a, b );
#endif /*DEBUG*/

#ifdef HAVE_ORC
#ifdef DEBUG_TRACE
	printf( "orc_program_append_ds_str( %s, \"%s\", \"%s\", \"%s\" );\n",
		vector->unique_name, op, a, b ); 
#endif /*DEBUG_TRACE*/
	 orc_program_append_ds_str( vector->program, op, a, b );
#endif /*HAVE_ORC*/
}

void 
vips_vector_asm3( VipsVector *vector, 
	const char *op, const char *a, const char *b, const char *c )
{
	vector->n_instruction += 1;

#ifdef DEBUG
	 printf( "  %s %s %s %s\n", op, a, b, c );
#endif /*DEBUG*/

#ifdef HAVE_ORC
#ifdef DEBUG_TRACE
	printf( "orc_program_append_str( %s, \"%s\", "
		"\"%s\", \"%s\", \"%s\" );\n",
		vector->unique_name, op, a, b, c ); 
#endif /*DEBUG_TRACE*/
	 orc_program_append_str( vector->program, op, a, b, c );
#endif /*HAVE_ORC*/
}

void
vips_vector_constant( VipsVector *vector, char *name, int value, int size )
{
#ifdef HAVE_ORC
	char *sname;

	if( size == 1 )
		sname = "b";
	else if( size == 2 )
		sname = "w";
	else if( size == 4 )
		sname = "l";
	else {
		printf( "vips_vector_constant: bad constant size\n" );

		/* Not really correct, heh.
		 */
		sname = "x";
	}

	if( value > 0 )
		vips_snprintf( name, 256, "c%d%s", value, sname );
	else
		vips_snprintf( name, 256, "cm%d%s", -value, sname );

	if( orc_program_find_var_by_name( vector->program, name ) == -1 ) {
#ifdef DEBUG_TRACE
		printf( "orc_program_add_constant( %s, %d, %d, \"%s\" );\n", 
			vector->unique_name, size, value, name ); 
#endif /*DEBUG_TRACE*/
		if( !orc_program_add_constant( vector->program, 
			size, value, name ) )
			vips_vector_error( vector );
		vector->n_constant += 1;
	}
#endif /*HAVE_ORC*/
}

void
vips_vector_source_scanline( VipsVector *vector, 
	char *name, int line, int size )
{
#ifdef HAVE_ORC
	vips_snprintf( name, 256, "sl%d", line );

	if( orc_program_find_var_by_name( vector->program, name ) == -1 ) {
		int var;

		if( !(var = orc_program_add_source( vector->program, 
			size, name )) ) 
			vips_vector_error( vector );
#ifdef DEBUG_TRACE
		printf( "orc_program_add_source( %s, %d, \"%s\" );\n",
			vector->unique_name, size, name );
#endif /*DEBUG_TRACE*/
		vector->sl[vector->n_scanline] = var;
		vector->line[vector->n_scanline] = line;
		vector->n_scanline += 1;
	}
#endif /*HAVE_ORC*/
}

int
vips_vector_source_name( VipsVector *vector, const char *name, int size )
{
	int var;

#ifdef HAVE_ORC
	g_assert( orc_program_find_var_by_name( vector->program, name ) == -1 );

	if( !(var = orc_program_add_source( vector->program, size, name )) )
		vips_vector_error( vector ); 
	vector->s[vector->n_source] = var;
#ifdef DEBUG_TRACE
	printf( "orc_program_add_source( %s, %d, \"%s\" );\n", 
		vector->unique_name, size, name );
#endif /*DEBUG_TRACE*/
	vector->n_source += 1;
#else /*!HAVE_ORC*/
	var = -1;
#endif /*HAVE_ORC*/

	return( var );
}

void
vips_vector_temporary( VipsVector *vector, const char *name, int size )
{
#ifdef HAVE_ORC
	g_assert( orc_program_find_var_by_name( vector->program, name ) == -1 );

	if( !orc_program_add_temporary( vector->program, size, name ) )
		vips_vector_error( vector ); 

#ifdef DEBUG_TRACE
	printf( "orc_program_add_temporary( %s, %d, \"%s\" );\n",
		vector->unique_name, size, name );
#endif /*DEBUG_TRACE*/
	vector->n_temp += 1;
#endif /*HAVE_ORC*/
}

int
vips_vector_parameter( VipsVector *vector, const char *name, int size )
{
	int var;

#ifdef HAVE_ORC
	g_assert( orc_program_find_var_by_name( vector->program, name ) == -1 );

	var = orc_program_add_parameter( vector->program, size, name );
	if( !var )
		vips_vector_error( vector ); 

#ifdef DEBUG_TRACE
	printf( "orc_program_add_parameter( %s, %d, \"%s\" );\n",
		vector->unique_name, size, name );
#endif /*DEBUG_TRACE*/
	vector->n_parameter += 1;
#else /*!HAVE_ORC*/
	var = -1;
#endif /*HAVE_ORC*/

	return ( var ); 
}

int
vips_vector_destination( VipsVector *vector, const char *name, int size )
{
	int var;

#ifdef HAVE_ORC
	g_assert( orc_program_find_var_by_name( vector->program, name ) == -1 );

	var = orc_program_add_destination( vector->program, size, name );
#ifdef DEBUG_TRACE
	printf( "orc_program_add_destination( %d, \"%s\" );\n",
		size, name );
#endif /*DEBUG_TRACE*/
	vector->n_destination += 1;
#else /*!HAVE_ORC*/
	var = -1;
#endif /*HAVE_ORC*/

	return( var ); 
}

gboolean
vips_vector_full( VipsVector *vector )
{
	/* Many orcs don't have ORC_MAX_CONST_VARS etc., stick to our own
	 * constants for now.
	 */

	/* We can need a max of 2 constants plus one source per
	 * coefficient, so stop if we're sure we don't have enough.
	 */
	if( vector->n_constant + 2 > 8 /*ORC_MAX_CONST_VARS*/ )
		return( TRUE );

	/* You can have 8 source, and d1 counts as one of them, so +1
	 * there.
	 */
	if( vector->n_source + vector->n_scanline + 1 > 7 /*ORC_MAX_SRC_VARS - 1*/ )
		return( TRUE );

	/* Need to leave some space, so 1 spare. 
	 */
	if( vector->n_parameter > 7 /*ORC_MAX_PARAM_VARS - 1*/ )
		return( TRUE );

	/* After signalling full, some operations will add up to 4 more 
	 * instructions as they finish up. Leave a margin.
	 */
	if( vector->n_instruction + 10 > 50 /*ORC_N_INSNS / 2*/ )
		return( TRUE );

	return( FALSE );
}

gboolean
vips_vector_compile( VipsVector *vector )
{
#ifdef HAVE_ORC
	OrcCompileResult result;

	/* Some orcs seem to be unstable with many compilers active at once.
	 */
	g_mutex_lock( vips__global_lock );
	result = orc_program_compile( vector->program );
	g_mutex_unlock( vips__global_lock );

#ifdef DEBUG_TRACE
	printf( "orc_program_compile( %s );\n", vector->unique_name );
#endif /*DEBUG_TRACE*/
	if( !ORC_COMPILE_RESULT_IS_SUCCESSFUL( result ) ) {
#ifdef DEBUG
		printf( "*** error compiling %s\n", vector->name );
#endif /*DEBUG*/

		return( FALSE );
	}

	vector->compiled = TRUE;
#endif /*HAVE_ORC*/

	return( TRUE );
}

void
vips_vector_print( VipsVector *vector )
{
	int i;

	printf( "%s: ", vector->name );
	if( vector->compiled )
		printf( "successfully compiled\n" );
	else
		printf( "not compiled\n" );
	printf( "  n_scanline = %d\n", vector->n_scanline );
	for( i = 0; i < vector->n_scanline; i++ )
		printf( "        var %d = line %d\n", 
			vector->sl[i], vector->line[i] ); 
	printf( "  n_source = %d\n", vector->n_source );
	for( i = 0; i < vector->n_source; i++ )
		printf( "        var %d\n", vector->s[i] );
	printf( "  n_parameter = %d\n", vector->n_parameter );
	printf( "  n_destination = %d\n", vector->n_destination );
	printf( "  n_constant = %d\n", vector->n_constant );
	printf( "  n_temp = %d\n", vector->n_temp );
	printf( "  n_instruction = %d\n", vector->n_instruction );
}

void
vips_executor_set_program( VipsExecutor *executor, VipsVector *vector, int n )
{
#ifdef HAVE_ORC
	executor->vector = vector;

	orc_executor_set_program( &executor->executor, vector->program );
	orc_executor_set_n( &executor->executor, n );
#endif /*HAVE_ORC*/
}

void
vips_executor_set_array( VipsExecutor *executor, int var, void *value )
{
#ifdef HAVE_ORC
	if( var != -1 )  
		orc_executor_set_array( &executor->executor, var, value );
#endif /*HAVE_ORC*/
}

void
vips_executor_set_parameter( VipsExecutor *executor, int var, int value )
{
#ifdef HAVE_ORC
	if( var != -1 )  
		orc_executor_set_param( &executor->executor, var, value );
#endif /*HAVE_ORC*/
}

void
vips_executor_set_scanline( VipsExecutor *executor, 
	VipsRegion *ir, int x, int y )
{
	VipsVector *vector = executor->vector;
	VipsPel *base = VIPS_REGION_ADDR( ir, x, y );
	int lsk = VIPS_REGION_LSKIP( ir );

	int i;

	for( i = 0; i < vector->n_scanline; i++ ) 
		vips_executor_set_array( executor, 
			vector->sl[i], base + vector->line[i] * lsk );
}

void
vips_executor_set_destination( VipsExecutor *executor, void *value )
{
	VipsVector *vector = executor->vector;

	vips_executor_set_array( executor, vector->d1, value );
}

void
vips_executor_run( VipsExecutor *executor )
{
#ifdef HAVE_ORC
	orc_executor_run( &executor->executor );
#endif /*HAVE_ORC*/
}

/* Make a fixed-point version of a matrix. Each 
 * out[i] = rint(in[i] * adj_scale), where adj_scale is selected so that 
 * sum(out) = sum(in) * scale.
 *
 * Because of the vagaries of rint(), we can't just calc this, we have to
 * iterate and converge on the best value for adj_scale.
 */
void
vips_vector_to_fixed_point( double *in, int *out, int n, int scale )
{
	double fsum;
	int i;
	int target;
	int sum;
	double high;
	double low;
	double guess;

	fsum = 0.0;
	for( i = 0; i < n; i++ )
		fsum += in[i];
	target = VIPS_RINT( fsum * scale );

	/* As we rint() each scale element, we can get up to 0.5 error.
	 * Therefore, by the end of the mask, we can be off by up to n/2. Our
	 * high and low guesses are therefore n/2 either side of the obvious
	 * answer.
	 */
	high = scale + (n + 1) / 2;
	low = scale - (n + 1) / 2;

	do {
		guess = (high + low) / 2.0;

		for( i = 0; i < n; i++ ) 
			out[i] = VIPS_RINT( in[i] * guess );

		sum = 0;
		for( i = 0; i < n; i++ )
			sum += out[i];

		if( sum == target )
			break;
		if( sum < target )
			low = guess;
		if( sum > target )
			high = guess;

	/* This will typically produce about 5 iterations.
	 */
	} while( high - low > 0.01 );

	if( sum != target ) {
		/* Spread the error out thinly over the whole array. For
		 * example, consider the matrix:
		 *
		 * 	3 3 9 0
		 *	1 1 1
		 *	1 1 1
		 *	1 1 1
		 *
		 * being converted with scale = 64 (convi does this). We want
		 * to generate a mix of 7s and 8s. 
		 */
		int each_error = (target - sum) / n;
		int extra_error = (target - sum) % n;

		/* To share the residual error, we add or subtract 1 from the
		 * first abs(extra_error) elements.
		 */
		int direction = extra_error > 0 ? 1 : -1;
		int n_elements = VIPS_ABS( extra_error );

		for( i = 0; i < n; i++ )
			out[i] += each_error;

		for( i = 0; i < n_elements; i++ )
			out[i] += direction;
	}
}
