//  $Id$
// 
//   Copyright (C) 2003-2006 Rational Discovery LLC
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
#include <RDGeneral/Invariant.h>
#include <cstring>
#include "Transform.h"
#include "Transform2D.h"
#include <math.h>
#include "point.h"

namespace RDGeom {
  
  void Transform2D::setToIdentity() {
    unsigned int i, id;
    double *data = d_data.get();
    memset(static_cast<void *>(data),0,d_dataSize*sizeof(double));
    for (i = 0; i < DIM_2D; i++) {
      id = i*(DIM_2D+1);
      data[id] = 1.0;
    }
  }
  
  void Transform2D::TransformPoint(Point2D &pt) const {
    double x, y;
    double *data = d_data.get();
    x = data[0]*pt.x + data[1]*pt.y + data[2];
    y = data[3]*pt.x + data[4]*pt.y + data[5];
        
    pt.x = x;
    pt.y = y;
  }

  void Transform2D::SetTranslation(const Point2D &pt) {
    unsigned int i = DIM_2D -1;
    double *data = d_data.get();
    data[i] = pt.x; i += DIM_2D;
    data[i] = pt.y; i += DIM_2D;
    data[i] = 1.0; i += DIM_2D;
  }

  void Transform2D::SetTransform(const Point2D &pt, double angle) {
    this->setToIdentity();
    
    Transform2D trans1;
    trans1.SetTranslation(-pt);
    double *data = d_data.get();
    // set the rotation
    data[0] = cos(angle);
    data[1] = -sin(angle);
    data[3] = sin(angle);
    data[4] = cos(angle);
    
    (*this) *= trans1;

    // translation back to the orignal coordinate
    Transform2D trans2;
    trans2.SetTranslation(pt);
    trans2 *= (*this);

    // now combine them
    this->assign(trans2);
  }
    
    
  void Transform2D::SetTransform(const Point2D &ref1, const Point2D &ref2,
                                 const Point2D &pt1, const Point2D &pt2) {
    // compute the angle between the two vectors
    Point2D rvec = ref2 - ref1;
    Point2D pvec = pt2 - pt1;

    double dp = rvec.dotProduct(pvec);
    double lp = (rvec.length())*(pvec.length());
    if(lp<=0.0){
      this->setToIdentity();
      return;
    }

    double cval = dp/lp;
    if (cval < -1.0) {
      cval = -1.0;
    } else if (cval > 1.0) {
      cval = 1.0;
    }

    double ang = acos(cval);
    
    // figure out if we have to do clock wise or anti clock wise rotation
    double cross = (pvec.x)*(rvec.y) - (pvec.y)*(rvec.x);
    
    if (cross < 0.0) { 
      ang *= -1.0;
    }
    this->setToIdentity();
    // set the rotation 
    double *data = d_data.get();
    data[0] = cos(ang);
    data[1] = -sin(ang);
    data[3] = sin(ang);
    data[4] = cos(ang);
    
    // apply this rotation to pt1 and compute the translation
    Point2D npt1 = pt1;
    this->TransformPoint(npt1);
    data[DIM_2D-1] = ref1.x - npt1.x;
    data[2*DIM_2D-1] = ref1.y - npt1.y;
    
  }
  
}

RDGeom::Transform2D operator* (const RDGeom::Transform2D &t1, const RDGeom::Transform2D &t2) {
  RDGeom::Transform2D res;
  RDNumeric::multiply(t1, t2, res);
  return res;
};
