/*========================= MiscCoordConverter.m ============================*/
/* MiscCoordConverter is an abstract super class that supports the writing of
   converters between coordinate systems. All values are double precision
   floating point numbers 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
10-Mar-93  Dale Amon at GPL
	   Created.
*/

#import <MiscFoundation/MiscCoordConverter.h>
#import <MiscFoundation/MiscCoordConverterClient.h>
#import "_MiscCoordConverterService.h"

#ifdef WIN32
#import <winnt-pdo.h>
#endif

@implementation MiscCoordConverter

/*===========================================================================*/
/* Internal Class Subcontractor support methods */
/*===========================================================================*/
/* register a converter to the list of conversion subcontractors */

static NSMutableArray *subcontractors = nil; /* Master list of converters */

+ registerSubcontractor: aConverter
  {	if (!subcontractors)
		subcontractors = [[NSMutableArray allocWithZone:[self zone]] init];
	if (![subcontractors containsObject: aConverter])
            [subcontractors addObject: aConverter];
	return self;
  }


/*---------------------------------------------------------------------------*/
/* unregister a converter from the list of conversion subcontractors */

+ unregisterSubcontractor: aConverter
  {	[subcontractors removeObject:aConverter];
	return self;
  }

/*---------------------------------------------------------------------------*/
/* get the name of the next one in the list */

+ getSubcontractor:(unsigned int) n {return [subcontractors objectAtIndex:n];}


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

+ (void)initialize
  {[MiscCoordConverter setVersion:MISC_COORD_CONVERTER_VERSION_ID]; return;}


/*===========================================================================*/
/* Initialization methods */
/*===========================================================================*/
/* We cache everything that we will need repeatedly, ie the class id's
   of all the MiscCoord classes we know how to service.

   Every instance of a converter is registered so it can be subcontracted
   to.

*/

- init
  {	[MiscCoordConverter registerSubcontractor: self];
	return self;
  }


/*---------------------------------------------------------------------------*/
/* Free self and subcontractor list, but NOT the subcontractors. 

	There is a potential problem: what if someone holds
	a pointer to us? Some going out of business notification
	would be necessary in that case.

	Most subclasses lock out free so this is not an immediate problem
*/

- (void)dealloc
  {	[MiscCoordConverter unregisterSubcontractor:self];
	[services release];
	return [self release];
  }


/*===========================================================================*/
/* Service support methods */
/*===========================================================================*/
/* For subclass use: add a service to our list of conversion services. */

- addService: (SEL) aSelector convertsFrom: (Class) one to: (Class) two
{	_MiscCoordConverterService	*theService;

	theService = [[[_MiscCoordConverterService
                allocWithZone:[self zone]] init] autorelease];
	if (!services) services = [[NSMutableSet allocWithZone:[self zone]]
				  initWithCapacity: 0 ];

        [theService setInputClass:one];
        [theService setOutputClass:two];
        [theService setTranslator:aSelector];
        [services addObject: theService];
	return self;
}


/*---------------------------------------------------------------------------*/
/* Internal method to search for a service in our list of conversion services.
   We return the selector that can carry out the desired conversion,
   or else a null selector if we cannot. It is assumed that a null selector
   will always be undefined.

*/

- (SEL) findServiceFrom: (Class) one to: (Class) two
{
    _MiscCoordConverterService	*theService;
    NSEnumerator *enumerator = [services objectEnumerator];

    theService = [enumerator nextObject];
    while (theService) {
        if (([theService inputClass] == one) &&
            ([theService outputClass] == two)) return [theService translator];
        theService = [enumerator nextObject];
    }
    return	(SEL) 0;
  }

/*===========================================================================*/
/* Basic conversion methods (Internal use only) */
/*===========================================================================*/
/* Collect info we need to carry out the requested job

  NOTE: Any method that is sent to srcCoord or dstCoord must be in 
	the MiscCoordConverterClient protocol!
*/

- prepareForJob: inCoord : outCoord
  {
	srcConstants  = [inCoord constants];
	dstConstants  = [outCoord constants];
	sameConstants = [srcConstants isEqual: dstConstants];

	dimensions    = [[inCoord class] dimensions];
	npoints       = [inCoord  curBlockSize];

	src           = [inCoord  curPtr];
	dst           = [outCoord curPtr];

	return	self;
  }


/*---------------------------------------------------------------------------*/
/* a quick means of copying if data formats are identical. This method
   is known outside by name, but should never be actually used outside
   of applyTransform.

   It will fail if the constants associated with the two MiscCoords are not
   functionally the same.

*/

- (BOOL) copyCoords
  {	int		len;

	if (!sameConstants) return NO;
	len = dimensions * npoints * sizeof(double);
	bcopy((char*)src, (char*)dst, len);

	return YES;
  }


/*---------------------------------------------------------------------------*/
/* Apply the specified method to a block of coordinates. Job is set up
   by prepareForJob: before this method is called.
*/

- (BOOL) applyTransform:(SEL)aSelector
  {	unsigned int	i;

	/* copy has it's own loop */
	if (aSelector == @selector(copyCoords))
		[self performSelector: aSelector];
	else 
	   for (i=0;
	        i<npoints;
	        i++,dst+=dimensions,src+=dimensions)
			[self performSelector: aSelector];

	return YES;
  }


/*---------------------------------------------------------------------------*/
/* Conversion setup method. This method sees if we or anyone we know of
   can handle the requested job. If so, we return YES with local instance
   vars theTransform and subContractor ready to go.

   NOTE: If you want to handle conversion between coords of different
         dimensionality, look here first.

   Both MiscCoords must:
	1) Conform to the MiscCoordConverterClient protocol.
	2) Have the same dimensionality.
	3) Have the same transfer blocksize pre-set to the same size.
	4) Some Converter object must be willing to accept the job
	   of converting or copying between the two types.

*/

- (BOOL) reviewSpecification: inCoord to: outCoord 
  {	unsigned int	i;
	Class		srcClass, dstClass;
	BOOL		srcOk,dstOk;

	srcClass      = [inCoord  class];
	dstClass      = [outCoord class];
	srcOk	      = [srcClass conformsToProtocol:@protocol(MiscCoordConverterClient)];
	dstOk	      = [dstClass conformsToProtocol:@protocol(MiscCoordConverterClient)];
	if (!(srcOk && dstOk)) return NO;

	if ([srcClass dimensions]  != [dstClass dimensions])   return NO;
	if ([inCoord curBlockSize] != [outCoord curBlockSize]) return NO;

	/* We should be able to handle it ourselves most of the time, or
	   else the customer has the wrong delegate! */

	if (theTransform = [self findServiceFrom: srcClass to: dstClass])
		aSubcontractor = self;
	else
	/* If we can't do it ourselves, try subcontracting to one of the
	   other registered convertors
	*/
	for (i=0; aSubcontractor = [MiscCoordConverter getSubcontractor:i]; i++)
	 {if (aSubcontractor == self) continue;
	  if (theTransform =
	      [aSubcontractor findServiceFrom: srcClass to: dstClass])
	      break;
	 }

	return ((theTransform) ? YES : NO);
  }
			

/*===========================================================================*/
/* Misc Coord Converter Server protocol */
/*===========================================================================*/
/* Convert the specified block of coordinates from the source type to the
   destination type if we are able, or if we can subcontract it to someone
   else who is able to do it. If there is any reason at all that we
   cannot, then return NO.

   DESIGN NOTE: Although local methods are executing, there may be one OR two
   different objects with private storage involved. Care must be taken to
   make sure that setup takes place in the correct object, ie usually the
   subcontractor object. The subcontractor will often be self, in which case
   setting ivars in this method would work: but if not, the subcontractor
   would probably crash because things would not be set up in ITS' ivars.
   THINK before making changes to where set up occurs!

*/

- (BOOL) convert: inCoord to: outCoord 
  {
	if (![self reviewSpecification: inCoord to: outCoord]) return NO;
	[aSubcontractor prepareForJob: inCoord : outCoord];
	return [aSubcontractor applyTransform:theTransform];
   }


/*===========================================================================*/
/* For Subclass use: We have a fast means of copying, but there is no need to
   make it really public. To discourage possibly disastrous attempts to use it
   directly, we supply a means of getting it's selector for use in an 
   addService method by a subclass.
*/

- (SEL) fastCopySelector {return (@selector(copyCoords));}


/*===========================================================================*/
/* Archive methods */
/*===========================================================================*/
/* This class should not be used on its own. Presumably a subclass will 
   re-generate it's services list when it awakes, or if it is a single
   instance class, it will just delete this object when it answers with
   a finishUnarchiving message.
*/

- initWithCoder:(NSCoder *)aCoder
{	
    	services = nil;
	[MiscCoordConverter registerSubcontractor: self];
	return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
}

@end
