/* $RCSfile$
 * $Author$
 * $Date$
 * $Revision$
 *
 * Copyright (C) 2002-2007  The Chemistry Development Kit (CDK) project
 *
 * Contact: cdk-devel@lists.sourceforge.net
 *
 * This program 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 2.1
 * of the License, or (at your option) any later version.
 * All we ask is that proper credit is given for our work, which includes
 * - but is not limited to - adding the above copyright notice to the beginning
 * of your source code files, and to any copyright notice that you may distribute
 * with programs based on this work.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *  */
package org.openscience.cdk.geometry;

import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.annotations.TestMethod;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.ICrystal;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

/**
 * A set of static methods for working with crystal coordinates.
 *
 * @cdk.module standard
 * @cdk.githash
 *
 * @author  Egon Willighagen <egonw@sci.kun.nl>
 *
 * @cdk.keyword fractional coordinates, crystal
 */
@TestClass("org.openscience.cdk.geometry.CrystalGeometryToolsTest")
public class CrystalGeometryTools {

    /**
     * Inverts three cell axes.
     *
     * @return         a 3x3 matrix with the three cartesian vectors representing
     *                 the unit cell axes. The a axis is the first row.
     */
    @TestMethod("testCalcInvertedAxes_Vector3d_Vector3d_Vector3d")
    public static Vector3d[] calcInvertedAxes(Vector3d aAxis, Vector3d bAxis, Vector3d cAxis) {
         double det = aAxis.x*bAxis.y*cAxis.z - aAxis.x*bAxis.z*cAxis.y -
                      aAxis.y*bAxis.x*cAxis.z + aAxis.y*bAxis.z*cAxis.x +
                      aAxis.z*bAxis.x*cAxis.y - aAxis.z*bAxis.y*cAxis.x;
         Vector3d[] invaxes = new Vector3d[3];
         invaxes[0] = new Vector3d();
         invaxes[0].x = (bAxis.y*cAxis.z - bAxis.z*cAxis.y)/det;
         invaxes[0].y = (bAxis.z*cAxis.x - bAxis.x*cAxis.z)/det;
         invaxes[0].z = (bAxis.x*cAxis.y - bAxis.y*cAxis.x)/det;

         invaxes[1] = new Vector3d();
         invaxes[1].x = (aAxis.z*cAxis.y - aAxis.y*cAxis.z)/det;
         invaxes[1].y = (aAxis.x*cAxis.z - aAxis.z*cAxis.x)/det;
         invaxes[1].z = (aAxis.y*cAxis.x - aAxis.x*cAxis.y)/det;

         invaxes[2] = new Vector3d();
         invaxes[2].x = (aAxis.y*bAxis.z - aAxis.z*bAxis.y)/det;
         invaxes[2].y = (aAxis.z*bAxis.x - aAxis.x*bAxis.z)/det;
         invaxes[2].z = (aAxis.x*bAxis.y - aAxis.y*bAxis.x)/det;
         return invaxes;
    }

    /**
     * @cdk.dictref blue-obelisk:convertCartesianIntoFractionalCoordinates
     */
    @TestMethod("testCartesianToFractional_Vector3d_Vector3d_Vector3d_Point3d")
    public static Point3d cartesianToFractional(Vector3d aAxis, Vector3d bAxis, Vector3d cAxis,
                                                 Point3d cartPoint) {
        Vector3d[] invaxis = calcInvertedAxes(aAxis,bAxis,cAxis);
        Point3d frac = new Point3d();
        frac.x = invaxis[0].x*cartPoint.x + invaxis[0].y*cartPoint.y +
                 invaxis[0].z*cartPoint.z;
        frac.y = invaxis[1].x*cartPoint.x + invaxis[1].y*cartPoint.y +
                 invaxis[1].z*cartPoint.z;
        frac.z = invaxis[2].x*cartPoint.x + invaxis[2].y*cartPoint.y +
                 invaxis[2].z*cartPoint.z;
        return frac;
    }

    /**
     * @cdk.dictref blue-obelisk:convertFractionIntoCartesianCoordinates
     */
    @TestMethod("testFractionalToCartesian_Vector3d_Vector3d_Vector3d_Point3d")
    public static Point3d fractionalToCartesian(Vector3d aAxis, Vector3d bAxis, Vector3d cAxis,
                                                 Point3d frac) {
        Point3d cart = new Point3d();
        cart.x = frac.x*aAxis.x + frac.y*bAxis.x + frac.z*cAxis.x;
        cart.y = frac.x*aAxis.y + frac.y*bAxis.y + frac.z*cAxis.y;
        cart.z = frac.x*aAxis.z + frac.y*bAxis.z + frac.z*cAxis.z;
        return cart;
    }

    /**
     * Calculates cartesian vectors for unit cell axes from axes lengths and angles
     * between axes.
     *
     * <p>To calculate cartesian coordinates, it places the a axis on the x axes,
     * the b axis in the xy plane, making an angle gamma with the a axis, and places
     * the c axis to fullfil the remaining constraints. (See also
     * <a href="http://server.ccl.net/cca/documents/molecular-modeling/node4.html">the 
     * CCL archive</a>.)
     *
     * @param alength   length of the a axis
     * @param blength   length of the b axis
     * @param clength   length of the c axis
     * @param alpha     angle between b and c axes in degrees
     * @param beta      angle between a and c axes in degrees
     * @param gamma     angle between a and b axes in degrees
     * @return          an array of Vector3d objects with the three cartesian vectors representing
     *                  the unit cell axes.
     *
     * @cdk.keyword  notional coordinates
     * @cdk.dictref  blue-obelisk:convertNotionalIntoCartesianCoordinates
     */
    @TestMethod("testNotionalToCartesian_double_double_double_double_double_double")
    public static Vector3d[] notionalToCartesian(double alength, double blength,
                                                 double clength, double alpha,
                                                 double beta, double gamma) {
        Vector3d[] axes = new Vector3d[3];
        
        /* 1. align the a axis with x axis */
        axes[0] = new Vector3d();
        axes[0].x = alength;
        axes[0].y = 0.0;
        axes[0].z = 0.0;

        double toRadians = Math.PI/180.0;
        
        /* some intermediate variables */
        double cosalpha = Math.cos(toRadians*alpha);
        double cosbeta = Math.cos(toRadians*beta);
        double cosgamma = Math.cos(toRadians*gamma);
        double singamma = Math.sin(toRadians*gamma);

        /* 2. place the b is in xy plane making a angle gamma with a */
        axes[1] = new Vector3d();
        axes[1].x = blength*cosgamma;
        axes[1].y = blength*singamma;
        axes[1].z = 0.0;

        /* 3. now the c axis, with more complex maths */
        axes[2] = new Vector3d();
        double volume = alength * blength * clength *
                        Math.sqrt(1.0 - cosalpha*cosalpha -
                                  cosbeta*cosbeta -
                                  cosgamma*cosgamma +
                                  2.0*cosalpha*cosbeta*cosgamma);
        axes[2].x = clength*cosbeta;
        axes[2].y = clength*(cosalpha-cosbeta*cosgamma)/singamma;
        axes[2].z = volume/(alength*blength*singamma);
        
        return axes;
    }
    
    /**
     * @cdk.dictref  blue-obelisk:convertCartesianIntoNotionalCoordinates
     */
    @TestMethod("testCartesianToNotional_Vector3d_Vector3d_Vector3d")
    public static double[] cartesianToNotional(Vector3d aAxis, Vector3d bAxis, Vector3d cAxis) {
        double[] notionalCoords = new double[6];
        notionalCoords[0] = aAxis.length();
        notionalCoords[1] = bAxis.length();
        notionalCoords[2] = cAxis.length();
        notionalCoords[3] = bAxis.angle(cAxis)*180.0/Math.PI;
        notionalCoords[4] = aAxis.angle(cAxis)*180.0/Math.PI;
        notionalCoords[5] = aAxis.angle(bAxis)*180.0/Math.PI;
        return notionalCoords;
    }
                               
	/**
     * Determines if this model contains fractional (crystal) coordinates.
	 *
	 * @return  boolean indication that 3D coordinates are available 
	 */
    public static boolean hasCrystalCoordinates(IAtomContainer container) {
    	java.util.Iterator atoms = container.atoms().iterator();
        while (atoms.hasNext()) {
            if (((IAtom)atoms.next()).getFractionalPoint3d() == null) {
                return false;
            }
        }
        return true;
    }

	/**
     * Creates cartesian coordinates for all Atoms in the Crystal.
	 */
    public static void fractionalToCartesian(ICrystal crystal) {
    	java.util.Iterator atoms = crystal.atoms().iterator();
        Vector3d aAxis = crystal.getA();
        Vector3d bAxis = crystal.getB();
        Vector3d cAxis = crystal.getC();
        while (atoms.hasNext()) {
        	IAtom atom = (IAtom)atoms.next();
            Point3d fracPoint = atom.getFractionalPoint3d();
            if (fracPoint != null) {
                atom.setPoint3d(fractionalToCartesian(aAxis,bAxis,cAxis, fracPoint));
            }
        }
    }
}



