/** <title>NSAffineTransform.m</title>

   <abstract>
   This class provides a way to perform affine transforms.  It provides 
   a matrix for transforming from one coordinate system to another.
   </abstract>
   Copyright (C) 1996,1999 Free Software Foundation, Inc.

   Author: Ovidiu Predescu <ovidiu@net-community.com>
   Date: August 1997
   Author: Richard Frith-Macdonald <richard@brainstorm.co.uk>
   Date: March 1999
   
   This file is part of the GNUstep Base Library.

   This library 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 3 of the License, or (at your option) any later version.
   
   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02111 USA.
*/

#import "config.h"
#include <math.h>

#import "Foundation/NSArray.h"
#import "Foundation/NSException.h"
#import "Foundation/NSString.h"
#import "Foundation/NSAffineTransform.h"
#import "Foundation/NSCoder.h"
#import "Foundation/NSDebug.h"

/* Private definitions */
#define A _matrix.m11
#define B _matrix.m12
#define C _matrix.m21
#define D _matrix.m22
#define TX _matrix.tX
#define TY _matrix.tY

/* A Postscript matrix looks like this:

  /  a  b  0 \
  |  c  d  0 |
  \ tx ty  1 /

 */

static const float pi = 3.1415926535897932384626434;

#if 0
#define valid(o) NSAssert((o->_isIdentity && o->A==1.0 && o->B==0.0 && o->C==0.0 && o->D==1.0) || (o->_isFlipY && o->A==1.0 && o->B==0.0 && o->C==0.0 && o->D==-1.0) || !(o->_isIdentity||o->_isFlipY), NSInternalInconsistencyException) 
#define check() valid(self)
#else
#define valid(o)
#define check()
#endif

/* Quick function to multiply two coordinate matrices. C = AB */
static inline NSAffineTransformStruct 
matrix_multiply (NSAffineTransformStruct MA, NSAffineTransformStruct MB)
{
  NSAffineTransformStruct MC;
  MC.m11 = MA.m11 * MB.m11 + MA.m12 * MB.m21;
  MC.m12 = MA.m11 * MB.m12 + MA.m12 * MB.m22;
  MC.m21 = MA.m21 * MB.m11 + MA.m22 * MB.m21;
  MC.m22 = MA.m21 * MB.m12 + MA.m22 * MB.m22;
  MC.tX  = MA.tX * MB.m11 + MA.tY * MB.m21 + MB.tX;
  MC.tY  = MA.tX * MB.m12 + MA.tY * MB.m22 + MB.tY;
  return MC;
}

/*
  MC.m11 = MA->A * MB->A + MA->B * MB->C;
  MC.m12 = MA->A * MB->B + MA->B * MB->D;
  MC.m21 = MA->C * MB->A + MA->D * MB->C;
  MC.m22 = MA->C * MB->B + MA->D * MB->D;
  MC.tX  = MA->TX * MB->A + MA->TY * MB->C + MB->TX;
  MC.tY  = MA->TX * MB->B + MA->TY * MB->D + MB->TY;
 */

@implementation NSAffineTransform

static NSAffineTransformStruct identityTransform = {
   1.0, 0.0, 0.0, 1.0, 0.0, 0.0
};

/**
 * Return an autoreleased instance of this class.
 */
+ (NSAffineTransform*) transform
{
  NSAffineTransform	*t;

  t = (NSAffineTransform*)NSAllocateObject(self, 0, NSDefaultMallocZone());
  t->_matrix = identityTransform;
  t->_isIdentity = YES;
  return AUTORELEASE(t);
}

/**
 * Return an autoreleased instance of this class.
 */
+ (id) new
{
  NSAffineTransform	*t;

  t = (NSAffineTransform*)NSAllocateObject(self, 0, NSDefaultMallocZone());
  t->_matrix = identityTransform;
  t->_isIdentity = YES;
  return t;
}

/**
 * Appends the transform matrix to the receiver.  This is done by performing a
 * matrix multiplication of the receiver with aTransform so that aTransform
 * is the first transform applied to the user coordinate. The new
 * matrix then replaces the receiver's matrix.
 */
- (void) appendTransform: (NSAffineTransform*)aTransform
{
  valid(aTransform);

  if (aTransform->_isIdentity)
    {
      TX += aTransform->TX;
      TY += aTransform->TY;
      check();
      return;
    }

  if (aTransform->_isFlipY)
    {
      B = -B;
      D = -D;
      TX = aTransform->TX + TX;
      TY = aTransform->TY - TY;
      if (_isIdentity)
        {
	  _isFlipY = YES;
	  _isIdentity = NO;
	}
      else if (_isFlipY)
        {
	  _isFlipY = NO;
	  _isIdentity = YES;
	}
      check();
      return;
    }

  if (_isIdentity)
    {
      A = aTransform->A;
      B = aTransform->B;
      C = aTransform->C;
      D = aTransform->D;
      TX = TX * aTransform->A + TY * aTransform->C + aTransform->TX;
      TY = TX * aTransform->B + TY * aTransform->D + aTransform->TY;
      _isIdentity = NO;  // because aTransform is not an identity transform.
      _isFlipY = aTransform->_isFlipY;
      check();
      return;
    }

  if (_isFlipY)
    {
      A = aTransform->A;
      B = aTransform->B;
      C = -aTransform->C;
      D = -aTransform->D;
      TX  = TX * aTransform->A + TY * aTransform->C + aTransform->TX;
      TY  = TX * aTransform->B + TY * aTransform->D + aTransform->TY;
      _isIdentity = NO;
      _isFlipY = NO;
      check();
      return;
    }

  _matrix = matrix_multiply(_matrix, aTransform->_matrix);
  _isIdentity = NO;
  _isFlipY = NO;
  check();
}

- (NSString*) description
{
  return [NSString stringWithFormat:
    @"NSAffineTransform ((%f, %f) (%f, %f) (%f, %f))", A, B, C, D, TX, TY];
}

/**
 * Initialize the transformation matrix instance to the identity matrix.
 * The identity matrix transforms a point to itself.
 */ 
- (id) init
{
  _matrix = identityTransform;
  _isIdentity = YES;
  return self;
}

/**
 * Initialize the receiever's instance with the instance represented 
 * by aTransform. 
 */
- (id) initWithTransform: (NSAffineTransform*)aTransform
{
  _matrix = aTransform->_matrix;
  _isIdentity = aTransform->_isIdentity;
  _isFlipY = aTransform->_isFlipY;
  return self;
}

/**
 * Calculates the inverse of the receiver's matrix and replaces the 
 * receiever's matrix with it.
 */
- (void) invert
{
  float newA, newB, newC, newD, newTX, newTY;
  float det;

  if (_isIdentity)
    {
      TX = -TX;
      TY = -TY;
      return;
    }

  if (_isFlipY)
    {
      TX = -TX;
      return;
    }

  det = A * D - B * C;
  if (det == 0)
    {
      NSLog (@"error: determinant of matrix is 0!");
      return;
    }

  newA = D / det;
  newB = -B / det;
  newC = -C / det;
  newD = A / det;
  newTX = (-D * TX + C * TY) / det;
  newTY = (B * TX - A * TY) / det;

  NSDebugLLog(@"NSAffineTransform",
	@"inverse of matrix ((%f, %f) (%f, %f) (%f, %f))\n"
	@"is ((%f, %f) (%f, %f) (%f, %f))",
	A, B, C, D, TX, TY,
	newA, newB, newC, newD, newTX, newTY);

  A = newA; B = newB;
  C = newC; D = newD;
  TX = newTX; TY = newTY;
}

/**
 * Prepends the transform matrix to the receiver.  This is done by performing a
 * matrix multiplication of the receiver with aTransform so that aTransform
 * is the last transform applied to the user coordinate. The new
 * matrix then replaces the receiver's matrix.
 */
- (void) prependTransform: (NSAffineTransform*)aTransform
{
  valid(aTransform);

  if (aTransform->_isIdentity)
    {
      TX = aTransform->TX * A + aTransform->TY * C + TX;
      TY = aTransform->TX * B + aTransform->TY * D + TY;
      check();
      return;
    }

  if (aTransform->_isFlipY)
    {
      TX  = aTransform->TX * A + aTransform->TY * C + TX;
      TY  = aTransform->TX * B + aTransform->TY * D + TY;
      C = -C;
      D = -D;
      if (_isIdentity)
        {
	  _isFlipY = YES;
	  _isIdentity = NO;
	}
      else if (_isFlipY)
        {
	  _isFlipY = NO;
	  _isIdentity = YES;
	}
      check();
      return;
    }

  if (_isIdentity)
    {
      A = aTransform->A;
      B = aTransform->B;
      C = aTransform->C;
      D = aTransform->D;
      TX += aTransform->TX;
      TY += aTransform->TY;
      _isIdentity = NO;
      _isFlipY = aTransform->_isFlipY;
      check();
      return;
    }

  if (_isFlipY)
    {
      A = aTransform->A;
      B = -aTransform->B;
      C = aTransform->C;
      D = -aTransform->D;
      TX += aTransform->TX;
      TY -= aTransform->TY;
      _isIdentity = NO;
      _isFlipY = NO;
      check();
      return;
    }

  _matrix = matrix_multiply(aTransform->_matrix, _matrix);
  _isIdentity = NO;
  _isFlipY = NO;
  check();
}

/**
 * Applies the rotation specified by angle in degrees.   Points transformed
 * with the transformation matrix of the receiver are rotated counter-clockwise 
 * by the number of degrees specified by angle.
 */
- (void) rotateByDegrees: (float)angle
{
  [self rotateByRadians: pi * angle / 180];
}

/**
 * Applies the rotation specified by angle in radians.   Points transformed
 * with the transformation matrix of the receiver are rotated counter-clockwise 
 * by the number of radians specified by angle.
 */
- (void) rotateByRadians: (float)angleRad
{
  if (angleRad != 0.0)
    {
      float sine;
      float cosine;
      NSAffineTransformStruct rotm;

      sine = sin (angleRad);
      cosine = cos (angleRad);
      rotm.m11 = cosine;
      rotm.m12 = sine;
      rotm.m21 = -sine;
      rotm.m22 = cosine;
      rotm.tX = rotm.tY = 0;
      _matrix = matrix_multiply(rotm, _matrix);
      _isIdentity = NO;
      _isFlipY = NO;
      check();
    }
}

/**
 * Scales the transformation matrix of the reciever by the factor specified
 * by scale.  
 */
- (void) scaleBy: (float)scale
{
  NSAffineTransformStruct scam = identityTransform;

  scam.m11 = scale;
  scam.m22 = scale;
  _matrix = matrix_multiply(scam, _matrix);
  _isIdentity = NO;
  _isFlipY = NO;
  check();
}

/**
 * Scales the X axis of the receiver's transformation matrix 
 * by scaleX and the Y axis of the transformation matrix by scaleY.
 */
- (void) scaleXBy: (float)scaleX yBy: (float)scaleY
{
  if (_isIdentity && scaleX == 1.0)
    {
      if (scaleY == 1.0)
        {
	  return;	// no scaling
	}
      if (scaleY == -1.0)
	{
	  D = -1.0;
	  _isFlipY = YES;
	  _isIdentity = NO;
	  return;
	}
    }

  if (_isFlipY && scaleX == 1.0)
    {
      if (scaleY == 1.0)
        {
	  return;	// no scaling
	}
      if (scaleY == -1.0)
	{
	  D = 1.0;
	  _isFlipY = NO;
	  _isIdentity = YES;
	  return;
	}
    }

  A *= scaleX;
  B *= scaleX;
  C *= scaleY;
  D *= scaleY;
  _isIdentity = NO;
  _isFlipY = NO;
}

/**
 * <p>
 * Sets the structure which represents the matrix of the reciever. 
 * The struct is of the form:</p>
 * <p>{m11, m12, m21, m22, tX, tY}</p>
 */
- (void) setTransformStruct: (NSAffineTransformStruct)val
{
  _matrix = val;
  _isIdentity = NO;
  _isFlipY = NO;
  if (A == 1.0 && B == 0.0 && C == 0.0)
    {
      if (D == 1.0)
	{
	  _isIdentity = YES;
	}
      else if (D == -1.0)
	{
	  _isFlipY = YES;
	}
    }
  check();
}

/**
 * Transforms a single point based on the transformation matrix.
 * Returns the resulting point.
 */
- (NSPoint) transformPoint: (NSPoint)aPoint
{
  NSPoint new;

  if (_isIdentity)
    {
      new.x = TX + aPoint.x;
      new.y = TY + aPoint.y;
    }
  else if (_isFlipY)
    {
      new.x = TX + aPoint.x;
      new.y = TY - aPoint.y;
    }
  else
    {
      new.x = A * aPoint.x + C * aPoint.y + TX;
      new.y = B * aPoint.x + D * aPoint.y + TY;
    }

  return new;
}

/**
 * Transforms the NSSize represented by aSize using the reciever's 
 * transformation matrix.  Returns the resulting NSSize.<br />
 * NB. A transform can result in negative size components ... so calling
 * code should check for and deal with that situation.
 */
- (NSSize) transformSize: (NSSize)aSize
{
  if (_isIdentity)
    {
      return aSize;
    }
  else
    {
      NSSize new;

      if (_isFlipY)
        {
	  new.width = aSize.width;
	  new.height = -aSize.height;
	}
      else
        {
	  new.width = A * aSize.width + C * aSize.height;
	  new.height = B * aSize.width + D * aSize.height;
	}
      return new;
    }
}

/**
 * <p>
 * Returns the <code>NSAffineTransformStruct</code> structure 
 * which represents the matrix of the reciever. 
 * The struct is of the form:</p>
 * <p>{m11, m12, m21, m22, tX, tY}</p>
 */
- (NSAffineTransformStruct) transformStruct
{
  return _matrix;
}

/**
 * Applies the translation specified by tranX and tranY to the receiver's
 * matrix.
 * Points transformed by the reciever's matrix after this operation will 
 * be shifted in position based on the specified translation.
 */
- (void) translateXBy: (float)tranX  yBy: (float)tranY
{
  if (_isIdentity)
    {
      TX += tranX;
      TY += tranY;
    }
  else if (_isFlipY)
    {
      TX += tranX;
      TY -= tranY;
    }
  else
    {
      TX += A * tranX + C * tranY;
      TY += B * tranX + D * tranY;
    }
  check();
}

- (id) copyWithZone: (NSZone*)zone
{
  return NSCopyObject(self, 0, zone);
}

- (BOOL) isEqual: (id)anObject
{
  if ([anObject class] == isa)
    {
      NSAffineTransform	*o = anObject;

      if (A == o->A && B == o->B && C == o->C
	&& D == o->D && TX == o->TX && TY == o->TY)
	return YES;
    }
  return NO;
}

- (id) initWithCoder: (NSCoder*)aCoder
{
  NSAffineTransformStruct	replace;
    
  [aCoder decodeArrayOfObjCType: @encode(float)
			  count: 6
			     at: (float*)&replace];
  [self setTransformStruct: replace];
  return self;
}

- (void) encodeWithCoder: (NSCoder*)aCoder
{
  NSAffineTransformStruct	replace;
    
  replace = [self transformStruct];
  [aCoder encodeArrayOfObjCType: @encode(float)
			  count: 6
			     at: (float*)&replace];
}

@end /* NSAffineTransform */

