/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---- includes ----------------------------------------------------------- */

#include "b_TensorEm/RBFMap2D.h"
#include "b_BasicEm/Math.h"
#include "b_BasicEm/Memory.h"
#include "b_BasicEm/Functions.h"

/* ------------------------------------------------------------------------- */

/* ========================================================================= */
/*                                                                           */
/* ---- \ghd{ auxiliary functions } ---------------------------------------- */
/*                                                                           */
/* ========================================================================= */

/* ------------------------------------------------------------------------- */

/* ========================================================================= */
/*                                                                           */
/* ---- \ghd{ constructor / destructor } ----------------------------------- */
/*                                                                           */
/* ========================================================================= */

/* ------------------------------------------------------------------------- */

void bts_RBFMap2D_init( struct bbs_Context* cpA,
					    struct bts_RBFMap2D* ptrA )
{
	ptrA->RBFTypeE = bts_RBF_LINEAR;
	bts_Cluster2D_init( cpA, &ptrA->srcClusterE );
	bts_Cluster2D_init( cpA, &ptrA->rbfCoeffClusterE );
	ptrA->altTypeE = bts_ALT_LINEAR;
	bts_Flt16Alt2D_init( &ptrA->altE );

	ptrA->altOnlyE = FALSE;

	bts_Int32Mat_init( cpA, &ptrA->matE );
	bts_Int32Mat_init( cpA, &ptrA->tempMatE );
	bbs_Int32Arr_init( cpA, &ptrA->inVecE );
	bbs_Int32Arr_init( cpA, &ptrA->outVecE );
	bbs_Int32Arr_init( cpA, &ptrA->tempVecE );
}

/* ------------------------------------------------------------------------- */

void bts_RBFMap2D_exit( struct bbs_Context* cpA,
					    struct bts_RBFMap2D* ptrA )
{
	ptrA->RBFTypeE = bts_RBF_LINEAR;
	bts_Cluster2D_exit( cpA, &ptrA->srcClusterE );
	bts_Cluster2D_exit( cpA, &ptrA->rbfCoeffClusterE );
	ptrA->altTypeE = bts_ALT_LINEAR;
	bts_Flt16Alt2D_exit( &ptrA->altE );

	ptrA->altOnlyE = FALSE;

	bts_Int32Mat_exit( cpA, &ptrA->matE );
	bts_Int32Mat_exit( cpA, &ptrA->tempMatE );
	bbs_Int32Arr_exit( cpA, &ptrA->inVecE );
	bbs_Int32Arr_exit( cpA, &ptrA->outVecE );
	bbs_Int32Arr_exit( cpA, &ptrA->tempVecE );
}

/* ------------------------------------------------------------------------- */

/* ========================================================================= */
/*                                                                           */
/* ---- \ghd{ operators } -------------------------------------------------- */
/*                                                                           */
/* ========================================================================= */

/* ------------------------------------------------------------------------- */

void bts_RBFMap2D_copy( struct bbs_Context* cpA,
					    struct bts_RBFMap2D* ptrA, 
						const struct bts_RBFMap2D* srcPtrA )
{
	ptrA->RBFTypeE = srcPtrA->RBFTypeE;
	bts_Cluster2D_copy( cpA, &ptrA->srcClusterE, &srcPtrA->srcClusterE );
	bts_Cluster2D_copy( cpA, &ptrA->rbfCoeffClusterE, &srcPtrA->rbfCoeffClusterE );
	ptrA->altTypeE = srcPtrA->altTypeE;
	bts_Flt16Alt2D_copy( &ptrA->altE, &srcPtrA->altE );
}

/* ------------------------------------------------------------------------- */

flag bts_RBFMap2D_equal( struct bbs_Context* cpA,
						 const struct bts_RBFMap2D* ptrA, 
						 const struct bts_RBFMap2D* srcPtrA )
{
	if( ptrA->RBFTypeE != srcPtrA->RBFTypeE ) return FALSE;
	if( ! bts_Cluster2D_equal( cpA, &ptrA->srcClusterE, &srcPtrA->srcClusterE ) ) return FALSE;
	if( ! bts_Cluster2D_equal( cpA, &ptrA->rbfCoeffClusterE, &srcPtrA->rbfCoeffClusterE ) ) return FALSE;
	if( ptrA->altTypeE != srcPtrA->altTypeE ) return FALSE;
	if( ! bts_Flt16Alt2D_equal( &ptrA->altE, &srcPtrA->altE ) ) return FALSE;
	return TRUE;
}

/* ------------------------------------------------------------------------- */

/* ========================================================================= */
/*                                                                           */
/* ---- \ghd{ query functions } -------------------------------------------- */
/*                                                                           */
/* ========================================================================= */

/* ------------------------------------------------------------------------- */

/* ========================================================================= */
/*                                                                           */
/* ---- \ghd{ modify functions } ------------------------------------------- */
/*                                                                           */
/* ========================================================================= */

/* ------------------------------------------------------------------------- */
	
void bts_RBFMap2D_create( struct bbs_Context* cpA,
						  struct bts_RBFMap2D* ptrA,
						  uint32 sizeA,
				          struct bbs_MemSeg* mspA )
{
	if( bbs_Context_error( cpA ) ) return;
	bts_Cluster2D_create( cpA, &ptrA->srcClusterE, sizeA, mspA );
	bts_Cluster2D_create( cpA, &ptrA->rbfCoeffClusterE, sizeA, mspA );

	bts_Int32Mat_create( cpA, &ptrA->matE, sizeA, mspA );
	bts_Int32Mat_create( cpA, &ptrA->tempMatE, sizeA, mspA );
	bbs_Int32Arr_create( cpA, &ptrA->inVecE, sizeA, mspA );
	bbs_Int32Arr_create( cpA, &ptrA->outVecE, sizeA, mspA );
	bbs_Int32Arr_create( cpA, &ptrA->tempVecE, sizeA, mspA );
}

/* ------------------------------------------------------------------------- */

void bts_RBFMap2D_compute( struct bbs_Context* cpA,
						   struct bts_RBFMap2D* ptrA,
						   const struct bts_Cluster2D* srcPtrA,
						   const struct bts_Cluster2D* dstPtrA )
{
	const uint32 sizeL = srcPtrA->sizeE;
	int32 bbp_internalL = 15;
	int32 bbp_rbfCoeffL = 12;

	int32 internalShiftL = bbp_internalL - srcPtrA->bbpE;
	int32 rbfCoeffShiftL;

	uint32 iL, jL;

	if( dstPtrA->sizeE != srcPtrA->sizeE )
	{
		bbs_ERROR2( "void bts_RBFMap2D_compute( ... ): size mismatch, src cluster has size %d,"
			"but dst cluster has size %d\n", srcPtrA->sizeE, dstPtrA->sizeE );
		return;
	}

	ptrA->altOnlyE = FALSE;

	/* if bbp of src cluster should be larger than bbp_internal, use it instead */
	if( internalShiftL < 0 )
	{
		internalShiftL = 0;
		bbp_internalL = srcPtrA->bbpE;
	}

	/* also checks for sizeL > allocated size */
	bts_Cluster2D_size( cpA, &ptrA->rbfCoeffClusterE, sizeL );

	/* set rbf coefficients to 0 in case they don't get computed */
	for( iL =0; iL < sizeL; iL++ )
	{
		ptrA->rbfCoeffClusterE.vecArrE[ iL ].xE = 0;
		ptrA->rbfCoeffClusterE.vecArrE[ iL ].yE = 0;
	}

	/* 1. Compute rigid transformation: if cluster size == 0 returns identity */
	ptrA->altE = bts_Cluster2D_alt( cpA, srcPtrA, dstPtrA, ptrA->altTypeE );

	/* if cluster size is less than 3 affine trafo covers whole transformation */
	if( sizeL < 3 )
	{
		bts_Cluster2D_copy( cpA, &ptrA->srcClusterE, srcPtrA );
		ptrA->altOnlyE = TRUE;
		return;
	}

	/* 2. Compute RBF trafo */
	ptrA->matE.widthE = sizeL;
	ptrA->tempMatE.widthE = sizeL;
	
	/* Set up linear matrix to invert */
	switch( ptrA->RBFTypeE )
	{
		case bts_RBF_IDENTITY:
		{
			return;
		}

		case bts_RBF_LINEAR:
		{
			/* ||r|| */
			for( iL = 0; iL < sizeL; iL++ )
			{
				struct bts_Int16Vec2D vec0L = srcPtrA->vecArrE[ iL ];
				int32* ptrL = ptrA->matE.arrE.arrPtrE + iL * sizeL;

				/* set diagonal elements having null distance */
				*( ptrL + iL ) = 0;

				for( jL = 0; jL < iL; jL++ )	/* use symmetry */
				{
					int32 normL = 0;
					struct bts_Int16Vec2D vecL = srcPtrA->vecArrE[ jL ];
					vecL.xE -= vec0L.xE;
					vecL.yE -= vec0L.yE;
					normL = bts_Int16Vec2D_norm( &vecL );
					*ptrL++ = normL << internalShiftL;
				}
			}
		}
		break;

		/* Add a new RBF type here */

		default:
		{
			bbs_ERROR1( "void bts_RBFMap2D_compute( ... ): RBFType %d is not handled\n", ptrA->RBFTypeE );
			return;
		}
	}

	/* use symmetry: set symmetric elements in matrix */
	for( iL = 0; iL < sizeL; iL++ )
	{
		int32* basePtrL = ptrA->matE.arrE.arrPtrE;
		uint32 jL;
		for( jL = iL + 1; jL < sizeL; jL++ )
		{
			*( basePtrL + iL * sizeL + jL ) = *( basePtrL + jL * sizeL + iL );
		}
	}

	/* Precompute alt transformed cluster, srcClusterE will be restored at the end */
	bts_Cluster2D_copy( cpA, &ptrA->srcClusterE, srcPtrA );
	bts_Cluster2D_transformBbp( cpA, &ptrA->srcClusterE, ptrA->altE, dstPtrA->bbpE );

	bbs_Int32Arr_size( cpA, &ptrA->inVecE, sizeL );
	bbs_Int32Arr_size( cpA, &ptrA->outVecE, sizeL );
	bbs_Int32Arr_size( cpA, &ptrA->tempVecE, sizeL );

	{
		flag successL;

		/* compute right side vector of linear system to be solved, for x */
		int32* inPtrL = ptrA->inVecE.arrPtrE;
		struct bts_Int16Vec2D* dstVecL = dstPtrA->vecArrE;
		struct bts_Int16Vec2D* altVecL = ptrA->srcClusterE.vecArrE;

		int32 shiftL = srcPtrA->bbpE - ptrA->srcClusterE.bbpE + internalShiftL;
		if( shiftL >= 0 )
		{
			for( iL = 0; iL < sizeL; iL++ ) inPtrL[ iL ] = ( int32 )( dstVecL[ iL ].xE - altVecL[ iL ].xE ) << shiftL;
		}
		else
		{
			for( iL = 0; iL < sizeL; iL++ ) inPtrL[ iL ] = ( ( ( int32 )( dstVecL[ iL ].xE - altVecL[ iL ].xE ) >> ( ( -shiftL ) - 1 ) ) + 1 ) >> 1;
		}

		/* solve linear system in x */
		successL = bts_Int32Mat_solve(  cpA, 
			                            ptrA->matE.arrE.arrPtrE,
										sizeL,
										ptrA->inVecE.arrPtrE,
										ptrA->outVecE.arrPtrE,
										bbp_internalL,
										ptrA->tempMatE.arrE.arrPtrE,
										ptrA->tempVecE.arrPtrE );

		/* no error condition here! system must be failsafe */
		if( !successL ) ptrA->altOnlyE = TRUE;

		/* store rbf coefficients, x component */
		rbfCoeffShiftL = bbp_internalL - bbp_rbfCoeffL;
		for( iL = 0; iL < sizeL; iL++ )
		{
			int32 rbfCoeffL = ptrA->outVecE.arrPtrE[ iL ] >> rbfCoeffShiftL;
			if( rbfCoeffL < -32768 || rbfCoeffL > 32767 ) ptrA->altOnlyE = TRUE; /* check for overflow */
			ptrA->rbfCoeffClusterE.vecArrE[ iL ].xE = rbfCoeffL;
		}


		/* compute right side vector of linear system to be solved, for y */
		if( shiftL >= 0 )
		{
			for( iL = 0; iL < sizeL; iL++ ) inPtrL[ iL ] = ( int32 )( dstVecL[ iL ].yE - altVecL[ iL ].yE ) << shiftL;
		}
		else
		{
			for( iL = 0; iL < sizeL; iL++ ) inPtrL[ iL ] = ( ( ( int32 )( dstVecL[ iL ].yE - altVecL[ iL ].yE ) >> ( ( -shiftL ) - 1 ) ) + 1 ) >> 1;
		}

		/* solve linear system in y */
		successL = bts_Int32Mat_solve(  cpA, 
			                            ptrA->matE.arrE.arrPtrE,
										sizeL,
										ptrA->inVecE.arrPtrE,
										ptrA->outVecE.arrPtrE,
										bbp_internalL,
										ptrA->tempMatE.arrE.arrPtrE,
										ptrA->tempVecE.arrPtrE );
		if( !successL )
		{
			/* no error condition here! system must be failsafe */
			ptrA->altOnlyE = TRUE;
		}

		/* store rbf coefficients, y component */
		for( iL = 0; iL < sizeL; iL++ )
		{
			int32 rbfCoeffL = ptrA->outVecE.arrPtrE[ iL ] >> rbfCoeffShiftL;
			if( rbfCoeffL < -32768 || rbfCoeffL > 32767 ) ptrA->altOnlyE = TRUE; /* check for overflow */
			ptrA->rbfCoeffClusterE.vecArrE[ iL ].yE = rbfCoeffL;
		}

		/* set bbp of coeff cluster */
		ptrA->rbfCoeffClusterE.bbpE = bbp_rbfCoeffL;
	}

	/** after having used srcClusterE for temporary storage of the alt src cluster,
		restore the orig src cluster as needed for the RBF trafo */
	bts_Cluster2D_copy( cpA, &ptrA->srcClusterE, srcPtrA );
}

/* ------------------------------------------------------------------------- */
	
/* ========================================================================= */
/*                                                                           */
/* ---- \ghd{ I/O } -------------------------------------------------------- */
/*                                                                           */
/* ========================================================================= */

/* ------------------------------------------------------------------------- */
	
uint32 bts_RBFMap2D_memSize( struct bbs_Context* cpA,
							 const struct bts_RBFMap2D *ptrA )
{
	return  bbs_SIZEOF16( uint32 )
		  + bbs_SIZEOF16( uint32 ) /* version */
		  + bbs_SIZEOF16( ptrA->RBFTypeE )
		  + bts_Cluster2D_memSize( cpA, &ptrA->srcClusterE )
		  + bts_Cluster2D_memSize( cpA, &ptrA->rbfCoeffClusterE )
		  + bbs_SIZEOF16( ptrA->altTypeE ) 
		  + bts_Flt16Alt2D_memSize( cpA, &ptrA->altE );
}

/* ------------------------------------------------------------------------- */
	
uint32 bts_RBFMap2D_memWrite( struct bbs_Context* cpA,
							  const struct bts_RBFMap2D* ptrA, 
							  uint16* memPtrA )
{
	uint32 memSizeL = bts_RBFMap2D_memSize( cpA, ptrA );
	memPtrA += bbs_memWrite32( &memSizeL, memPtrA );
	memPtrA += bbs_memWriteUInt32( bts_IRBFMAP2D_VERSION, memPtrA );
	memPtrA += bbs_memWrite32( &ptrA->RBFTypeE, memPtrA );
	memPtrA += bts_Cluster2D_memWrite( cpA, &ptrA->srcClusterE, memPtrA );
	memPtrA += bts_Cluster2D_memWrite( cpA, &ptrA->rbfCoeffClusterE, memPtrA );
	memPtrA += bbs_memWrite32( &ptrA->altTypeE, memPtrA );
	memPtrA += bts_Flt16Alt2D_memWrite( cpA, &ptrA->altE, memPtrA );
	return memSizeL;
}

/* ------------------------------------------------------------------------- */
	
uint32 bts_RBFMap2D_memRead( struct bbs_Context* cpA,
							 struct bts_RBFMap2D* ptrA, 
							 const uint16* memPtrA,
				             struct bbs_MemSeg* mspA )
{
	uint32 memSizeL, versionL;
	if( bbs_Context_error( cpA ) ) return 0;
	memPtrA += bbs_memRead32( &memSizeL, memPtrA );
	memPtrA += bbs_memReadVersion32( cpA, &versionL, bts_IRBFMAP2D_VERSION, memPtrA );
	memPtrA += bbs_memRead32( &ptrA->RBFTypeE, memPtrA );
	memPtrA += bts_Cluster2D_memRead( cpA, &ptrA->srcClusterE, memPtrA, mspA );
	memPtrA += bts_Cluster2D_memRead( cpA, &ptrA->rbfCoeffClusterE, memPtrA, mspA );
	memPtrA += bbs_memRead32( &ptrA->altTypeE, memPtrA );
	memPtrA += bts_Flt16Alt2D_memRead( cpA, &ptrA->altE, memPtrA );

	bts_Int32Mat_create( cpA, &ptrA->matE, ptrA->srcClusterE.sizeE, mspA );
	bts_Int32Mat_create( cpA, &ptrA->tempMatE, ptrA->srcClusterE.sizeE, mspA );

	if( memSizeL != bts_RBFMap2D_memSize( cpA, ptrA ) )
	{
		bbs_ERR0( bbs_ERR_CORRUPT_DATA, "uint32 bts_RBFMap2D_memRead( ... ): size mismatch\n" ); 
		return 0;
	}
	return memSizeL;
}

/* ------------------------------------------------------------------------- */
	
/* ========================================================================= */
/*                                                                           */
/* ---- \ghd{ exec functions } --------------------------------------------- */
/*                                                                           */
/* ========================================================================= */

/* ------------------------------------------------------------------------- */
/**	R, A are rbf and A affine linear transformations 
 *	T( x ) = R( x ) + A( x )
 */
struct bts_Flt16Vec2D bts_RBFMap2D_mapVector( struct bbs_Context* cpA,
											  const struct bts_RBFMap2D* ptrA,
											  struct bts_Flt16Vec2D vecA )
{
	const uint32 sizeL = ptrA->srcClusterE.sizeE;
	const int32 bbp_internalL = ptrA->rbfCoeffClusterE.bbpE;
	uint32 iL;
	int32 shL;

	int32 outXL;
	int32 outYL;
	int32 outBbpL;

	/* 1. Compute rigid transformation, i.e. A( x ) */
	struct bts_Flt16Vec2D altVecL = bts_Flt16Alt2D_mapFlt( &ptrA->altE, &vecA );

	/* compute output on 32 bit here to prevent temporary overflows (j.s.) */
	outXL   = altVecL.xE;
	outYL   = altVecL.yE;
	outBbpL = altVecL.bbpE;

	/* if bbp was altered, change it back to bbp of vecA ( det A is always close to 1 here ) */
	shL = vecA.bbpE - outBbpL;
	if( shL > 0 )
	{
		outXL <<= shL;
		outYL <<= shL;
	}
	else if( shL < 0 )
	{
		outXL = ( ( outXL >> ( -shL - 1 ) ) + 1 ) >> 1;
		outYL = ( ( outYL >> ( -shL - 1 ) ) + 1 ) >> 1;
	}
	outBbpL = vecA.bbpE;

	/* stop here if rbf coefficients could not be computed  */
	if( ptrA->altOnlyE )
	{
		return bts_Flt16Vec2D_create32( outXL, outYL, outBbpL );
	}

	/* 2. Compute RBF transformation, i.e. R( x ) depending on type */
	switch( ptrA->RBFTypeE )
    {
		case bts_RBF_IDENTITY:
		break;

        case bts_RBF_LINEAR:
        {
			int32 xSumL = 0;
			int32 ySumL = 0;
			int32 internalShiftL = bbp_internalL - ptrA->srcClusterE.bbpE;

			/* first adapt vecA to bbp of srcCluster */
			int32 xL = vecA.xE;
			int32 yL = vecA.yE;
			int32 shiftL = ptrA->srcClusterE.bbpE - vecA.bbpE;
			if( shiftL > 0 )
			{
				xL <<= shiftL;
				yL <<= shiftL;
			}
			else if( shiftL < 0 )
			{
				xL = ( ( xL >> ( -shiftL - 1 ) ) + 1 ) >> 1;
				yL = ( ( yL >> ( -shiftL - 1 ) ) + 1 ) >> 1;
			}

			shiftL = ptrA->srcClusterE.bbpE;

            for( iL = 0; iL < sizeL; iL++ )
            {
				struct bts_Int16Vec2D vecL = ptrA->srcClusterE.vecArrE[ iL ];
				int32 normL = 0;
				vecL.xE -= xL;
				vecL.yE -= yL;
				normL = bts_Int16Vec2D_norm( &vecL );

/* printf( "iL = %d, norm = %d\n", iL, normL ); */

				xSumL += ( normL * ptrA->rbfCoeffClusterE.vecArrE[ iL ].xE ) >> shiftL;
				ySumL += ( normL * ptrA->rbfCoeffClusterE.vecArrE[ iL ].yE ) >> shiftL;

/* printf( "iL = %d, xSumL = %d, ySumL = %d\n", iL, xSumL, ySumL ); */

            }

			xSumL >>= internalShiftL;
			ySumL >>= internalShiftL;

			/* change bbp of result back to bbp of vecA */
		/*	shiftL = vecA.bbpE - ptrA->srcClusterE.bbpE - internalShiftL; */
			shiftL = vecA.bbpE - ptrA->srcClusterE.bbpE;
			if( shiftL > 0 )
			{
				xSumL <<= shiftL;
				ySumL <<= shiftL;
			}
			else if( shiftL < 0 )
			{
				xSumL = ( ( xSumL >> ( -shiftL - 1 ) ) + 1 ) >> 1;
				ySumL = ( ( ySumL >> ( -shiftL - 1 ) ) + 1 ) >> 1;
			}

			/* add rbf part to already computed alt part */
			outXL += xSumL;
			outYL += ySumL;
        }
        break;

		/* Add a new RBF type here */

		default:
		{
			bbs_ERROR1( "struct bts_Flt16Vec2D bts_RBFMap2D_mapVector( ... ): "
				"RBFType %d is not handled\n", ptrA->RBFTypeE );
			return bts_Flt16Vec2D_create32( outXL, outYL, outBbpL );
		}
	}

	return bts_Flt16Vec2D_create32( outXL, outYL, outBbpL );
}

/* ------------------------------------------------------------------------- */

void bts_RBFMap2D_mapCluster( struct bbs_Context* cpA,
							  const struct bts_RBFMap2D* ptrA,
							  const struct bts_Cluster2D* srcPtrA,
							  struct bts_Cluster2D* dstPtrA,
							  int32 dstBbpA )
{
	if( dstPtrA->sizeE != srcPtrA->sizeE )
	{
		/* resizing of clusters is allowed as long as allocated size is not exceeded */
		bts_Cluster2D_size( cpA, dstPtrA, srcPtrA->sizeE );
	}

	{
		uint32 iL;
		int16 bbpL = srcPtrA->bbpE;

		dstPtrA->bbpE = dstBbpA;

		for( iL = 0; iL < srcPtrA->sizeE; iL++ )
		{
			struct bts_Int16Vec2D vecL = srcPtrA->vecArrE[ iL ];
			struct bts_Flt16Vec2D srcVecL = bts_Flt16Vec2D_create16( vecL.xE, vecL.yE, bbpL );
			struct bts_Flt16Vec2D dstVecL = bts_RBFMap2D_mapVector( cpA, ptrA, srcVecL );
			dstPtrA->vecArrE[ iL ].xE = bbs_convertS32( dstVecL.xE, dstVecL.bbpE, dstBbpA );
			dstPtrA->vecArrE[ iL ].yE = bbs_convertS32( dstVecL.yE, dstVecL.bbpE, dstBbpA );
		}
	}
}

/* ------------------------------------------------------------------------- */

/* ========================================================================= */