#include "AffineTransformationMatrix.h"

AffineTransformationMatrix::AffineTransformationMatrix()
{
  clear();
}

AffineTransformationMatrix::~AffineTransformationMatrix()
{
}

//sets back to the identity matrix
void AffineTransformationMatrix::clear()
{
  data.clear();
  data.push_back(1);
  data.push_back(0);
  data.push_back(0);

  data.push_back(0);
  data.push_back(1);
  data.push_back(0);

  data.push_back(0);
  data.push_back(0);
  data.push_back(1);

  scaleX=1; scaleY=1; ang=0; shiftX=0; shiftY=0;
}

//multiply other matrix into this matrix
//this = lhs * this
void AffineTransformationMatrix::multiplyMatrix(AffineTransformationMatrix & lhs)
{
  multiplyMatrix(lhs.getData());

  //metadata
  scaleX*=lhs.scaleX;
  scaleY*=lhs.scaleY;
  ang+=lhs.ang;
  shiftX+=lhs.shiftX;
  shiftY+=lhs.shiftY;
}

//this doesn't affect metadata.
void AffineTransformationMatrix::multiplyMatrix(vector <FixedInt> & lhs)
{
  vector<FixedInt>result;
  result.clear();

  result.push_back(lhs[0]*data[0]+lhs[1]*data[3]+lhs[2]*data[6]);
  result.push_back(lhs[0]*data[1]+lhs[1]*data[4]+lhs[2]*data[7]);
  result.push_back(lhs[0]*data[2]+lhs[1]*data[5]+lhs[2]*data[8]);

  result.push_back(lhs[3]*data[0]+lhs[4]*data[3]+lhs[5]*data[6]);
  result.push_back(lhs[3]*data[1]+lhs[4]*data[4]+lhs[5]*data[7]);
  result.push_back(lhs[3]*data[2]+lhs[4]*data[5]+lhs[5]*data[8]);

  result.push_back(lhs[6]*data[0]+lhs[7]*data[3]+lhs[8]*data[6]);
  result.push_back(lhs[6]*data[1]+lhs[7]*data[4]+lhs[8]*data[7]);
  result.push_back(lhs[6]*data[2]+lhs[7]*data[5]+lhs[8]*data[8]);

  data=result;
}

//multiply this matrix into (x, y, 1) to get the new coordinates after applying the matrix.
//this * (x,y,1)
void AffineTransformationMatrix::apply(int & x, int & y)
{
  int newx = ((data[0]*x) + (data[1]*y) + data[2]).toInt();
  int newy = ((data[3]*x) + (data[4]*y) + data[5]).toInt();
  x=newx;y=newy;
}

void AffineTransformationMatrix::apply(FixedInt & x, FixedInt & y)
{
  FixedInt newx = ((data[0]*x) + (data[1]*y) + data[2]);
  FixedInt newy = ((data[3]*x) + (data[4]*y) + data[5]);
  x=newx;y=newy;
}

vector <FixedInt> & AffineTransformationMatrix::getData()
{
  return data;
}

//apply effects
void AffineTransformationMatrix::scale (FixedInt width, FixedInt height)
{
  vector<FixedInt>atm;

  atm.push_back(width);
  atm.push_back(0);
  atm.push_back(0);

  atm.push_back(0);
  atm.push_back(height);
  atm.push_back(0);

  atm.push_back(0);
  atm.push_back(0);
  atm.push_back(1);

  multiplyMatrix(atm);

  scaleX*=width;scaleY*=height;
}

//screen axes are different from cartesian coordinates.
void AffineTransformationMatrix::rotate (FixedInt angle)
{
  vector<FixedInt>atm;

  atm.push_back(cosine(angle.toAngle()));
  atm.push_back(sine(angle.toAngle())*-1);
  atm.push_back(0);

  atm.push_back(sine(angle.toAngle()));
  atm.push_back(cosine(angle.toAngle()));
  atm.push_back(0);

  atm.push_back(0);
  atm.push_back(0);
  atm.push_back(1);

  multiplyMatrix(atm);

  ang+=angle;
}

void AffineTransformationMatrix::shift (FixedInt x, FixedInt y)
{
  vector<FixedInt>atm;

  atm.push_back(1);
  atm.push_back(0);
  atm.push_back(x);

  atm.push_back(0);
  atm.push_back(1);
  atm.push_back(y);

  atm.push_back(0);
  atm.push_back(0);
  atm.push_back(1);

  multiplyMatrix(atm);

  shiftX+=x;
  shiftY+=y;
}

void AffineTransformationMatrix::shear (FixedInt horiz, FixedInt vert)
{
  vector<FixedInt>atm;

  atm.push_back(1);
  atm.push_back(horiz);
  atm.push_back(0);

  atm.push_back(vert);
  atm.push_back(1);
  atm.push_back(0);

  atm.push_back(0);
  atm.push_back(0);
  atm.push_back(1);

  multiplyMatrix(atm);
}

FixedInt AffineTransformationMatrix::getScaleX()
{
  return scaleX;
}

FixedInt AffineTransformationMatrix::getScaleY()
{
  return scaleY;
}

FixedInt AffineTransformationMatrix::getAngle()
{
  return ang;
}

FixedInt AffineTransformationMatrix::getShiftX()
{
  return shiftX;
}

FixedInt AffineTransformationMatrix::getShiftY()
{
  return shiftY;
}

//This matrix, applied to a point, will put a point onto a line with the direction (ux, uy) that passes through the origin.
void AffineTransformationMatrix::orthographicProjection(FixedInt ux, FixedInt uy)
{
  vector<FixedInt>atm;

  FixedInt scalar=ux*ux + uy*uy;

  atm.push_back((ux*ux)/scalar);
  atm.push_back((ux*uy)/scalar);
  atm.push_back(0);

  atm.push_back((ux*uy)/scalar);
  atm.push_back((uy*uy)/scalar);
  atm.push_back(0);

  atm.push_back(0);
  atm.push_back(0);
  atm.push_back(1);

  multiplyMatrix(atm); 
}
