/**
 *    Copyright 2011 Peter Murray-Rust et. al.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.xmlcml.cml.element;

import java.util.ArrayList;
import java.util.List;

import nu.xom.Element;
import org.xmlcml.cml.base.CMLElement;
import org.xmlcml.cml.base.CMLElements;

/**
 * user-modifiable class supporting lattice. * autogenerated from schema use as
 * a shell which can be edited
 *
 */
public class CMLLattice extends AbstractLattice {

	/** namespaced element name.*/
	public final static String NS = C_E+TAG;

    /**
     * constructor.
     */
    public CMLLattice() {
    }

    /**
     * constructor.
     *
     * @param old
     */
    public CMLLattice(CMLLattice old) {
        super((AbstractLattice) old);
    }

    /**
     * copy node .
     *
     * @return Node
     */
    public Element copy() {
        return new CMLLattice(this);
    }

    /**
     * create new instance in context of parent, overridable by subclasses.
     *
     * @param parent
     *            parent of element to be constructed (ignored by default)
     * @return CMLLattice
     */
    public CMLElement makeElementInContext(Element parent) {
        return new CMLLattice();
    }

    /**
     * constructor.
     *
     * @param lv
     *            latticeVectors
     */
    public CMLLattice(CMLLatticeVector[] lv) {
        this();
        if (lv == null || lv.length != 3) {
            throw new RuntimeException("Invalid latticeVectors");
        }
        addLatticeVectors(lv[0], lv[1], lv[2]);
    }

    /**
     * constructor.
     *
     * @param a
     *            vector
     * @param b
     *            vector
     * @param c
     *            vector
     */
    public CMLLattice(CMLLatticeVector a, CMLLatticeVector b, CMLLatticeVector c) {
        this();
        addLatticeVectors(a, b, c);
    }

    void addLatticeVectors(CMLLatticeVector a, CMLLatticeVector b,
            CMLLatticeVector c) {
        if (a == null) {
            throw new RuntimeException("Null latticeVector");
        }
        this.appendChild(a);
        if (b == null) {
            throw new RuntimeException("Null latticeVector");
        }
        this.appendChild(b);
        if (c == null) {
            throw new RuntimeException("Null latticeVector");
        }
        this.appendChild(c);
    }

    /**
     * get cell parameters in crystallographic form. units are angstrom and
     * degrees
     *
     * @return 3 lengths and 3 angles
     */
    public double[] getCellParameters() {
        CMLVector3 a = this.getLatticeVectorElements().get(0).getCMLVector3();
        CMLVector3 b = this.getLatticeVectorElements().get(1).getCMLVector3();
        CMLVector3 c = this.getLatticeVectorElements().get(2).getCMLVector3();
        double[] params = new double[6];
        params[0] = a.getLength();
        params[1] = b.getLength();
        params[2] = c.getLength();
        params[3] = b.getAngleMadeWith(c).getDegrees();
        params[4] = c.getAngleMadeWith(a).getDegrees();
        params[5] = a.getAngleMadeWith(b).getDegrees();
        return params;
    }

    /**
     * get i'th lattice vector as CMLVector.
     *
     * @param i
     *            index of vector (0 &le; i &le; 2)
     * @return vector or null
     */
    public CMLVector3 getCMLVector3(int i) {
        CMLElements<CMLLatticeVector> abc = this.getLatticeVectorElements();
        return (abc == null || i < 0 || i > 2) ? null : abc.get(i)
                .getCMLVector3();
    }

    /**
     * get volume. this is a signed quantity, depending on handedness of
     * vectors.
     *
     * @return volume
     */
    public double getVolume() {
        CMLElements<CMLLatticeVector> latticeVectors = this
                .getLatticeVectorElements();
        if (latticeVectors.size() != 3) {
            throw new RuntimeException("Cannot calculate volume without vectors");
        }
        return this.getCMLVector3(0).getScalarTripleProduct(
                this.getCMLVector3(1), this.getCMLVector3(2));
    }

    /**
     * get reduced cell. starts with lattice with 3 non-coplanar vectors and
     * iterates through all sums and differences, continually taking the
     * shortest 3 vectors.
     *
     * @return the new lattice
     */
    public CMLLattice getReducedCell() {
        if (Math.abs(this.getVolume()) < EPS) {
            throw new RuntimeException("coplanar vectors");
        }
        List<CMLVector3> vList = new ArrayList<CMLVector3>();
        for (int i = 0; i < 3; i++) {
            vList.add(new CMLVector3(this.getCMLVector3(i)));
        }
        sort(vList);
        boolean change = true;
        int count = 0;
        while (change || count++ < 10) {
            change = false;
            change = newVector(vList, 0, 1);
            if (!change) {
                change = newVector(vList, 0, 2);
            }
            if (!change) {
                change = newVector(vList, 1, 2);
            }
        }
        sort(vList);
        CMLLattice reducedLattice = new CMLLattice(new CMLLatticeVector(vList
                .get(0)), new CMLLatticeVector(vList.get(1)),
                new CMLLatticeVector(vList.get(2)));
        return reducedLattice;
    }

    private void sort(List<CMLVector3> vList) {
        sort(vList, 0, 1);
        sort(vList, 1, 2);
        sort(vList, 0, 1);
    }

    private void sort(List<CMLVector3> vList, int i, int j) {
        CMLVector3 ti = vList.get(i);
        CMLVector3 tj = vList.get(j);
        if (tj.getLength() < ti.getLength()) {
            vList.set(j, ti);
            vList.set(i, tj);
        }
    }

    /**
     * try various combinations of plus and minus vectors. if any are shorter,
     * replace and return true
     *
     * @param vList
     * @param i
     * @param j
     * @return true if change
     */
    private boolean newVector(List<CMLVector3> vList, int i, int j) {
        CMLVector3 ti = vList.get(i);
        double li = ti.getLength();
        CMLVector3 tj = vList.get(j);
        double lj = tj.getLength();
        boolean change = false;
        // compute difference
        CMLVector3 tij = ti.subtract(tj);
        // copmare with both lengths
        if (tij.getLength() < li) {
            vList.set(i, tij);
            change = true;
        }
        if (!change && tij.getLength() < lj) {
            vList.set(j, tij);
            change = true;
        }
        // compare sum
        if (!change) {
            tij = ti.plus(tj);
            if (tij.getLength() < li) {
                vList.set(i, tij);
                change = true;
            }
            if (!change && tij.getLength() < lj) {
                vList.set(j, tij);
                change = true;
            }
        }
        return change;
    }

    /**
     * gets a string representation of the lattice.
     *
     * @return the string (never null);
     */
    public String getString() {
        String s = "";
        for (CMLLatticeVector lv : this.getLatticeVectorElements()) {
            s += S_LSQUARE + lv.getStringContent() + S_RSQUARE;
            s += S_LBRAK + lv.getCMLVector3().getLength() + S_RBRAK;
        }
        return s;
    }
}
