// Object part of VMask class

/*

    Copyright (C) 1991-2001 The National Gallery

    This program 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

 */

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

#include <cstdlib>
#include <cmath>

#include <vips/vips.h>

#include <vips/vipscpp.h>

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

VIPS_NAMESPACE_START

/* Functions for VMask - refcounting layer over VPMask.
 */

VMask::~VMask()
{
	ref->nrefs--;
	if( !ref->nrefs )
		delete ref;
}

VMask &VMask::operator=( const VMask &a )
{ 
	// Loosing ref to LHS
	ref->nrefs--;

	if( ref->nrefs > 0 )
		// Need fresh refblock
		ref = new refblock;
	else 
		// Recycle old refblock
		delete ref->pmask;

	// LHS now points to RHS
	ref = a.ref; 
	ref->nrefs++; 
	
	return( *this ); 
}

// Make sure this is a private copy of pmask --- dup if nrefs != 1
void VMask::make_private()
{
	if( ref->nrefs > 1 ) {
		// Make fresh refblock
		refblock *ref2 = new refblock;

		// And copy the mask
		ref2->pmask = ref->pmask->dup();
		ref->nrefs--;
		ref = ref2;
	}
}

void VMask::ostream_print( std::ostream &file ) const
{
	file << *(ref->pmask);
}

// Embed INTMASK in VIMask
void VIMask::embed( INTMASK *i ) throw( VError )
{
	if( ref->pmask )
		verror( "embed: VIMask not empty" );
	ref->pmask = new _private_detail::VPIMask( i );
}

// Type conversions: implicit INTMASK to DOUBLEMASK 
VIMask::operator VDMask()
{
	VDMask out( xsize(), ysize() );

	out.mask().dptr->scale = scale();
	out.mask().dptr->offset = offset();

	for( int i = 0; i < size(); i++ )
		out[i] = (*this)[i];

	return( out );
}


// Forward ref of VImage class
class VImage;

// Type conversions: implicit DOUBLEMASK to INTMASK
VDMask::operator VIMask()
{
	VIMask out( xsize(), ysize() );

	out.mask().iptr->scale = int( scale() );
	out.mask().iptr->offset = int( offset() );

	for( int i = 0; i < size(); i++ )
		out[i] = (int) rint( (*this)[i] );

	return( out );
}

// Type conversions: implicit DOUBLEMASK to VImage
VDMask::operator VImage() throw( VError )
{
	VImage out;

	if( im_mask2vips( mask().dptr, out.image() ) )
		verror();

	return( out );
}

// ... and INTMASK to VImage
VIMask::operator VImage() { return( VImage( VDMask( *this ) ) ); }

// Embed DOUBLEMASK in VDMask
void VDMask::embed( DOUBLEMASK *i ) throw( VError )
{
	if( ref->pmask )
		verror( "embed: VDMask not empty" );
	ref->pmask = new _private_detail::VPDMask( i );
}

/* Functions for P*Mask - layer over im_*_*mask() functions.
 */

// Create empty imask
_private_detail::VPIMask::VPIMask( int xsize, int ysize ) throw( VError )
{
	if( !(data.iptr = im_create_imask( "VPIMask::VPIMask", xsize, ysize )) )
		verror();
	type = _private_detail::VPMask::INT;
}

// Init from vector
_private_detail::VPIMask::VPIMask( int xsize, int ysize, 
	int scale, int offset, std::vector<int> coeff )
	throw( VError )
{
	int i;

	if( !(data.iptr = im_create_imask( "VPIMask::VPIMask", xsize, ysize )) )
		verror();
	type = _private_detail::VPMask::INT;

	data.iptr->scale = scale;
	data.iptr->offset = offset;
	for( i = 0; i < xsize * ysize; i++ )
		data.iptr->coeff[i] = coeff[i];
}

// Create from filename
_private_detail::VPIMask::VPIMask( const char *name ) throw( VError )
{
	if( !(data.iptr = im_read_imask( (char *) name )) )
		verror();
	type = _private_detail::VPMask::INT;
}

// Create from existing INTMASK
_private_detail::VPIMask::VPIMask( INTMASK *imask )
{
	data.iptr = imask;
	type = _private_detail::VPMask::INT;
}

// Create empty
_private_detail::VPIMask::VPIMask()
{
	data.iptr = 0;
	type = _private_detail::VPMask::UNASSIGNED;
}

_private_detail::VPIMask::~VPIMask()
{
	if( data.iptr ) {
		im_free_imask( data.iptr );
		data.iptr = 0;
		type = _private_detail::VPMask::UNASSIGNED;
	}
}

// Duplicate -- we are a VPIMask, return a new VPIMask which is a copy of us.
// Return as a VPMask tho'.
_private_detail::VPMask *_private_detail::VPIMask::dup() const throw( VError )
{
	_private_detail::VPIMask *out = new _private_detail::VPIMask();

	INTMASK *msk;
	if( !(msk = im_dup_imask( data.iptr, "VPIMask::dup" )) ) {
		delete out;
		verror();
	}
	out->embed( msk );

	return( out );
}

// Insert INTMASK pointer
void _private_detail::VPIMask::embed( INTMASK *msk ) throw( VError )
{
	if( type != _private_detail::VPMask::UNASSIGNED )
		verror( "VPIMask::embed: VPIMask not empty" );

	data.iptr = msk;
	type = _private_detail::VPMask::INT;
}

int _private_detail::VPIMask::xsize() const throw( VError )
{
	if( !data.iptr ) 
		verror( "xsize: mask not set" );

	return( data.iptr->xsize );
}

int _private_detail::VPIMask::ysize() const throw( VError )
{
	if( !data.iptr ) 
		verror( "ysize: mask not set" );

	return( data.iptr->ysize );
}

int _private_detail::VPIMask::scale() const throw( VError )
{
	if( !data.iptr ) 
		verror( "scale: mask not set" );

	return( data.iptr->scale );
}

int _private_detail::VPIMask::offset() const throw( VError )
{
	if( !data.iptr ) 
		verror( "offset: mask not set" );

	return( data.iptr->offset );
}

const char *_private_detail::VPIMask::filename() const throw( VError )
{
	if( !data.iptr ) 
		verror( "filename: mask not set" );

	return( data.iptr->filename );
}

void _private_detail::VPIMask::ostream_print( std::ostream &file ) const 
	throw( VError )
{
	if( !data.iptr )
		verror( "internal error #7447234" );
	
	int i, j;
	int *p = data.iptr->coeff;

	file << this->xsize() << "\t" << this->ysize() << "\t";
	file << this->scale() << "\t" << this->offset() << "\n";

	for( i = 0; i < this->ysize(); i++ ) {
		for( j = 0; j < this->xsize(); j++ )
			file << *p++ << "\t";

		file << "\n";
	}
}

// Extract start of int array
int *_private_detail::VPIMask::array() const 
{ 
	return( data.iptr->coeff ); 
}

// Create empty dmask
_private_detail::VPDMask::VPDMask( int xsize, int ysize ) throw( VError )
{
	if( !(data.dptr = im_create_dmask( "VPDMask::VPDMask", xsize, ysize )) )
		verror();
	type = _private_detail::VPMask::DOUBLE;
}

// Create from vector
_private_detail::VPDMask::VPDMask( int xsize, int ysize, 
	double scale, double offset, std::vector<double> coeff ) throw( VError )
{
	int i;

	if( !(data.dptr = im_create_dmask( "VPDMask::VPDMask", xsize, ysize )) )
		verror();
	type = _private_detail::VPMask::DOUBLE;

	data.dptr->scale = scale;
	data.dptr->offset = offset;
	for( i = 0; i < xsize * ysize; i++ )
		data.dptr->coeff[i] = coeff[i];
}

// Create from filename
_private_detail::VPDMask::VPDMask( const char *name ) throw( VError )
{
	if( !(data.dptr = im_read_dmask( (char *) name )) )
		verror();
	type = _private_detail::VPMask::DOUBLE;
}

// Create empty
_private_detail::VPDMask::VPDMask()
{
	data.dptr = 0;
	type = _private_detail::VPMask::UNASSIGNED;
}

// Create from existing DOUBLEMASK
_private_detail::VPDMask::VPDMask( DOUBLEMASK *dmask )
{
	data.dptr = dmask;
	type = _private_detail::VPMask::DOUBLE;
}

_private_detail::VPDMask::~VPDMask()
{
	if( data.dptr ) {
		im_free_dmask( data.dptr );
		data.dptr = 0;
		type = _private_detail::VPMask::UNASSIGNED;
	}
}

// Duplicate -- we are a VPIMask, return a new VPIMask which is a copy of us.
// Return as a VPMask tho'.
_private_detail::VPMask *_private_detail::VPDMask::dup() const throw( VError )
{
	_private_detail::VPDMask *out = new _private_detail::VPDMask();

	DOUBLEMASK *msk;
	if( !(msk = im_dup_dmask( data.dptr, "VPDMask::dup" )) ) {
		delete out;
		verror();
	}
	out->embed( msk );

	return( out );
}

// Insert DOUBLEMASK pointer
void _private_detail::VPDMask::embed( DOUBLEMASK *msk ) throw( VError )
{
	if( type != _private_detail::VPMask::UNASSIGNED )
		verror( "VPDMask::embed: VPDMask not empty" );

	data.dptr = msk;
	type = _private_detail::VPMask::DOUBLE;
}

int _private_detail::VPDMask::xsize() const throw( VError )
{
	if( !data.dptr ) 
		verror( "xsize: mask not set" );

	return( data.dptr->xsize );
}

int _private_detail::VPDMask::ysize() const throw( VError )
{
	if( !data.dptr ) 
		verror( "ysize: mask not set" );

	return( data.dptr->ysize );
}

double _private_detail::VPDMask::scale() const throw( VError )
{
	if( !data.dptr ) 
		verror( "scale: mask not set" );

	return( data.dptr->scale );
}

double _private_detail::VPDMask::offset() const throw( VError )
{
	if( !data.dptr ) 
		verror( "offset: mask not set" );

	return( data.dptr->offset );
}

const char *_private_detail::VPDMask::filename() const throw( VError )
{
	if( !data.dptr ) 
		verror( "filename: mask not set" );

	return( data.dptr->filename );
}

void _private_detail::VPDMask::ostream_print( std::ostream &file ) const 
	throw( VError )
{
	if( !data.dptr )
		verror( "internal error #7447234" );
	
	int i, j;
	double *p = data.dptr->coeff;

	file << this->xsize() << "\t" << this->ysize() << "\t";
	file << this->scale() << "\t" << this->offset() << "\n";

	for( i = 0; i < this->ysize(); i++ ) {
		for( j = 0; j < this->xsize(); j++ )
			file << *p++ << "\t";

		file << "\n";
	}
}

// Extract data pointer
double *_private_detail::VPDMask::array() const 
{ 
	return( data.dptr->coeff ); 
}

// Build functions
VIMask VIMask::gauss( double sig, double minamp ) throw( VError )
{
	VIMask out;
	INTMASK *msk;

	if( !(msk = im_gauss_imask( "VIMask::gauss", sig, minamp )) )
		verror();
	out.embed( msk );

	return( out );
}

VIMask VIMask::gauss_sep( double sig, double minamp ) throw( VError )
{
	VIMask out;
	INTMASK *msk;

	if( !(msk = im_gauss_imask_sep( "VIMask::gauss", sig, minamp )) )
		verror();
	out.embed( msk );

	return( out );
}

VDMask VDMask::gauss( double sig, double minamp ) throw( VError )
{
	VDMask out;
	DOUBLEMASK *msk;

	if( !(msk = im_gauss_dmask( "VDMask::gauss", sig, minamp )) )
		verror();
	out.embed( msk );

	return( out );
}

VIMask VIMask::log( double sig, double minamp ) throw( VError )
{
	VIMask out;
	INTMASK *msk;

	if( !(msk = im_log_imask( "VIMask::log", sig, minamp )) )
		verror();
	out.embed( msk );

	return( out );
}

VDMask VDMask::log( double sig, double minamp ) throw( VError )
{
	VDMask out;
	DOUBLEMASK *msk;

	if( !(msk = im_log_dmask( "VDMask::log", sig, minamp )) )
		verror();
	out.embed( msk );

	return( out );
}

// Manipulation functions
VIMask VIMask::rotate45() throw( VError )
{
	VIMask out;
	INTMASK *msk;

	if( !(msk = im_rotate_imask45( mask().iptr, "VIMask::rotate45" )) )
		verror();
	out.embed( msk );

	return( out );
}

VIMask VIMask::rotate90() throw( VError )
{
	VIMask out;
	INTMASK *msk;

	if( !(msk = im_rotate_imask90( mask().iptr, "VIMask::rotate90" )) )
		verror();
	out.embed( msk );

	return( out );
}

VDMask VDMask::rotate45() throw( VError )
{
	VDMask out;
	DOUBLEMASK *msk;

	if( !(msk = im_rotate_dmask45( mask().dptr, "VDMask::rotate45" )) )
		verror();
	out.embed( msk );

	return( out );
}

VDMask VDMask::rotate90() throw( VError )
{
	VDMask out;
	DOUBLEMASK *msk;

	if( !(msk = im_rotate_dmask90( mask().dptr, "VDMask::rotate90" )) )
		verror();
	out.embed( msk );

	return( out );
}

VDMask VDMask::trn() throw( VError )
{
	VDMask out;
	DOUBLEMASK *msk;

	if( !(msk = im_mattrn( mask().dptr, "VDMask::trn" )) )
		verror();
	out.embed( msk );

	return( out );
}

VDMask VDMask::inv() throw( VError )
{
	VDMask out;
	DOUBLEMASK *msk;

	if( !(msk = im_matinv( mask().dptr, "VDMask::inv" )) )
		verror();
	out.embed( msk );

	return( out );
}

VDMask VDMask::mul( VDMask m ) throw( VError )
{
	VDMask out;
	DOUBLEMASK *msk;

	if( !(msk = im_matmul( mask().dptr, m.mask().dptr, "VDMask::mul" )) )
		verror();
	out.embed( msk );

	return( out );
}

VDMask VDMask::cat( VDMask m ) throw( VError )
{
	VDMask out;
	DOUBLEMASK *msk;

	if( !(msk = im_matcat( mask().dptr, m.mask().dptr, "VDMask::cat" )) )
		verror();
	out.embed( msk );

	return( out );
}

VIMask VDMask::scalei() throw( VError )
{
	VIMask out;
	INTMASK *msk;

	if( !(msk = im_scale_dmask( mask().dptr, "VDMask::scalei" )) )
		verror();
	out.embed( msk );

	return( out );
}

// Arithmetic on a VIMask ... just cast and use VDMask
VDMask VIMask::trn() throw( VError ) 
	{ return( ((VDMask)*this).trn() ); }
VDMask VIMask::inv() throw( VError ) 
	{ return( ((VDMask)*this).inv() ); }
VDMask VIMask::cat( VDMask a ) throw( VError ) 
	{ return( ((VDMask)*this).cat( a ) ); }
VDMask VIMask::mul( VDMask a ) throw( VError ) 
	{ return( ((VDMask)*this).mul( a ) ); }

// Overload [] to get linear array subscript.
// Our caller may write to the result, so make sure we have a private
// copy.
// Involves function call, slow anyway, so do range checking
int &VIMask::operator[]( int x ) throw( VError )
{ 
	if( ref->nrefs != 1 )
		make_private();

	if( x > size() )
		verror( "VIMask::operator[]: subscript out of range" );

	return( ((_private_detail::VPIMask *)ref->pmask)->array()[x] ); 
}

double &VDMask::operator[]( int x ) throw( VError )
{ 
	if( ref->nrefs != 1 )
		make_private();

	if( x > size() )
		verror( "VDMask::operator[]: subscript out of range" );

	return( ((_private_detail::VPDMask *)ref->pmask)->array()[x] ); 
}

VIPS_NAMESPACE_END