/*=============================== MiscCoord.m ===============================*/
/* MiscCoord class contains and supports double precision floating point
   values representing locations in a three dimensional coordinate system.

   DMA Release 0.8, Copyright @1993 by Genesis Project, Ltd. All Rights
   Reserved. For further information on terms and conditions see
		the MiscKit license.

HISTORY
22-Feb-93  Dale Amon at GPL
	   Created.
*/

#import <MiscFoundation/MiscCoord.h>
#import <objc/Storage.h>

#ifdef WIN32
#define M_PI 3.14159265358979323846
#endif

@implementation MiscCoord

/*===========================================================================*/
/* Class methods */
/*===========================================================================*/
/* Initialize the class */

+ (void)initialize {[MiscCoord setVersion:MISC_COORD_VERSION_ID]; return;}

+ (unsigned int) dimensions {return MISC_DIMENSIONS;}


/*===========================================================================*/
/* Class: basic argument conversions */
/*===========================================================================*/
/* convert from r.rrr radians degrees to ddd.ddd degrees */

+(double) radiansToDegrees: (double)angle
 {	double deg = angle * MISC_DEGREES_PER_RADIAN;
	return ((deg<0.0) ? deg - MISC_ROUND_OFF : deg + MISC_ROUND_OFF);
 }

/*---------------------------------------------------------------------------*/
/* convert from ddd.ddd degrees to r.rrr radians. It is rounded up
   slightly to correct for the slight numerical error that crops up
   due to the multiplication by an approximation to pi.
*/

+(double) degreesToRadians: (double) angle
 {return (angle * MISC_RADIANS_PER_DEGREE);}

/*---------------------------------------------------------------------------*/
/* convert degrees from ddd:mm:ss.ss to ddd.ddd */

+(double) toDegreesOnlyDegrees: (double) deg
		       minutes: (double) min
		       seconds: (double) sec
 {return (deg + min/60.0 + sec/3600.0);}

/*---------------------------------------------------------------------------*/
/* Convert degrees from ddd.ddd to ddd:mm:ss.ss
   Handles negative values by making degrees positive and replacing the
   negative sign on each resultant value of deg,min,sec.
*/

+(double) fromDegreesOnly: (double) degrees
		  degrees: (double *) deg
		  minutes: (double *) min
		  seconds: (double *) sec
 {	double remainder,sign;

	sign = (degrees<0.0) ? -1.0 : 1.0;
	degrees    = sign * degrees;

	*deg       = floor(degrees);
	remainder  = (degrees - *deg) * 60;
	*min       = floor(remainder);
	*sec       = (remainder - *min) * 60;

	*deg *= sign;
	*min *= sign;
	*sec *= sign;

	return degrees;
 }


/*===========================================================================*/
/* Initialization methods */
/*===========================================================================*/
/* DESIGNATED INITIALIZER:
   Creates and explicitely inits a three dimensional map or globe coordinate.

   Default size is one point, but point storage will be allocated on demand.
   The initial point
   The default block is the one initial point.

*/

-initDescription: (NSString *) txt
       converter: (id <MiscCoordConverterServer>) aConverter
       constants: anObject
 {	[super init];

	curIndex     = 0;
	curBlockSize = MISC_DEFAULT_POINT_CAPACITY;
	constants    = anObject;
	converter   = aConverter;
	pntStorage = [[Storage alloc] // can't use our zone: NXZone!=NSZone
				   initCount: MISC_DEFAULT_POINT_CAPACITY
			         elementSize: MISC_DIMENSIONS * sizeof(double)
			         description: MISC_ELEMENT_DESCRIPTION];
	[self setDescription: txt];
	return self;
 }


/*---------------------------------------------------------------------------*/
/* Creates and explicitely inits a three dimensional map or globe coordinate
   to the origin. This should be overridden by the subclass because it
   creates an coord object that is unmappable to another coord system.
*/

-init
 {return ([self initDescription: (NSString*)nil converter: nil constants: nil]);}


/*---------------------------------------------------------------------------*/
/* free the storage and description before freeing self.
   Constants and converters are freed because either the object is unique to
   one MiscCoord, in which case it should be locally deleted, or else it is
   shared in which case it should have free blocked. It is one or the other.
*/

- (void)dealloc
  {	[pntStorage free];
	[constants release];
	[(id)converter release];
	if (description) free((void*)description);

	[super dealloc];
}


/*===========================================================================*/
/* Description handling methods */
/*===========================================================================*/

- setDescription: (NSString *) txt
  {
	[description autorelease];
	description = [txt  copy];
	return self;
  }

- (NSString *) description {return ( description);}


/*===========================================================================*/
/* Conversion converter and constants methods */
/*===========================================================================*/

- (id <MiscCoordConverterServer>) converter {return converter;}
- constants                             {return constants;}


/*===========================================================================*/
/* Current point and storage management methods

   Select a block of points using the index of a starting point and a count of
   the number of points required after that index. Manage storage requirement
   for the requested blocks. */
/*===========================================================================*/
/* If the index is out of range, return NO, otherwise return YES.
   If the blockSize would include out of range points, modify it to be in
   range. Does not modify the MiscCoord storage.
*/

- (BOOL) selectExistingPoints: (unsigned int) n blockSize: (unsigned int) m 
  {	unsigned int availableSize, requiredSize;

	availableSize = [pntStorage count];
	requiredSize  = n + m;

	if (n >= availableSize) return NO;
	curIndex = n;
	curBlockSize = (requiredSize<=availableSize) ? m :
						       availableSize - n;
	return YES;
  }

/*---------------------------------------------------------------------------*/
/* Resize so that the capacity is exactly sufficient to hold a block
   ending at n+m. Either expands or truncates the MiscCoord data storage

   Note: May invalidate pointers to data. Always assume that it does.
*/

- (BOOL) selectAndSetNumPoints: (unsigned int) n blockSize: (unsigned int) m 
  {	[pntStorage setNumSlots:n+m];
	curIndex     = n;
	curBlockSize = m;
	return YES;
  }

/*---------------------------------------------------------------------------*/
/* Resize so that the capacity is sufficient to hold a block
   ending at n+m. Expands the MiscCoord data storage if necessary.
   Never contracts the storage.

   Note: May invalidate pointers to data. Always assume that it does.
*/

- (BOOL) selectAndSetMinPoints: (unsigned int) n blockSize: (unsigned int) m 
  {	if (n+m > [pntStorage count]) [pntStorage setNumSlots:n+m];
	curIndex     = n;
	curBlockSize = m;
	return YES;
  }

/*---------------------------------------------------------------------------*/
/* Return the current Point, BlockSize or capacity. */

- (unsigned int) curIndex     {return curIndex;}
- (unsigned int) curBlockSize {return curBlockSize;}
- (unsigned int) numPoints    {return [pntStorage count];}


/*===========================================================================*/
/* Storage management methods */
/*===========================================================================*/
/* return a pointer to the actual stored data of the currently selected block
   of points. This should ONLY be used by converters that need to iterate
   over data in a fast and efficient manner. The data pointer can is only 
   valid if nothing causes the size of the array to change while you posses it.
   CAVEAT EMPTOR.
*/

-(double *) curPtr {return ((double *) [pntStorage elementAt:curIndex]);}


/*===========================================================================*/
/* Coordinate handling methods */
/*===========================================================================*/
/* set Coord value */

-setCoord:(double) c1 : (double) c2 : (double) c3
 {	double	*ptr;

	ptr = (double *)[pntStorage elementAt: curIndex];
	ptr[0] = c1;
	ptr[1] = c2;
	ptr[2] = c3;
	return self;
 }

/*---------------------------------------------------------------------------*/
/* get Coord value */

- coord: (double*) c1 : (double*) c2 : (double*) c3;
 {	double	*ptr;

	ptr = (double *)[pntStorage elementAt: curIndex];
	*c1 = ptr[0];
	*c2 = ptr[1];
	*c3 = ptr[2];
	return self;
 }

/*---------------------------------------------------------------------------*/
/* Individually request coord values at current Index */ 

-(double) coord1  {return ((double *)[pntStorage elementAt:curIndex])[0];}
-(double) coord2  {return ((double *)[pntStorage elementAt:curIndex])[1];}
-(double) coord3  {return ((double *)[pntStorage elementAt:curIndex])[2];}


/*===========================================================================*/
/* Conversion methods */
/*===========================================================================*/
/* Copy or convert the entire MiscCoord from source to destination with
   assistance of a converter. If we succeed, also copy the
   text description to the destination.
*/

-(BOOL) convertCoord: (id <MiscCoordConverterClient>) aCoord
 {	if (converter)
	   {[self selectAndSetMinPoints: 0 blockSize: [pntStorage count]];
	    [(id)aCoord selectAndSetMinPoints: curIndex
				    blockSize: curBlockSize];
	    if ([converter convert: self to: aCoord])
		{[(id)aCoord setDescription: description];
		 return YES;
		}
	   }
	return NO;
 }


/*---------------------------------------------------------------------------*/
/* Send our selected block of points to the converter and have the result
   stored in the selected block in the target coord.
*/

-(BOOL) convert:  (id <MiscCoordConverterClient>) aCoord
 {	if (converter) return [converter convert: self to: aCoord];
	return NO;
 }


/*===========================================================================*/
/* Archive methods */
/*===========================================================================*/
- (void)encodeWithCoder:(NSCoder *)aCoder
  {
	[aCoder encodeValuesOfObjCTypes:"*ii",
			description, (int*)&curIndex, (int*)&curBlockSize];
	[aCoder encodeObject:constants];
	[aCoder encodeObject:converter];
	[aCoder encodeObject:(id)pntStorage];
}

/*---------------------------------------------------------------------------*/
- (id)initWithCoder:(NSCoder *)aDecoder
  {	
	[aDecoder decodeValuesOfObjCTypes:"*ii",
			description, (int*)&curIndex, (int*)&curBlockSize];
	constants  = [[aDecoder decodeObject] retain];
	converter  = (id <MiscCoordConverterServer>) [[aDecoder decodeObject] retain];
	pntStorage = (Storage*) [[aDecoder decodeObject] retain];
	return self;
  }

@end
