/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2005/08/10, 11:01
!  AUTHOR(S): KOGA, Junichiro
!  File : UnitCellOperations.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */

package ciss.phase_viewer.acviewer.operations;

import java.util.Vector;

import org.apache.log4j.Logger;

import ciss.phase_viewer.acviewer.CoordsViewerInterface;
import ciss.phase_viewer.atomcoord.Atom;
import ciss.phase_viewer.atomcoord.AtomCoords;
import ciss.phase_viewer.atomcoord.AtomList;
import ciss.phase_viewer.atomcoord.Cell;
import ciss.phase_viewer.common.VectorOperations;

/**
 * performs the following operations regarding the unit cell: - add boundary
 * atoms - pack atoms into unit cell the manipulations of this class acts on all
 * frames, and must be done before the main panel is booted.
 * 
 * @author KOGA, Junichiro
 */
public class UnitCellOperations {
    public static Logger logger = Logger.getLogger(UnitCellOperations.class
            .getName());

    private Vector frames;

    private double eps = 0.00001d;

    private boolean packAtoms = false;

    private boolean ab = false;

    private boolean ac = false;

    private boolean bc = false;

    private int curframe;

    private CoordsViewerInterface parent;

    /**
     * Creates a new instance of UnitCellOperations
     * 
     * @param frames
     *            Vector holding atom coords objects.
     */
    public UnitCellOperations(Vector frames, CoordsViewerInterface parent) {
        this.frames = frames;
        this.parent = parent;
    }

    public UnitCellOperations(AtomCoords coord, CoordsViewerInterface parent) {
        this.frames = new Vector();
        frames.add(coord);
        this.parent = parent;
    }

    /**
     * set the parameter 'eps'. The atoms are deemed to be on the plane defined
     * by the cell vectors if they are less than eps apart.
     * 
     * @param eps
     *            the value of eps, in angstrom
     */
    public void setEps(double eps) {
        this.eps = eps;
    }

    /**
     * set flag which specifies whether to pack all atoms into unit cell or not.
     * 
     * @param packAtoms
     *            when true, all atoms will be packed into the unit cell.
     */
    public void setPackAtomsIntoUnitCell(boolean packAtoms) {
        this.packAtoms = packAtoms;
    }

    /**
     * set flags which specifies whether to add boundary atoms or not.
     * 
     * @param ab
     *            when true, add boundary atoms for the a-b plane.
     * @param ac
     *            when true, add boundary atoms for the a-c plane.
     * @param bc
     *            when true, add boundary atoms for the b-c plane.
     */
    public void setAddBoundaryAtoms(boolean ab, boolean ac, boolean bc) {
        this.ab = ab;
        this.ac = ac;
        this.bc = bc;
    }

    /**
     * pack and add boundary atoms for all frames. does nothing if all flags are
     * set to false.
     */
    public void processFrame() {
        for (int i = 0; i < frames.size(); i++) {
            AtomCoords cds = (AtomCoords) frames.elementAt(i);
            cds.removeBoundaryAtoms();
        }
        if (!packAtoms && !ab && !ac && !bc) {
            return;
        }

        if (frames == null) {
            logger.error("'frames' vector is null.");
            return;
        }

        if (frames.size() == 0) {
            logger.error("no frames.");
            return;
        }

        for (int i = 0; i < frames.size(); i++) {
            AtomCoords cds = (AtomCoords) frames.elementAt(i);
            if (packAtoms) {
                packAtomsIntoCell(cds);
            }
            if (ab || ac || bc) {
                addBoundaryAtoms(cds);
            }
        }
    }

    /**
     * pack and add boundary atom for a particular frame.
     */
    public void processFrame(AtomCoords cds) {
        if (!packAtoms && !ab && !ac && !bc) {
            return;
        }
        if (packAtoms) {
            packAtomsIntoCell(cds);
        }
        if (ab || ac || bc) {
            addBoundaryAtoms(cds);
        }
    }

    public static void packAtomsIntoCell(AtomCoords cds) {
        AtomList atomList = cds.getAtomList();
        double[][] cell = cds.getCellDouble(0);
        double[] cellOrigin = cds.getCell()[0].getCellOffset();

        if (cell == null) {
            logger.error("invalid cell!");
            return;
        }

        double[][] plane01 = new double[2][3];
        double[][] plane02 = new double[2][3];
        double[][] plane12 = new double[2][3];

        plane01[0] = cell[0];
        plane01[1] = cell[1];
        plane02[0] = cell[2];
        plane02[1] = cell[0];
        plane12[0] = cell[1];
        plane12[1] = cell[2];

        int nat = atomList.size();
        double[] orig = new double[3];
        double[][] pospos = cds.getPosDouble();
        double[][] shiftedCell = new double[3][3];
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                orig[j] = -cellOrigin[j];
                shiftedCell[i][j] = cell[i][j] + orig[j];
            }
        }

        for (int i = 0; i < nat; i++) {
            double[] pos = pospos[i];

            double r01 = VectorOperations.getDistanceFromPlane(pos, plane01[0],
                    plane01[1], orig, true);
            double r10 = VectorOperations.getDistanceFromPlane(pos, plane01[1],
                    plane01[0], shiftedCell[2], true);

            double r02 = VectorOperations.getDistanceFromPlane(pos, plane02[0],
                    plane02[1], orig, true);
            double r20 = VectorOperations.getDistanceFromPlane(pos, plane02[1],
                    plane02[0], shiftedCell[1], true);

            double r12 = VectorOperations.getDistanceFromPlane(pos, plane12[0],
                    plane12[1], orig, true);
            double r21 = VectorOperations.getDistanceFromPlane(pos, plane12[1],
                    plane12[0], shiftedCell[0], true);

            if (r01 < 0) {
                for (int j = 0; j < 3; j++) {
                    pos[j] += cell[2][j];
                }
            } else if (r10 < 0) {
                for (int j = 0; j < 3; j++) {
                    pos[j] -= cell[2][j];
                }
            }

            if (r02 < 0) {
                for (int j = 0; j < 3; j++) {
                    pos[j] += cell[1][j];
                }
            } else if (r20 < 0) {
                for (int j = 0; j < 3; j++) {
                    pos[j] -= cell[1][j];
                }
            }

            if (r12 < 0) {
                for (int j = 0; j < 3; j++) {
                    pos[j] += cell[0][j];
                }
            } else if (r21 < 0) {
                for (int j = 0; j < 3; j++) {
                    pos[j] -= cell[0][j];
                }
            }
            pospos[i] = pos;
        }
        cds.setPosDouble(pospos, true);
    }

    private void addBoundaryAtoms(AtomCoords cds) {
        Cell cell = cds.getCell()[0];
        AtomList atomList = cds.getAtomList();
        double[] cellOrigin = cds.getCell()[0].getCellOffset();
        if (parent.getCD() == null)
            return;
        double[][] dcell = parent.getCD().getCell();

        double[][] plane1 = new double[2][3];
        double[][] plane2 = new double[2][3];
        logger.debug("size of atomlist: " + atomList.size());
        int natm = atomList.size();

        double[][] plane = new double[2][3];

        // for (0-1) and (0-1)' plane
        if (ab) {
            plane[0] = dcell[0];
            plane[1] = dcell[1];
            addAtomToOppositePlane(plane, dcell[2], cds);
        }

        if (ac) {
            // for (0-2) and (0-2)' plane
            plane[0] = dcell[0];
            plane[1] = dcell[2];
            addAtomToOppositePlane(plane, dcell[1], cds);
        }

        if (bc) {
            // for (1-2) and (1-2)' plane
            plane[0] = dcell[1];
            plane[1] = dcell[2];
            addAtomToOppositePlane(plane, dcell[0], cds);
        }

    }

    private void addAtomToOppositePlane(double[][] plane, double[] shiftVector,
            AtomCoords cds) {
        // double [] orig = new double [] {0.d,0.d,0.d};

        double[] origcell = cds.getCell()[0].getCellOffset();
        double[] shiftedCell = new double[3];
        double[] orig = new double[3];
        for (int j = 0; j < 3; j++) {
            orig[j] = -origcell[j];
            shiftedCell[j] = shiftVector[j] + orig[j];
        }
        AtomList list = cds.getAtomList();
        Vector newPos = new Vector();
        Vector newPosIndex = new Vector();
        for (int i = 0; i < list.size(); i++) {
            double[] pos = list.getAtomAt(i).getDouble();
            double r = VectorOperations.getDistanceFromPlane(pos, plane[0],
                    plane[1], orig);
            double rop = VectorOperations.getDistanceFromPlane(pos, plane[1],
                    plane[0], shiftedCell);
            if (r < eps) {
                Double[] npos = new Double[3];
                for (int j = 0; j < 3; j++) {
                    npos[j] = new Double(pos[j] + shiftVector[j]);
                }
                newPos.addElement(npos);
                newPosIndex.addElement(new Integer(i));
            } else if (rop < eps) {
                Double[] npos = new Double[3];
                for (int j = 0; j < 3; j++) {
                    npos[j] = new Double(pos[j] - shiftVector[j]);
                }
                newPos.addElement(npos);
                newPosIndex.addElement(new Integer(i));
            }
        }

        if (newPos == null || newPos.size() == 0) {
            return;
        }

        int undoCount = 0;
        for (int i = 0; i < newPos.size(); i++) {
            Double[] npos = (Double[]) newPos.elementAt(i);
            double[] tmp = new double[3];
            for (int j = 0; j < 3; j++) {
                tmp[j] = npos[j].doubleValue();
            }

            int posIndex = ((Integer) newPosIndex.elementAt(i)).intValue();
            Atom atom = cds.getAtomList().getAtomAt(posIndex);
            Atom newAtom = atom.getCopy();
            newAtom.setPos(tmp);
            newAtom.isBoundaryAtom(true);
            if (!cds.checkDupli(newAtom)) {
                cds.getAtomList().addAtom(newAtom);
                undoCount++;
            }
        }
        cds.getAtomList().setUndoCount(undoCount);
    }

}
