/*****
 * transform.h
 * Andy Hammerlindl 2002/05/22
 *
 * The transform datatype stores an affine transformation on the plane
 * The datamembers are x, y, xx, xy, yx, and yy.  A pair (x,y) is
 * transformed as
 *   x' = t.x + t.xx * x + t.xy * y
 *   y' = t.y + t.yx * x + t.yy * y
 *****/

#ifndef TRANSFORM_H
#define TRANSFORM_H

#include <iostream>

#include "pair.h"

namespace camp {

class transform : public gc {
  double x;
  double y;
  double xx;
  double xy;
  double yx;
  double yy;

public:
  transform()
    : x(0.0), y(0.0), xx(1.0), xy(0.0), yx(0.0), yy(1.0) {}

  virtual ~transform() {}

  transform(double x, double y,
            double xx, double xy,
            double yx, double yy)
    : x(x), y(y), xx(xx), xy(xy), yx(yx), yy(yy) {}

  double getx() const { return x; }
  double gety() const { return y; }
  double getxx() const { return xx; }
  double getxy() const { return xy; }
  double getyx() const { return yx; }
  double getyy() const { return yy; }

  friend transform operator+ (const transform& t, const transform& s)
  {
    return transform(t.x + s.x, t.y + s.y,
                     t.xx + s.xx, t.xy + s.xy,
                     t.yx + s.yx, t.yy + s.yy);
  }

  friend transform operator- (const transform& t, const transform& s)
  {
    return transform(t.x - s.x, t.y - s.y,
                     t.xx - s.xx, t.xy - s.xy,
                     t.yx - s.yx, t.yy - s.yy);
  }

  friend transform operator- (const transform& t)
  {
    return transform(-t.x, -t.y,
                     -t.xx, -t.xy,
                     -t.yx, -t.yy);
  }

  friend pair operator* (const transform& t, const pair& z)
  {
    double x = z.getx(), y = z.gety();
    return pair(t.x + t.xx * x + t.xy * y, t.y + t.yx * x + t.yy * y);
  }

  // Calculates the composition of t and s, so for a pair, z,
  //   t * (s * z) == (t * s) * z
  // Can be thought of as matrix multiplication.
  friend transform operator* (const transform& t, const transform& s)
  {
    return transform(t.x + t.xx * s.x  + t.xy * s.y,
                     t.y + t.yx * s.x  + t.yy * s.y,
                     t.xx * s.xx + t.xy * s.yx,
                     t.xx * s.xy + t.xy * s.yy,
                     t.yx * s.xx + t.yy * s.yx,
                     t.yx * s.xy + t.yy * s.yy);
  }

  friend bool operator== (const transform& t1, const transform& t2)
  {
    return t1.x  == t2.x  && t1.y  == t2.y  &&
      t1.xx == t2.xx && t1.xy == t2.xy &&
      t1.yx == t2.yx && t1.yy == t2.yy;
  }

  friend bool operator!= (const transform& t1, const transform& t2)
  {
    return !(t1 == t2);
  }

  bool isIdentity() const
  {
    return x == 0.0 && y == 0.0 &&
      xx == 1.0 && xy == 0.0 && yx == 0.0 && yy == 1.0;
  }

  bool isIsometry() const
  {
    return xx*xx+xy*xy == 1.0 && xx*yx+xy*yy == 0.0 && yx*yx+yy*yy == 1.0;
  }

  bool isNull() const
  {
    return x == 0.0 && y == 0.0 &&
      xx == 0.0 && xy == 0.0 && yx == 0.0 && yy == 0.0;
  }

  // Calculates the determinant, as if it were a matrix.
  friend double det(const transform& t)
  {
    return t.xx * t.yy - t.xy * t.yx;
  }

  // Tells if the transformation is invertible (bijective).
  bool invertible() const
  {
    return det(*this) != 0.0;
  }

  friend transform inverse(const transform& t)
  {
    double d = det(t);
    if (d == 0.0)
      reportError("inverting singular transform");

    d=1.0/d;
    return transform((t.xy * t.y - t.yy * t.x)*d,
                     (t.yx * t.x - t.xx * t.y)*d,
                     t.yy*d, -t.xy*d, -t.yx*d, t.xx*d);
  }

  friend ostream& operator<< (ostream& out, const transform& t)
  {
    return out << "(" << t.x  << ","
               << t.y  << ","
               << t.xx << ","
               << t.xy << ","
               << t.yx << ","
               << t.yy << ")";
  }
};

// The common transforms
static const transform identity;

inline transform shift(pair z)
{
  return transform (z.getx(), z.gety(), 1.0, 0.0, 0.0, 1.0);
}

inline transform xscale(double s)
{
  return transform (0.0, 0.0, s, 0.0, 0.0, 1.0);
}

inline transform yscale(double s)
{
  return transform (0.0, 0.0, 1.0, 0.0, 0.0, s);
}

inline transform scale(double s)
{
  return transform (0.0, 0.0, s, 0.0, 0.0, s);
}

inline transform scale(double x, double y)
{
  return transform (0.0, 0.0, x, 0.0, 0.0, y);
}

inline transform scale(pair z)
{
  // Equivalent to multiplication by z.
  double x = z.getx(), y = z.gety();
  return transform (0.0, 0.0, x, -y, y, x);
}

inline transform slant(double s)
{
  return transform (0.0, 0.0, 1.0, s, 0.0, 1.0);
}

inline transform rotate(double theta)
{
  double s = sin(theta), c = cos(theta);
  return transform (0.0, 0.0, c, -s, s, c);
}

// return rotate(angle(v)) if z != (0,0); otherwise return identity.
inline transform rotate(pair z)
{
  double d=z.length();
  if(d == 0.0) return identity;
  d=1.0/d;
  return transform (0.0, 0.0, d*z.getx(), -d*z.gety(), d*z.gety(), d*z.getx());
}

inline transform rotatearound(pair z, double theta)
{
  // Notice the operators are applied from right to left.
  // Could be optimized.
  return shift(z) * rotate(theta) * shift(-z);
}

inline transform reflectabout(pair z, pair w)
{
  if (z == w)
    reportError("points determining line to reflect about must be distinct");

  // Also could be optimized.
  transform basis = shift(z) * scale(w-z);
  transform flip = yscale(-1.0);

  return basis * flip * inverse(basis);
}

// Return the rotational part of t.
inline transform rotation(transform t)
{
  pair z(2.0*t.getxx()*t.getyy(),t.getyx()*t.getyy()-t.getxx()*t.getxy());
  if(t.getxx() < 0) z=-z;
  return rotate(atan2(z.gety(),z.getx()));
}

// Remove the x and y components, so that the new transform maps zero to zero.
inline transform shiftless(transform t)
{
  return transform(0, 0, t.getxx(), t.getxy(), t.getyx(), t.getyy());
}

// Return the translational component of t.
inline transform shift(transform t)
{
  return transform(t.getx(), t.gety(), 1.0, 0, 0, 1.0);
}

// Return the translational pair of t.
inline pair shiftpair(transform t)
{
  return pair(t.getx(), t.gety());
}

inline transform matrix(pair lb, pair rt)
{
  pair size=rt-lb;
  return transform(lb.getx(),lb.gety(),size.getx(),0,0,size.gety());
}

} //namespace camp

GC_DECLARE_PTRFREE(camp::transform);

#endif
