/* @(#) Bitwise operations on VASARI images. Inputs must be some 
 * @(#) integer type and have the same size and number of bands. Use
 * @(#) im_eorconst( in, out, -1 ) for im_not.
 * @(#)
 * @(#) int im_andimage( a, b, out )	int im_andconst( a, out, c )
 * @(#) IMAGE *a, *b, *out;		IMAGE *a, *out;
 * @(#)					unsigned char c;
 * @(#)
 * @(#) int im_orimage( a, b, out )	int im_orconst( a, out, c )
 * @(#) IMAGE *a, *b, *out;		IMAGE *a, *out;
 * @(#)					unsigned char c;
 * @(#)
 * @(#) int im_eorimage( a, b, out )	int im_eorconst( a, out, c )
 * @(#) IMAGE *a, *b, *out;		IMAGE *a, *out;
 * @(#)					unsigned char c;
 * @(#)
 * @(#) int im_shiftleft( in, out, n )	int im_shiftright( in, out, n )
 * @(#) IMAGE *in, *out;		IMAGE *in, *out;
 * @(#)	int n;				int n;
 * @(#)
 * @(#) Returns either 0 (success) or -1 (fail).
 *
 * Modified: 
 * 15/12/94 JC
 * 	- ANSIfied
 * 	- adapted to partials with im_wrap...
 * 25/1/95 JC
 *	- added check1ary(), check2ary()
 * 8/2/95 JC
 *	- new im_wrapmany
 * 19/7/95 JC
 *	- added im_shiftleft() and im_shiftright()
 * 6/7/98 JC
 *	- added _vec forms
 * 	- removed *p++ stuff
 * 10/9/99 JC
 *	- and/or/eor now do all int types
 * 10/10/02 JC
 *	- renamed im_and() etc. as im_andimage() to remove breakage in the C++
 *	  layer if operator names are turned on
 * 30/6/04
 *	- now cast float/complex args to int
 */

/*

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/*

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

 */

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

#include <stdio.h>
#include <math.h>
#include <assert.h>

#include <vips/vips.h>

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/

/* Save a bit of typing.
 */
#define UC IM_BANDFMT_UCHAR
#define C IM_BANDFMT_CHAR
#define US IM_BANDFMT_USHORT
#define S IM_BANDFMT_SHORT
#define UI IM_BANDFMT_UINT
#define I IM_BANDFMT_INT
#define F IM_BANDFMT_FLOAT
#define M IM_BANDFMT_COMPLEX
#define D IM_BANDFMT_DOUBLE
#define DM IM_BANDFMT_DPCOMPLEX

/* Type conversions for boolean.
 */
static int iformat[10][10] = {
        /* UC  C   US  S   UI  I   F   M   D   DM */
/* UC */ { UC, C,  US, S,  UI, I,  I,  I,  I,  I },
/* C */  { C,  C,  S,  S,  I,  I,  I,  I,  I,  I },
/* US */ { US, S,  US, S,  UI, I,  I,  I,  I,  I },
/* S */  { S,  S,  S,  S,  I,  I,  I,  I,  I,  I },
/* UI */ { UI, I,  UI, I,  UI, I,  I,  I,  I,  I },
/* I */  { I,  I,  I,  I,  I,  I,  I,  I,  I,  I },
/* F */  { I,  I,  I,  I,  I,  I,  I,  I,  I,  I },
/* M */  { I,  I,  I,  I,  I,  I,  I,  I,  I,  I },
/* D */  { I,  I,  I,  I,  I,  I,  I,  I,  I,  I },
/* DM */ { I,  I,  I,  I,  I,  I,  I,  I,  I,  I }
};

/* Check args. Cast inputs to matching integer format. 
 */
static int
check( IMAGE **in, IMAGE *out )
{
	int i, n;
	int fmt;

	/* Count args.
	 */
	for( n = 0; in[n]; n++ ) {
		if( in[n]->Coding != IM_CODING_NONE ) {
			im_error( "boolean", 
				"%s", _( "uncoded images only" ) );
			return( -1 );
		}
	}

	/* Check sizes match.
	 */
	for( i = 1; i < n; i++ )
		if( in[0]->Bands != in[i]->Bands ||
			in[0]->Xsize != in[i]->Xsize ||
			in[0]->Ysize != in[i]->Ysize ) {
			im_error( "boolean", 
				"%s", _( "images differ in size" ) );
			return( -1 );
		}

	/* Calculate type conversion ... just 1ary and 2ary.
	 */
	switch( n ) {
	case 1:
		fmt = iformat[0][in[0]->BandFmt];
		break;

	case 2:
		fmt = iformat[in[1]->BandFmt][in[0]->BandFmt];
		break;

	default:
		assert( FALSE );
	}

	for( i = 0; i < n; i++ ) {
		IMAGE *t = im_open_local( out, "check-1", "p" );

		if( !t || im_clip2fmt( in[i], t, fmt ) )
			return( -1 );
		in[i] = t;
	}

	/* Prepare the output image.
	 */
	if( im_cp_desc_array( out, in ) )
		return( -1 );

	return( 0 );
}

/* A selection of main loops. As with im_add(), only implement monotype
 * operations. TYPE is some integer type, signed or unsigned.
 */
#define AND2( TYPE ) { \
	TYPE *tq = (TYPE *) q; \
	TYPE *tp1 = (TYPE *) p1; \
	TYPE *tp2 = (TYPE *) p2; \
 	\
	for( x = 0; x < ne; x++ )  \
		tq[x] = tp1[x] & tp2[x]; \
}

#define OR2( TYPE ) { \
	TYPE *tq = (TYPE *) q; \
	TYPE *tp1 = (TYPE *) p1; \
	TYPE *tp2 = (TYPE *) p2; \
 	\
	for( x = 0; x < ne; x++ )  \
		tq[x] = tp1[x] | tp2[x]; \
}

#define EOR2( TYPE ) { \
	TYPE *tq = (TYPE *) q; \
	TYPE *tp1 = (TYPE *) p1; \
	TYPE *tp2 = (TYPE *) p2; \
 	\
	for( x = 0; x < ne; x++ )  \
		tq[x] = tp1[x] ^ tp2[x]; \
}

#define ANDCONST( TYPE ) { \
	TYPE *tq = (TYPE *) q; \
	TYPE *tp = (TYPE *) p; \
	TYPE *tc = (TYPE *) c; \
 	\
	for( i = 0, x = 0; x < n; x++ ) \
		for( b = 0; b < bands; b++, i++ ) \
			tq[i] = tp[i] & tc[b]; \
}

#define ORCONST( TYPE ) { \
	TYPE *tq = (TYPE *) q; \
	TYPE *tp = (TYPE *) p; \
	TYPE *tc = (TYPE *) c; \
 	\
	for( i = 0, x = 0; x < n; x++ ) \
		for( b = 0; b < bands; b++, i++ ) \
			tq[i] = tp[i] | tc[b]; \
}

#define EORCONST( TYPE ) { \
	TYPE *tq = (TYPE *) q; \
	TYPE *tp = (TYPE *) p; \
	TYPE *tc = (TYPE *) c; \
 	\
	for( i = 0, x = 0; x < n; x++ ) \
		for( b = 0; b < bands; b++, i++ ) \
			tq[i] = tp[i] ^ tc[b]; \
}

/* The above, wrapped up as buffer processing functions.
 */
static void
and_buffer( PEL **p, PEL *q, int n, IMAGE *im )
{
	int x;
	int bands = im->Bands;
	int ne = n * bands;
	PEL *p1 = p[0];
	PEL *p2 = p[1];

        switch( im->BandFmt ) {
        case IM_BANDFMT_CHAR:	AND2( signed char ); break;
        case IM_BANDFMT_UCHAR:  AND2( unsigned char ); break;
        case IM_BANDFMT_SHORT:  AND2( signed short ); break;
        case IM_BANDFMT_USHORT: AND2( unsigned short ); break;
        case IM_BANDFMT_INT:    AND2( signed int ); break;
        case IM_BANDFMT_UINT:   AND2( unsigned int ); break;

        default:
                error_exit( "im_and: internal error" );
        }
}

static void
or_buffer( PEL **p, PEL *q, int n, IMAGE *in1 )
{
	int x;
	int bands = in1->Bands;
	int ne = n * bands;
	PEL *p1 = p[0];
	PEL *p2 = p[1];

        switch( in1->BandFmt ) {
        case IM_BANDFMT_CHAR:	OR2( signed char ); break;
        case IM_BANDFMT_UCHAR:  OR2( unsigned char ); break;
        case IM_BANDFMT_SHORT:  OR2( signed short ); break;
        case IM_BANDFMT_USHORT: OR2( unsigned short ); break;
        case IM_BANDFMT_INT:    OR2( signed int ); break;
        case IM_BANDFMT_UINT:   OR2( unsigned int ); break;

        default:
                error_exit( "im_or: internal error" );
        }
}

static void
eor_buffer( PEL **p, PEL *q, int n, IMAGE *in1 )
{
	int x;
	int bands = in1->Bands;
	int ne = n * bands;
	PEL *p1 = p[0];
	PEL *p2 = p[1];

        switch( in1->BandFmt ) {
        case IM_BANDFMT_CHAR:	EOR2( signed char ); break;
        case IM_BANDFMT_UCHAR:  EOR2( unsigned char ); break;
        case IM_BANDFMT_SHORT:  EOR2( signed short ); break;
        case IM_BANDFMT_USHORT: EOR2( unsigned short ); break;
        case IM_BANDFMT_INT:    EOR2( signed int ); break;
        case IM_BANDFMT_UINT:   EOR2( unsigned int ); break;

        default:
                error_exit( "im_eor: internal error" );
        }
}

static void
andconst_buffer( PEL *p, PEL *q, int n, IMAGE *in, PEL *c )
{
	int x, i, b;
	int bands = in->Bands;

        switch( in->BandFmt ) {
        case IM_BANDFMT_CHAR:	ANDCONST( signed char ); break;
        case IM_BANDFMT_UCHAR:  ANDCONST( unsigned char ); break;
        case IM_BANDFMT_SHORT:  ANDCONST( signed short ); break;
        case IM_BANDFMT_USHORT: ANDCONST( unsigned short ); break;
        case IM_BANDFMT_INT:    ANDCONST( signed int ); break;
        case IM_BANDFMT_UINT:   ANDCONST( unsigned int ); break;

        default:
                error_exit( "im_andconst: internal error" );
        }
}

static void
orconst_buffer( PEL *p, PEL *q, int n, IMAGE *in, PEL *c )
{
	int x, i, b;
	int bands = in->Bands;

        switch( in->BandFmt ) {
        case IM_BANDFMT_CHAR:	ORCONST( signed char ); break;
        case IM_BANDFMT_UCHAR:  ORCONST( unsigned char ); break;
        case IM_BANDFMT_SHORT:  ORCONST( signed short ); break;
        case IM_BANDFMT_USHORT: ORCONST( unsigned short ); break;
        case IM_BANDFMT_INT:    ORCONST( signed int ); break;
        case IM_BANDFMT_UINT:   ORCONST( unsigned int ); break;

        default:
                error_exit( "im_orconst: internal error" );
        }
}

static void
eorconst_buffer( PEL *p, PEL *q, int n, IMAGE *in, PEL *c )
{
	int x, i, b;
	int bands = in->Bands;

        switch( in->BandFmt ) {
        case IM_BANDFMT_CHAR:	EORCONST( signed char ); break;
        case IM_BANDFMT_UCHAR:  EORCONST( unsigned char ); break;
        case IM_BANDFMT_SHORT:  EORCONST( signed short ); break;
        case IM_BANDFMT_USHORT: EORCONST( unsigned short ); break;
        case IM_BANDFMT_INT:    EORCONST( signed int ); break;
        case IM_BANDFMT_UINT:   EORCONST( unsigned int ); break;

        default:
                error_exit( "im_eorconst: internal error" );
        }
}

/* The above, wrapped up as im_*() functions.
 */
int 
im_andimage( IMAGE *in1, IMAGE *in2, IMAGE *out )
{
	IMAGE *invec[3];

	/* Check images.
	 */
	invec[0] = in1; invec[1] = in2; invec[2] = NULL;
	if( check( invec, out ) )
		return( -1 );

	/* Process!
	 */
	if( im_wrapmany( invec, out, (im_wrapmany_fn) and_buffer, out, NULL ) )
		return( -1 );

	return( 0 );
}

int 
im_orimage( IMAGE *in1, IMAGE *in2, IMAGE *out )
{	
	IMAGE *invec[3];

	invec[0] = in1; invec[1] = in2; invec[2] = NULL;
	if( check( invec, out ) )
		return( -1 );

	if( im_wrapmany( invec, out, (im_wrapmany_fn) or_buffer, out, NULL ) )
		return( -1 );

	return( 0 );
}

int 
im_eorimage( IMAGE *in1, IMAGE *in2, IMAGE *out )
{	
	IMAGE *invec[3];

	invec[0] = in1; invec[1] = in2; invec[2] = NULL;
	if( check( invec, out ) )
		return( -1 );

	if( im_wrapmany( invec, out, (im_wrapmany_fn) eor_buffer, out, NULL ) )
		return( -1 );

	return( 0 );
}

/* Cast a vector of double to a vector of TYPE.
 */
#define CAST( TYPE ) { \
	TYPE *tq = (TYPE *) q; \
	\
	for( i = 0; i < out->Bands; i++ ) \
		tq[i] = (TYPE) p[i]; \
}

/* Make a pixel of output type from a realvec.
 */
static PEL *
make_pixel( IMAGE *out, int fmt, double *p )
{
	PEL *q;
	int i;

	if( !(q = IM_ARRAY( out, IM_IMAGE_SIZEOF_PEL( out ), PEL )) )
		return( NULL );

        switch( fmt ) {
        case IM_BANDFMT_CHAR:	CAST( signed char ); break;
        case IM_BANDFMT_UCHAR:  CAST( unsigned char ); break;
        case IM_BANDFMT_SHORT:  CAST( signed short ); break;
        case IM_BANDFMT_USHORT: CAST( unsigned short ); break;
        case IM_BANDFMT_INT:    CAST( signed int ); break;
        case IM_BANDFMT_UINT:   CAST( unsigned int ); break;

        default:
                error_exit( "make_pixel: internal error" );
        }

	return( q );
}

int 
im_and_vec( IMAGE *in, IMAGE *out, int n, double *c )
{
	IMAGE *invec[2];
	PEL *cb;

	invec[0] = in; invec[1] = NULL;
	if( check( invec, out ) )
		return( -1 );
	in = invec[0];
	if( n != in->Bands ) {
		im_error( "im_and_vec", 
			"%s", _( "vec size does not match bands" ) );
		return( -1 );
	}
	if( !(cb = make_pixel( out, in->BandFmt, c )) )
		return( -1 );

	if( im_wrapone( in, out, 
		(im_wrapone_fn) andconst_buffer, (void *) in, (void *) cb ) )
		return( -1 );

	return( 0 );
}

int 
im_or_vec( IMAGE *in, IMAGE *out, int n, double *c )
{
	IMAGE *invec[2];
	PEL *cb;

	invec[0] = in; invec[1] = NULL;
	if( check( invec, out ) )
		return( -1 );
	in = invec[0];
	if( n != in->Bands ) {
		im_error( "im_or_vec", 
			"%s", _( "vec size does not match bands" ) );
		return( -1 );
	}
	if( !(cb = make_pixel( out, in->BandFmt, c )) )
		return( -1 );

	if( im_wrapone( in, out, 
		(im_wrapone_fn) orconst_buffer, (void *) in, (void *) cb ) )
		return( -1 );

	return( 0 );
}

int 
im_eor_vec( IMAGE *in, IMAGE *out, int n, double *c )
{
	IMAGE *invec[2];
	PEL *cb;

	invec[0] = in; invec[1] = NULL;
	if( check( invec, out ) )
		return( -1 );
	in = invec[0];
	if( n != in->Bands ) {
		im_error( "im_eor_vec", 
			"%s", _( "vec size does not match bands" ) );
		return( -1 );
	}
	if( !(cb = make_pixel( out, in->BandFmt, c )) )
		return( -1 );

	if( im_wrapone( in, out, 
		(im_wrapone_fn) eorconst_buffer, (void *) in, (void *) cb ) )
		return( -1 );

	return( 0 );
}

/* Cast a double to a vector of TYPE.
 */
#define CCAST( TYPE ) { \
	TYPE *tq = (TYPE *) q; \
	\
	for( i = 0; i < in->Bands; i++ ) \
		tq[i] = (TYPE) p; \
}

/* Make a pixel of output type from a single double.
 */
static double *
make_pixel_const( IMAGE *in, IMAGE *out, double p )
{
	double *q;
	int i;

	if( !(q = IM_ARRAY( out, in->Bands, double )) )
		return( NULL );
	for( i = 0; i < in->Bands; i++ ) 
		q[i] = p; 

	return( q );
}

int 
im_andconst( IMAGE *in, IMAGE *out, double c )
{
	double *v = make_pixel_const( in, out, c );

	return( !v || im_and_vec( in, out, in->Bands, v ) ); 
}

int 
im_orconst( IMAGE *in, IMAGE *out, double c )
{
	double *v = make_pixel_const( in, out, c );

	return( !v || im_or_vec( in, out, in->Bands, v ) );
}

int 
im_eorconst( IMAGE *in, IMAGE *out, double c )
{
	double *v = make_pixel_const( in, out, c );

	return( !v || im_eor_vec( in, out, in->Bands, v ) );
}

/* Assorted shift operations.
 */
#define SHIFTL( TYPE ) { \
	TYPE *pt = (TYPE *) p;\
	TYPE *qt = (TYPE *) q;\
	\
	for( x = 0; x < ne; x++ )\
		qt[x] = pt[x] << n;\
}

/* The above as buffer ops.
 */
static void
shiftleft_buffer( PEL *p, PEL *q, int len, IMAGE *in, int n )
{
	int x;
	int ne = len * in->Bands;

        switch( in->BandFmt ) {
        case IM_BANDFMT_UCHAR: 	SHIFTL( unsigned char ); break;
        case IM_BANDFMT_CHAR: 	SHIFTL( signed char ); break; 
        case IM_BANDFMT_USHORT:	SHIFTL( unsigned short ); break; 
        case IM_BANDFMT_SHORT: 	SHIFTL( signed short ); break; 
        case IM_BANDFMT_UINT: 	SHIFTL( unsigned int ); break; 
        case IM_BANDFMT_INT: 	SHIFTL( signed int );  break; 

	default:
		error_exit( "im_shiftleft: internal error" );
		/*NOTREACHED*/
	}
}

/* The above as im_*() functions.
 */
int 
im_shiftleft( IMAGE *in, IMAGE *out, int n )
{
	IMAGE *invec[2];

	invec[0] = in; invec[1] = NULL;
	if( check( invec, out ) )
		return( -1 );
	in = invec[0];

	if( im_wrapone( in, out, 
		(im_wrapone_fn) shiftleft_buffer, in, GINT_TO_POINTER( n ) ) )
		return( -1 );

	return( 0 );
}

#define SHIFTR( TYPE ) { \
	TYPE *pt = (TYPE *) p; \
	TYPE *qt = (TYPE *) q; \
	\
	for( x = 0; x < ne; x++ ) \
		qt[x] = pt[x] >> n; \
}

static void
shiftright_buffer( PEL *p, PEL *q, int len, IMAGE *in, int n )
{
	int x;
	int ne = len * in->Bands;

        switch( in->BandFmt ) {
        case IM_BANDFMT_UCHAR: 	SHIFTR( unsigned char ); break;
        case IM_BANDFMT_CHAR: 	SHIFTR( signed char ); break; 
        case IM_BANDFMT_USHORT:	SHIFTR( unsigned short ); break; 
        case IM_BANDFMT_SHORT: 	SHIFTR( signed short ); break; 
        case IM_BANDFMT_UINT: 	SHIFTR( unsigned int ); break; 
        case IM_BANDFMT_INT: 	SHIFTR( signed int );  break; 

	default:
		error_exit( "im_shiftright: internal error" );
		/*NOTREACHED*/
	}
}

int 
im_shiftright( IMAGE *in, IMAGE *out, int n )
{
	IMAGE *invec[2];

	invec[0] = in; invec[1] = NULL;
	if( check( invec, out ) )
		return( -1 );
	in = invec[0];

	if( im_wrapone( in, out, 
		(im_wrapone_fn) shiftright_buffer, in, GINT_TO_POINTER( n ) ) )
		return( -1 );

	return( 0 );
}