package ciss.phase_viewer.plugins.viewer.edit;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.media.j3d.Appearance;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.QuadArray;
import javax.media.j3d.RestrictedAccessException;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TransparencyAttributes;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix4d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import org.apache.log4j.Logger;
import org.jdom.DataConversionException;
import org.jdom.Element;

import Jama.Matrix;
import ciss.phase_viewer.acviewer.ACVAction;
import ciss.phase_viewer.acviewer.ChaseTransformGroup;
import ciss.phase_viewer.acviewer.MainPanel;
import ciss.phase_viewer.acviewer.geom.Plane;
import ciss.phase_viewer.acviewer.scenegraphelements.TGAtom;
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.ConstParameters;
import ciss.phase_viewer.mainpanel.Desk;
import ciss.phase_viewer.mainpanel.InternalFrameChase;
import ciss.phase_viewer.primitiveguis.ValueSlider;
import ciss.phase_viewer.primitiveguis.ValueSliderListener;

/**
 * 
 * @author
 */
public class SurfaceAction extends ACVAction {
	private Logger logger = Logger.getLogger(SurfaceAction.class.getName());

	/** Creates a new instance of UnitCellAction */
	public SurfaceAction(MainPanel parent) {
		super(parent);
	}

	public void actionPerformed(ActionEvent e) {
		new SurfacePanel((MainPanel) parent);
	}

}

class SurfacePanel extends InternalFrameChase {
	private Logger logger = Logger.getLogger(SurfacePanel.class.getName());
	private MainPanel parent;

	public SurfacePanel(MainPanel panel) {
		super("generate surface", new java.awt.Dimension(420, 420));
		this.parent = panel;
		this.parent.addDisposeOnExit(this);
		if (this.parent.getCD() == null) {
			dispose();
			JOptionPane.showInternalMessageDialog(Desk.getDesktop().getSelectedFrame(),
					"Atomic coordinates not allocated");
			logger.error("Atomic coordinates not allocated");
			return;
		}
		init();
	}

	private JTextField tfh;
	private JTextField tfk;
	private JTextField tfl;
	private JTextField tfsx;
	private JTextField tfsy;
	private JTextField tfsz;
	private JTextField tfvac;
	private ValueSlider vslider;
	private JCheckBox cbperp;

	public void dispose() {
		if (guidePlane != null)
			guidePlane.removeGuidePlane();
		super.dispose();
	}

	private void init() {
		JPanel millerp = new JPanel();
		millerp.setBorder(new TitledBorder("miller index"));
		millerp.setLayout(new BoxLayout(millerp, BoxLayout.X_AXIS));
		tfh = new JTextField(8);
		tfh.setHorizontalAlignment(JTextField.CENTER);
		tfk = new JTextField(8);
		tfk.setHorizontalAlignment(JTextField.CENTER);
		tfl = new JTextField(8);
		tfl.setHorizontalAlignment(JTextField.CENTER);
		tfh.setText("0");
		tfk.setText("0");
		tfl.setText("1");
		JPanel ph = new JPanel();
		ph.setBorder(new TitledBorder("h"));
		ph.add(tfh);
		JPanel pk = new JPanel();
		pk.setBorder(new TitledBorder("k"));
		pk.add(tfk);
		JPanel pl = new JPanel();
		pl.setBorder(new TitledBorder("l"));
		pl.add(tfl);
		millerp.add(ph);
		millerp.add(pk);
		millerp.add(pl);

		JPanel supp = new JPanel();
		supp.setBorder(new TitledBorder("super cell"));
		supp.setLayout(new BoxLayout(supp, BoxLayout.X_AXIS));
		tfsx = new JTextField(8);
		tfsx.setHorizontalAlignment(JTextField.CENTER);
		tfsy = new JTextField(8);
		tfsy.setHorizontalAlignment(JTextField.CENTER);
		tfsz = new JTextField(8);
		tfsz.setHorizontalAlignment(JTextField.CENTER);
		tfsx.setText("1");
		tfsy.setText("1");
		tfsz.setText("1");
		JPanel psx = new JPanel();
		psx.setBorder(new TitledBorder("a"));
		psx.add(tfsx);
		JPanel psy = new JPanel();
		psy.setBorder(new TitledBorder("b"));
		psy.add(tfsy);
		JPanel psz = new JPanel();
		psz.setBorder(new TitledBorder("c"));
		psz.add(tfsz);
		supp.add(psx);
		supp.add(psy);
		supp.add(psz);

		AtomicConfiguration conf = getCurrAtomicConfiguration();
		double[] lconst = conf.getLatConst();
		float max = 0f;
		for (int i = 0; i < 3; i++)
			if (max < lconst[i])
				max = (float) lconst[i];
		vslider = new ValueSlider((float) -Math.ceil(2 * max), (float) Math.ceil(2 * max));
		vslider.setTitleForBorder("shift");

		tfvac = new JTextField(8);
		tfvac.setHorizontalAlignment(JTextField.CENTER);
		tfvac.setText("0.0");
		JPanel pvac = new JPanel();
		pvac.setBorder(new TitledBorder("vacuum"));
		pvac.add(tfvac);

		JPanel pp = new JPanel();
		pp.setLayout(new BoxLayout(pp, BoxLayout.X_AXIS));
		pp.add(vslider);
		// pp.add(pvac);

		cbperp = new JCheckBox("set c-axis perpendicular to the ab plane");
		cbperp.setSelected(true);
		JPanel pperp = new JPanel();
		pperp.add(cbperp);
		pperp.add(pvac);

		JButton apply = new JButton("apply");
		JButton close = new JButton("close");
		JPanel pbtm = new JPanel();
		pbtm.setLayout(new BoxLayout(pbtm, BoxLayout.X_AXIS));
		pbtm.add(apply);
		pbtm.add(close);

		apply.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				generateSurface();
				guidePlane.removeGuidePlane();
			}
		});

		close.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				dispose();
			}
		});

		JPanel jpanel = new JPanel();
		jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS));
		jpanel.add(millerp);
		jpanel.add(supp);
		jpanel.add(pp);
		jpanel.add(pperp);
		jpanel.add(pbtm);
		getContentPane().add(jpanel);
		initGuidePlane();

		tfh.addCaretListener(new CaretListener() {
			@Override
			public void caretUpdate(CaretEvent e) {
				updateGuidePlane();
			}
		});

		tfk.addCaretListener(new CaretListener() {
			@Override
			public void caretUpdate(CaretEvent e) {
				updateGuidePlane();
			}
		});

		tfl.addCaretListener(new CaretListener() {
			@Override
			public void caretUpdate(CaretEvent e) {
				updateGuidePlane();
			}
		});

		vslider.addValueSliderListener(new ValueSliderListener() {
			@Override
			public void valueSliderValueChanged() {
				updateGuidePlane();
			}
		});
		updateGuidePlane();
	}

	private float[][] bounds;
	private GuidePlane guidePlane;

	private void initGuidePlane() {
		this.bounds = ((ChaseTransformGroup) parent.getRootTransform()).getEffectiveBounds();

		float sizex = (float) Math
				.sqrt(Math.pow(bounds[0][0], 2) + Math.pow(bounds[1][0], 2) + Math.pow(bounds[2][0], 2));
		float sizey = (float) Math
				.sqrt(Math.pow(bounds[0][1], 2) + Math.pow(bounds[1][1], 2) + Math.pow(bounds[2][1], 2));
		float sizez = (float) Math
				.sqrt(Math.pow(bounds[0][2], 2) + Math.pow(bounds[1][2], 2) + Math.pow(bounds[2][2], 2));
		logger.debug("effective bounds: " + sizex + ", " + sizey);
		if (guidePlane == null) {
			guidePlane = new GuidePlane(parent.getRootTransform(), sizex, sizey);
			guidePlane.zoffset = sizez * 0.5f;
		}
	}

	private void updateGuidePlane() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				int[] minds = getMillerIndex();
				if (minds != null) {
					Point3f p3f = AtomicConfiguration.getNormalVector(getCurrAtomicConfiguration(), minds);
					if (p3f != null)
						guidePlane.setNormalVector(p3f);
					guidePlane.updateGuidePlane();

					setCOMAndLenMax();
					Point3f pos = getPos();
					if (pos != null)
						guidePlane.setOrigin(pos);
					guidePlane.updateGuidePlane();
				}
			}
		});
	}

	private int[] getMillerIndex() {
		int[] ret = new int[] { 0, 0, 0 };
		try {
			ret[0] = Integer.parseInt(tfh.getText());
			ret[1] = Integer.parseInt(tfk.getText());
			ret[2] = Integer.parseInt(tfl.getText());
		} catch (NumberFormatException nfe) {
			logger.error("invalid miller index : (" + tfh.getText() + " " + tfk.getText() + " " + tfl.getText() + ")");
			return null;
		}
		logger.debug("miller index : (" + tfh.getText() + " " + tfk.getText() + " " + tfl.getText() + ")");
		return ret;
	}

	private void generateSurface() {
		int h = 0;
		int k = 0;
		int l = 0;
		int sx = 0;
		int sy = 0;
		int sz = 0;
		double vac = 0.0;
		double shift = 0.0;
		boolean perp = cbperp.isSelected();
		try {
			h = Integer.parseInt(tfh.getText());
			k = Integer.parseInt(tfk.getText());
			l = Integer.parseInt(tfl.getText());
			sx = Integer.parseInt(tfsx.getText());
			sy = Integer.parseInt(tfsy.getText());
			sz = Integer.parseInt(tfsz.getText());
			vac = Double.parseDouble(tfvac.getText());
			shift = vslider.getValue();
		} catch (NumberFormatException nfe) {
			nfe.printStackTrace();
			logger.error("miller index, supercell size, or the vacuum layer is invalid");
			return;
		}

		AtomCoords coor = parent.getCD().getAtomCoords();
		coor.saveState();
		boolean wasFract = coor.isInternal();
		int nat = coor.getAtomList().getNumAt();
		AtomicConfiguration conf = getCurrAtomicConfiguration();
		double sizez = Math.sqrt(Math.pow(bounds[0][2], 2) + Math.pow(bounds[1][2], 2) + Math.pow(bounds[2][2], 2));
		// conf.generateSurface(h, k, l, sx, sy, sz, vac, -shift, lenmax * sizez * 0.5,
		// perp);
		conf.generateSurface(h, k, l, sx, sy, sz, vac, -shift, 0, perp);
		Atom atmt = coor.getAtomList().getAtomAt(0);
		double[][] latvec = conf.getLatVec();
		coor.setCell(new Cell(latvec[0], latvec[1], latvec[2]));

		coor.convertCell(Cell.CELL_ABC);
		for (int i = 0; i < conf.getNumAt(); i++) {
			Atom atm = atmt.getCopy();
			atm.setElementName(conf.getElementName()[i]);
			atm.setPos(conf.getCartPos()[i]);
			if (i < nat)
				coor.getAtomList().replaceAtomAt(i, atm);
			else
				coor.getAtomList().addAtom(atm);
		}
		int undo = conf.getNumAt() + 1;
		if (wasFract) {
			coor.convert(AtomCoords.TO_INTERNAL, AtomCoords.TO_ANG);
			undo += conf.getNumAt();
		}
		coor.getAtomList().setUndoCount(undo);
		parent.getCD().setCoords(coor);
		coor.finalizeState();
	}

	private AtomicConfiguration getCurrAtomicConfiguration() {
		AtomCoords coor = parent.getCD().getAtomCoords();
		return AtomicConfiguration.getAtomicConfiguration(coor);
	}

	private void setCOMAndLenMax() {
		setCOMAndLenMax(true);
	}

	private double lenmax;
	private double[] jusin;

	private void setCOMAndLenMax(boolean check) {
		this.lenmax = 1;

		if (jusin == null) {
			this.jusin = new double[3];
			for (int i = 0; i < 3; i++)
				jusin[i] = 0d;
		}
		TGAtom tgatom = (TGAtom) parent.getRootTransform();
		// double[] jusin_buff = tgatom.getJusin();
		double[] jusin_buff = tgatom.getCOM();
		this.lenmax = tgatom.getLenMax();
		logger.debug("jusin1 " + jusin_buff[0] + " " + jusin_buff[1] + " " + jusin_buff[2]);
		jusin_buff = tgatom.getJusin();
		logger.debug("jusin2 " + jusin_buff[0] + " " + jusin_buff[1] + " " + jusin_buff[2]);
		// this.jusin = tgatom.getCOM();
		// double[] jusin_buff =
		// tgatom.getConfigData().getCellOriginVector();
		// double[] celloff = tgatom.getCellOffset();
		boolean comChanged = false;
		for (int i = 0; i < 3; i++) {
			// jusin_buff[i] = -(jusin_buff[i] + celloff[i] * lenmax);
			if (jusin_buff[i] != this.jusin[i])
				comChanged = true;
		}

		if (!check) {
			this.jusin = jusin_buff;
			return;
		}

		logger.debug("jusin : " + jusin[0] + ", " + jusin[1] + ", " + jusin[2]);
		logger.debug("jusin_buff : " + jusin_buff[0] + ", " + jusin_buff[1] + ", " + jusin_buff[2]);

		// 重心が変更されていた場合, GuidePlaneの原点も相応にずらす
		if (comChanged) {
			double[] diff = new double[3];
			for (int i = 0; i < 3; i++) {
				diff[i] = this.jusin[i] - jusin_buff[i];
				this.jusin[i] = jusin_buff[i];
			}
			if (guidePlane != null) {
				Point3f orig = guidePlane.getOrigin();
				orig.x += (float) (diff[0] / lenmax);
				orig.y += (float) (diff[1] / lenmax);
				orig.z += (float) (diff[2] / lenmax);
				// guidePlane.setOrigin(orig);
			}
		}
		logger.debug("lenmax: " + lenmax);
		logger.debug("jusin: " + jusin[0] + " " + jusin[1] + " " + jusin[2]);
	}

	private Point3f getPos() {
		Point3f center = new Point3f(0.0f, 0.0f, 0.0f);
		// Point3f center_tmp = new Point3f();
		int[] mill = getMillerIndex();
		if (mill == null)
			return null;
		Point3f norm = AtomicConfiguration.getNormalVector(getCurrAtomicConfiguration(), mill);
		float dx = vslider.getValue();

		// center_tmp.x = (dx * norm.x) / (bounds[0][0] + bounds[1][0] + bounds[2][0]);
		// center_tmp.y = (dx * norm.y) / (bounds[0][1] + bounds[1][1] + bounds[2][1]);
		// center_tmp.z = (dx * norm.z) / (bounds[0][2] + bounds[1][2] + bounds[2][2]);
		//
		// center.x = (center_tmp.x * bounds[0][0] + center_tmp.y * bounds[1][0] +
		// center_tmp.z * bounds[2][0]
		// - (float) (jusin[0])) / (float) lenmax;// + (bounds[0][0] + bounds[0][1] +
		// bounds[0][2]) * 0.5f;
		// center.y = (center_tmp.x * bounds[0][1] + center_tmp.y * bounds[1][1] +
		// center_tmp.z * bounds[2][1]
		// - (float) (jusin[1])) / (float) lenmax;// + (bounds[1][0] + bounds[1][1] +
		// bounds[1][2]) * 0.5f;
		// center.z = (center_tmp.x * bounds[0][2] + center_tmp.y * bounds[1][2] +
		// center_tmp.z * bounds[2][2]
		// - (float) (jusin[2])) / (float) lenmax;// + (bounds[2][0] + bounds[2][1] +
		// bounds[2][2]) * 0.5f;
		logger.debug("jusin " + jusin[0] + " " + jusin[1] + " " + jusin[2]);
		// double[] ajusin = getJusin();
		double[] ajusin = getCurrAtomicConfiguration().getCartJusin(mill);
		center.x = (dx * norm.x - (float) jusin[0] + (float) ajusin[0]) / (float) lenmax;
		center.y = (dx * norm.y - (float) jusin[1] + (float) ajusin[1]) / (float) lenmax;
		center.z = (dx * norm.z - (float) jusin[2] + (float) ajusin[2]) / (float) lenmax;
		logger.debug("center : " + String.valueOf(center));
		return center;
	}

}

class AtomicConfiguration implements Cloneable {
	private static Logger logger = Logger.getLogger(AtomicConfiguration.class.getName());

	enum SurfaceType {
		ONE_ZERO_ZERO, ONE_ONE_ZERO, ONE_ONE_ONE, INVALID
	}

	protected String name = "AtomicConfiguration";
	protected int nat;
	protected String[] elemname;
	protected double[][] cartpos;
	protected double[][] fractpos;
	protected double[][] latvec;
	protected double[] latconst;

	void setName(String name) {
		this.name = name;
	}

	void initFromElement(Element element) {
		String nam = element.getAttributeValue("name");
		if (nam != null && nam.length() > 0)
			setName(nam);
		try {
			int na = element.getAttribute("nat").getIntValue();
			setNumAt(na);
		} catch (DataConversionException dce) {
			dce.printStackTrace();
			logger.error("failed conversion");
			return;
		}
		Element lat = element.getChild("lattice");
		Element avec = lat.getChild("avec");
		Element bvec = lat.getChild("bvec");
		Element cvec = lat.getChild("cvec");
		Element lconst = lat.getChild("latconst");
		this.latvec = new double[3][3];
		this.latconst = new double[6];
		if (avec != null) {
			try {
				latvec[0][0] = avec.getAttribute("x").getDoubleValue();
				latvec[0][1] = avec.getAttribute("y").getDoubleValue();
				latvec[0][2] = avec.getAttribute("z").getDoubleValue();
			} catch (DataConversionException dce) {
				logger.error("failed conversion");
			}
		}
		if (bvec != null) {
			try {
				latvec[1][0] = bvec.getAttribute("x").getDoubleValue();
				latvec[1][1] = bvec.getAttribute("y").getDoubleValue();
				latvec[1][2] = bvec.getAttribute("z").getDoubleValue();
			} catch (DataConversionException dce) {
				logger.error("failed conversion");
			}
		}
		if (cvec != null) {
			try {
				latvec[2][0] = cvec.getAttribute("x").getDoubleValue();
				latvec[2][1] = cvec.getAttribute("y").getDoubleValue();
				latvec[2][2] = cvec.getAttribute("z").getDoubleValue();
			} catch (DataConversionException dce) {
				logger.error("failed conversion");
			}
		}
		if (lconst != null) {
			try {
				latconst[0] = lconst.getAttribute("a").getDoubleValue();
				latconst[1] = lconst.getAttribute("b").getDoubleValue();
				latconst[2] = lconst.getAttribute("c").getDoubleValue();
				latconst[3] = lconst.getAttribute("alpha").getDoubleValue();
				latconst[4] = lconst.getAttribute("beta").getDoubleValue();
				latconst[5] = lconst.getAttribute("gamma").getDoubleValue();
			} catch (DataConversionException dce) {
				logger.error("failed conversion");
			}
		}
		List<Element> atms = element.getChildren("atom");
		int iat = 0;
		for (Element atm : atms) {
			elemname[iat] = atm.getAttributeValue("elemname");
			Element cpos = atm.getChild("cartpos");
			try {
				cartpos[iat][0] = cpos.getAttribute("x").getDoubleValue();
				cartpos[iat][1] = cpos.getAttribute("y").getDoubleValue();
				cartpos[iat][2] = cpos.getAttribute("z").getDoubleValue();
			} catch (DataConversionException e) {
				logger.error("failed conversion");
			}
			Element fpos = atm.getChild("fractpos");
			try {
				fractpos[iat][0] = fpos.getAttribute("x").getDoubleValue();
				fractpos[iat][1] = fpos.getAttribute("y").getDoubleValue();
				fractpos[iat][2] = fpos.getAttribute("z").getDoubleValue();
			} catch (DataConversionException e) {
				logger.error("failed conversion");
			}
			iat++;
		}
	}

	Element getElement() {
		Element elem = new Element("AtomicConfiguration");
		elem.setAttribute("name", name);
		elem.setAttribute("nat", String.valueOf(nat));
		Element lattice = new Element("lattice");
		elem.addContent(lattice);
		Element eavec = new Element("avec");
		eavec.setAttribute("x", String.valueOf(latvec[0][0]));
		eavec.setAttribute("y", String.valueOf(latvec[0][1]));
		eavec.setAttribute("z", String.valueOf(latvec[0][2]));
		Element ebvec = new Element("bvec");
		ebvec.setAttribute("x", String.valueOf(latvec[1][0]));
		ebvec.setAttribute("y", String.valueOf(latvec[1][1]));
		ebvec.setAttribute("z", String.valueOf(latvec[1][2]));
		Element ecvec = new Element("cvec");
		ecvec.setAttribute("x", String.valueOf(latvec[2][0]));
		ecvec.setAttribute("y", String.valueOf(latvec[2][1]));
		ecvec.setAttribute("z", String.valueOf(latvec[2][2]));
		Element lconst = new Element("latconst");
		lconst.setAttribute("a", String.valueOf(latconst[0]));
		lconst.setAttribute("b", String.valueOf(latconst[1]));
		lconst.setAttribute("c", String.valueOf(latconst[2]));
		lconst.setAttribute("alpha", String.valueOf(latconst[3]));
		lconst.setAttribute("beta", String.valueOf(latconst[4]));
		lconst.setAttribute("gamma", String.valueOf(latconst[5]));
		lattice.addContent(eavec);
		lattice.addContent(ebvec);
		lattice.addContent(ecvec);
		lattice.addContent(lconst);
		for (int i = 0; i < nat; i++) {
			Element atm = new Element("atom");
			elem.addContent(atm);
			atm.setAttribute("no", String.valueOf(i));
			atm.setAttribute("elemname", elemname[i]);
			Element cart = new Element("cartpos");
			cart.setAttribute("x", String.valueOf(cartpos[i][0]));
			cart.setAttribute("y", String.valueOf(cartpos[i][1]));
			cart.setAttribute("z", String.valueOf(cartpos[i][2]));
			atm.addContent(cart);
			Element fract = new Element("fractpos");
			fract.setAttribute("x", String.valueOf(fractpos[i][0]));
			fract.setAttribute("y", String.valueOf(fractpos[i][1]));
			fract.setAttribute("z", String.valueOf(fractpos[i][2]));
			atm.addContent(fract);
		}
		return elem;
	}

	double[][] getCartPos() {
		return cartpos;
	}

	double[][] getFractPos() {
		return fractpos;
	}

	double[][] getLatVec() {
		return latvec;
	}

	double[] getLatConst() {
		return latconst;
	}

	int getNumAt() {
		return nat;
	}

	protected void setNumAt(int nat) {
		this.nat = nat;
		elemname = new String[nat];
		cartpos = new double[nat][3];
		fractpos = new double[nat][3];
	}

	AtomicConfiguration() {
	}

	AtomicConfiguration(int nat, String[] elemname, double[][] cartpos, double[][] latvec) {
		this.nat = nat;
		this.elemname = elemname;
		this.cartpos = cartpos;
		this.latvec = latvec;
		this.updateFract();

		this.updateLatticeConstant();
	}

	protected AtomCoords coords;

	AtomCoords getAtomCoordsObj() {
		return getAtomCoordsObj(true);
	}

	AtomCoords getAtomCoordsObj(boolean newInstance) {
		if (newInstance || coords == null)
			coords = new AtomCoords();
		coords.isCart(true);
		coords.setUnit(AtomCoords.ANGSTROM);

		AtomList list = coords.getAtomList();
		list.clear();

		// get cellinfo
		double[][] cellVec = latvec;

		double[] av = new double[3];
		double[] bv = new double[3];
		double[] cv = new double[3];
		for (int i = 0; i < 3; i++) {
			av[i] = cellVec[0][i];
			bv[i] = cellVec[1][i];
			cv[i] = cellVec[2][i];
		}
		Cell cellv = new Cell(av, bv, cv);
		coords.setCell(cellv);

		int NumAt = nat;
		double[][] fposs = fractpos;
		for (int i = 0; i < fposs.length; i++) {
			logger.debug("fractional pos " + i + " " + fposs[i][0] + ", " + fposs[i][1] + ", " + fposs[i][2]);
		}
		for (int i = 0; i < NumAt; i++) {
			String element = elemname[i];
			element = element.split("\\d+")[0].split("\\(")[0];
			logger.debug("atom :" + i + " name: " + element);
			double[] cpos = new double[3];
			for (int j = 0; j < 3; j++) {
				cpos[j] = 0d;
				for (int k = 0; k < 3; k++)
					cpos[j] = cpos[j] + cellVec[k][j] * fposs[i][k];
			}
			Atom at = new Atom(element, cpos);
			list.addAtom(at);
		}
		return coords;
	}

	String[] getElementName() {
		return this.elemname;
	}

	private double[] com;

	void setCOM(double[] com) {
		this.com = com;
	}

	static AtomicConfiguration getAtomicConfiguration(AtomCoords coor) {
		AtomicConfiguration conf = new AtomicConfiguration();
		conf.mergeAtomCoords(coor);
		return conf;
	}

	public Object clone() {
		AtomicConfiguration ret = new AtomicConfiguration();

		ret.setNumAt(nat);
		ret.latvec = new double[3][3];
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				ret.latvec[i][j] = latvec[i][j];
		ret.updateLatticeConstant();
		for (int i = 0; i < nat; i++) {
			ret.elemname[i] = new String(elemname[i]);
			for (int j = 0; j < 3; j++)
				ret.cartpos[i][j] = cartpos[i][j];
		}
		ret.updateFract();
		return ret;
	}

	double[] getCartJusin(int[] hkl) {
		AtomicConfiguration ac = null;
		ac = (AtomicConfiguration) clone();
		ac.swapABC(hkl[0], hkl[1], hkl[2], true);
		return ac.getCartJusin();
	}

	double[] getCartJusin() {
		double[] ret = { 0d, 0d, 0d };

		for (int i = 0; i < nat; i++)
			for (int j = 0; j < 3; j++)
				ret[j] += cartpos[i][j];
		for (int i = 0; i < 3; i++)
			ret[i] /= (double) nat;
		return ret;
	}

	void mergeAtomCoords(AtomCoords coor) {
		int uni = coor.getUnit();
		boolean wasCart = coor.isCart();
		coor.convert(AtomCoords.TO_CART, AtomCoords.TO_ANG);
		double[][] cell = coor.getCellDouble(0);
		AtomList alist = coor.getAtomList();
		int nat = alist.getNumAt();
		double[][] cartpos = new double[nat][3];
		for (int i = 0; i < nat; i++)
			cartpos[i] = alist.getAtomAt(i).getDouble();
		String[] elemname = new String[nat];
		for (int i = 0; i < nat; i++)
			elemname[i] = alist.getAtomAt(i).getElementName();
		setNumAt(nat);
		for (int i = 0; i < nat; i++) {
			this.elemname[i] = new String(elemname[i]);
			for (int j = 0; j < 3; j++)
				this.cartpos[i][j] = cartpos[i][j];
		}
		this.latvec = new double[3][3];
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				this.latvec[i][j] = cell[i][j];
		this.updateFract();
		this.updateLatticeConstant();
		if (!wasCart)
			coor.convert(AtomCoords.TO_INTERNAL, uni);
		else
			coor.convert(AtomCoords.TO_CART, uni);
	}

	void generateSurface(int h, int k, int l, int sx, int sy, int sz, double vac, double shift, double zoff,
			boolean perp) {
		int hh = h;
		int kk = k;
		int ll = l;
		if (hh == 0)
			hh = 1;
		if (kk == 0)
			kk = 1;
		if (ll == 0)
			ll = 1;
		// for (int i = 0; i < nat; i++)
		// cartpos[i][2] -= (latvec[0][1] + latvec[0][2] + latvec[2][2]) * 0.5d;
		this.updateFract();
		this.pack();
		this.toSuper(hh, kk, ll);
		// offset -= com[0] * nrm.x + com[1] * nrm.y + com[2] * nrm.z;
		// offset *= 0.5d;
		// for (int i = 0; i < nat; i++) {
		// for (int j = 0; j < 3; j++)
		// for (int kkk = 0; kkk < 3; kkk++)
		// cartpos[i][j] -= 0.5f * latvec[j][kkk] * dnrm[kkk];
		// }
		// this.updateFract();
		// this.pack();
		// zoff = latvec[2][2] * 0.5;
		zoff = 0.0;
		double[] jusin = getCartJusin(new int[] { h, k, l });
		for (int i = 0; i < nat; i++)
			for (int j = 0; j < 3; j++)
				cartpos[i][j] -= jusin[j];
		updateFract();
		pack();
		this.swapABC(h, k, l);
		logger.debug("fract coords");
		for (int i = 0; i < nat; i++)
			logger.debug(fractpos[i][0] + " " + fractpos[i][1] + " " + fractpos[i][2]);
		logger.debug("latvec before rotation");
		for (int i = 0; i < 3; i++)
			logger.debug(latvec[i][0] + " " + latvec[i][1] + " " + latvec[i][2]);
		this.rotate(h, k, l);
		logger.debug("latvec after rotation");
		for (int i = 0; i < 3; i++)
			logger.debug(latvec[i][0] + " " + latvec[i][1] + " " + latvec[i][2]);
		this.pack();
		this.toSuper(sx, sy, sz);
		this.updateCart();
		this.updateFract();
		this.updateLatticeConstant();
		this.updateLatticeVector();
		this.updateCart();
		if (perp) {
			this.setLatticeConstant(new double[] { latconst[0], latconst[1], latvec[2][2], 90, 90, latconst[5] });
			this.updateLatticeVector();
			this.updateFract();
			// this.pack();
		}
		Point3f nrm = getNormalVector(this, new int[] { h, k, l });
		this.doShift(shift, zoff, nrm);
		this.setVacuumLayer(vac);
		logger.debug(this);
	}

	private void setVacuumLayer(double vac) {
		this.latconst[2] += vac;
		this.updateLatticeVector();
		this.updateFract();
		// this.pack();
	}

	private void doShift(double shift, double offset, Point3f nrm) {
		double[] norm = getNorm(2);
		logger.debug("shift amount : " + shift + ", offset : " + offset);
		logger.debug("direction of shift: " + norm[0] + ", " + norm[1] + ", " + norm[2]);
		if (!reverseShift)
			for (int i = 0; i < nat; i++)
				cartpos[i][2] += shift + offset * nrm.z;
		else
			for (int i = 0; i < nat; i++) {
				cartpos[i][2] -= shift + offset * nrm.z;
				cartpos[i][2] *= -1d;
			}
		updateFract();
		pack();
	}

	private double[] getNorm(int i) {
		double[] ret = new double[3];
		for (int j = 0; j < 3; j++)
			ret[j] = latvec[i][j];
		double n = 0d;
		for (int j = 0; j < 3; j++)
			n += ret[j] * ret[j];
		n = Math.sqrt(n);
		for (int j = 0; j < 3; j++)
			ret[j] /= n;
		return ret;
	}

	private static ArrayList<Integer> nonZeroIndices(int[] mill) {
		ArrayList<Integer> ret = new ArrayList<>();
		for (int i = 0; i < 3; i++)
			if (mill[i] != 0)
				ret.add(i);
		return ret;
	}

	private static ArrayList<Integer> zeroIndices(int[] mill) {
		ArrayList<Integer> ret = new ArrayList<>();
		for (int i = 0; i < 3; i++)
			if (mill[i] == 0)
				ret.add(i);
		return ret;
	}

	static Point3f getNormalVector(AtomicConfiguration conf, int[] mill) {
		double[][] latvec = conf.getLatVec();
		double[][] latvecs = new double[3][3];
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				latvecs[i][j] = latvec[i][j] * mill[i];
		double[] vec1 = new double[3];
		double[] vec2 = new double[3];
		SurfaceType styp = AtomicConfiguration.getSurfaceType(mill[0], mill[1], mill[2]);
		ArrayList<Integer> nonzero = nonZeroIndices(mill);
		ArrayList<Integer> zero = zeroIndices(mill);
		Vector3d vec = new Vector3d();
		if (styp == SurfaceType.ONE_ONE_ONE) {
			for (int i = 0; i < 3; i++)
				vec1[i] = latvecs[0][i] - latvecs[2][i];
			for (int i = 0; i < 3; i++)
				vec2[i] = latvecs[1][i] - latvecs[2][i];
			vec.cross(new Vector3d(vec1), new Vector3d(vec2));
		} else if (styp == SurfaceType.ONE_ONE_ZERO) {
			for (int i = 0; i < 3; i++)
				vec1[i] = latvecs[nonzero.get(0)][i] - latvecs[nonzero.get(1)][i];
			Vector3d tvec = new Vector3d();
			tvec.cross(new Vector3d(latvecs[nonzero.get(1)]), new Vector3d(latvecs[nonzero.get(0)]));
			vec2[0] = tvec.x;
			vec2[1] = tvec.y;
			vec2[2] = tvec.z;
			vec.cross(new Vector3d(vec1), new Vector3d(vec2));
		} else if (styp == SurfaceType.ONE_ZERO_ZERO) {
			logger.debug("zero " + zero.get(0) + ", " + zero.get(1));
			int i0 = zero.get(0);
			int i1 = zero.get(1);
			logger.debug("latvecs0 " + latvecs[i0][0] + " " + latvecs[i0][1] + " " + latvecs[i0][2]);
			logger.debug("latvecs1 " + latvecs[i1][0] + " " + latvecs[i1][1] + " " + latvecs[i1][2]);
			vec1 = conf.getLatVec()[zero.get(0)];
			vec2 = conf.getLatVec()[zero.get(1)];
			logger.debug("vec1, vec2 : " + vec1[0] + ", " + vec1[1] + ", " + vec1[2] + " " + vec2[0] + ", " + vec2[1]
					+ ", " + vec2[2]);
			vec.cross(new Vector3d(vec1), new Vector3d(vec2));
			// vec.x = latvecs[nonzero.get(0)][0];
			// vec.y = latvecs[nonzero.get(0)][1];
			// vec.z = latvecs[nonzero.get(0)][2];
		} else
			return null;

		vec.normalize();
		return new Point3f(new float[] { (float) vec.x, (float) vec.y, (float) vec.z });
	}

	void pack(int[][] map) {
		for (int i = 0; i < nat; i++)
			for (int j = 0; j < 3; j++) {
				if (this.fractpos[i][j] > 1) {
					this.fractpos[i][j] = this.fractpos[i][j] - Math.floor(this.fractpos[i][j]);
					if (map != null)
						map[i][j] += 1;
				} else if (this.fractpos[i][j] < 0) {
					this.fractpos[i][j] = this.fractpos[i][j] + Math.ceil(-this.fractpos[i][j]);
					if (map != null)
						map[i][j] -= 1;
				}
			}
		updateCart();
	}

	void pack() {
		int[][] map = null;
		pack(map);
	}

	void pack(int j, int[][] map) {
		for (int i = 0; i < nat; i++)
			if (this.fractpos[i][j] > 1) {
				this.fractpos[i][j] = this.fractpos[i][j] - Math.floor(this.fractpos[i][j]);
				if (map != null)
					map[i][j] += 1;
			} else if (this.fractpos[i][j] < 0) {
				this.fractpos[i][j] = this.fractpos[i][j] + Math.ceil(-this.fractpos[i][j]);
				if (map != null)
					map[i][j] -= 1;
			}
		updateCart(j);
	}

	void pack(int j) {
		pack(j, null);
	}

	void swapABC(int h, int k, int l) {
		swapABC(h, k, l, false);
	}

	private boolean reverseShift;

	void swapABC(int h, int k, int l, boolean invertOnly) {
		reverseShift = false;
		boolean[] invert = { false, false, false };
		int inda = 0;
		int indb = 1;
		int indc = 2;
		if (h < 0)
			invert[0] = true;
		if (k < 0)
			invert[1] = true;
		if (l < 0)
			invert[2] = true;
		int ii = 0;
		for (int i = 0; i < 3; i++)
			if (invert[i])
				ii++;
		if (ii == 1 || ii == 3)
			reverseShift = true;
		if (getSurfaceType(h, k, l) == SurfaceType.ONE_ONE_ZERO) {
			for (int i = 0; i < 3; i++)
				invert[i] = false;
			if (h == 0) {
				if (k < 0)
					invert[1] = true;
				if (l < 0)
					invert[2] = true;
			}
			if (k == 0) {
				inda = 1;
				indb = 0;
				indc = 2;
				if (h < 0)
					invert[1] = true;
				if (l < 0)
					invert[2] = true;
			}
			if (l == 0) {
				inda = 2;
				indb = 1;
				indc = 0;
				if (h < 0)
					invert[2] = true;
				if (k < 0)
					invert[1] = true;
			}
		}
		if (getSurfaceType(h, k, l) == SurfaceType.ONE_ZERO_ZERO) {
			for (int i = 0; i < 3; i++)
				invert[i] = false;
			if (h != 0) {
				inda = 1;
				indb = 2;
				indc = 0;
				if (h < 0)
					invert[2] = true;
			}
			if (k != 0) {
				inda = 0;
				indb = 2;
				indc = 1;
				if (k < 0)
					invert[2] = true;
			}
			if (l != 0) {
				inda = 0;
				indb = 1;
				indc = 2;
				if (l < 0)
					invert[2] = true;
			}
			if (invert[2])
				reverseShift = true;
		}
		double[][] pos = new double[nat][3];
		double[][] cell = new double[3][3];
		for (int i = 0; i < nat; i++) {
			pos[i][0] = this.fractpos[i][inda];
			pos[i][1] = this.fractpos[i][indb];
			pos[i][2] = this.fractpos[i][indc];
		}

		for (int i = 0; i < 3; i++)
			if (invert[i])
				for (int j = 0; j < nat; j++)
					pos[j][i] = -pos[j][i];

		for (int i = 0; i < 3; i++) {
			cell[0][i] = latvec[inda][i];
			cell[1][i] = latvec[indb][i];
			cell[2][i] = latvec[indc][i];
		}
		for (int i = 0; i < 3; i++)
			if (invert[i])
				for (int j = 0; j < 3; j++)
					cell[i][j] = -cell[i][j];
		if (invertOnly) {
			double[][] tcell = new double[3][3];
			for (int i = 0; i < 3; i++) {
				tcell[inda][i] = cell[0][i];
				tcell[indb][i] = cell[1][i];
				tcell[indc][i] = cell[2][i];
			}
			cell = tcell;
			double[][] tpos = new double[nat][3];
			for (int i = 0; i < nat; i++) {
				tpos[i][inda] = pos[i][0];
				tpos[i][indb] = pos[i][1];
				tpos[i][indc] = pos[i][2];
			}
			pos = tpos;
		}
		this.fractpos = pos;
		this.latvec = cell;
		this.pack();
		this.updateCart();
	}

	private int[][] rotmat1 = new int[][] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 1, 1 } };
	private int[][] rotmat2 = new int[][] { { 1, 0, 0 }, { 0, 1, 0 }, { 1, 0, 1 } };

	private int[][] rotmat3 = new int[][] { { 1, 0, -1 }, { 0, 1, 0 }, { 0, 0, 1 } };
	private int[][] rotmat4 = new int[][] { { 1, 0, 0 }, { 0, 1, -1 }, { 0, 0, 1 } };

	static SurfaceType getSurfaceType(int h, int k, int l) {
		int zcount = 0;
		if (h == 0)
			zcount++;
		if (k == 0)
			zcount++;
		if (l == 0)
			zcount++;

		if (zcount == 0)
			return SurfaceType.ONE_ONE_ONE;
		if (zcount == 1)
			return SurfaceType.ONE_ONE_ZERO;
		if (zcount == 2)
			return SurfaceType.ONE_ZERO_ZERO;
		return SurfaceType.INVALID;
	}

	void rotate(int h, int k, int l) {
		SurfaceType styp = getSurfaceType(h, k, l);
		logger.debug("surface type " + styp);
		int[] hkl = new int[] { h, k, l };
		if (styp == SurfaceType.ONE_ONE_ONE) {
			rotateSub(rotmat2, rotmat3, hkl);
			rotateSub(rotmat1, rotmat4, hkl);
		} else if (styp == SurfaceType.ONE_ONE_ZERO)
			rotateSub(rotmat1, rotmat4, hkl);
	}

	void rotateSub(int[][] rotmat, int[][] cellrotmat, int[] hkl) {
		int[] pm = new int[] { 1, 1, 1 };
		for (int j = 0; j < 3; j++)
			if (hkl[j] < 0)
				pm[j] = -1;
		for (int iat = 0; iat < nat; iat++) {
			double[] opos = fractpos[iat];
			double[] pos = new double[3];
			for (int i = 0; i < 3; i++) {
				pos[i] = 0d;
				for (int j = 0; j < 3; j++)
					pos[i] += rotmat[i][j] * opos[j];
				pack(pos);
			}
			fractpos[iat] = pos;
		}
		double[][] cellvec = latvec;
		double[][] newcellvec = new double[3][3];
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				newcellvec[i][j] = 0.0;
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				for (int k = 0; k < 3; k++)
					newcellvec[i][j] += cellrotmat[i][k] * cellvec[k][j] * pm[j];
		latvec = newcellvec;
		logger.debug(this);
	}

	void rotateSub(int[][] rotmat, int[][] cellrotmat) {
		for (int iat = 0; iat < nat; iat++) {
			double[] opos = fractpos[iat];
			double[] pos = new double[3];
			for (int i = 0; i < 3; i++) {
				pos[i] = 0d;
				for (int j = 0; j < 3; j++)
					pos[i] += rotmat[i][j] * opos[j];
				pack(pos);
			}
			fractpos[iat] = pos;
		}
		double[][] cellvec = latvec;
		double[][] newcellvec = new double[3][3];
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				newcellvec[i][j] = 0.0;
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				for (int k = 0; k < 3; k++)
					newcellvec[i][j] += cellrotmat[i][k] * cellvec[k][j];
		latvec = newcellvec;
		logger.debug(this);
	}

	void toSuper(int sx, int sy, int sz) {
		if (Math.abs(sx) <= 1 && Math.abs(sy) <= 1 && Math.abs(sz) <= 1)
			return;
		int asx = Math.abs(sx);
		int asy = Math.abs(sy);
		int asz = Math.abs(sz);
		double[][] tmplatvec = new double[3][3];
		for (int i = 0; i < 3; i++) {
			tmplatvec[0][i] = asx * latvec[0][i];
			tmplatvec[1][i] = asy * latvec[1][i];
			tmplatvec[2][i] = asz * latvec[2][i];
		}
		int natold = nat;
		logger.debug("sx, sy, sz " + asx + ", " + asy + ", " + asz);
		for (int i = 0; i < nat; i++)
			logger.debug("curr pos " + cartpos[i][0] + " " + cartpos[i][1] + " " + cartpos[i][2]);
		double[][] tmpc = cartpos;
		String[] tmpe = elemname;
		setNumAt(nat * asx * asy * asz);
		for (int i = 0; i < natold; i++) {
			elemname[i] = tmpe[i];
			for (int j = 0; j < 3; j++)
				cartpos[i][j] = tmpc[i][j];
		}

		int atmcount = natold;
		for (int iat = 0; iat < natold; iat++)
			for (int i = 0; i < asx; i++)
				for (int j = 0; j < asy; j++)
					for (int k = 0; k < asz; k++) {
						if (i == 0 && j == 0 && k == 0)
							continue;
						elemname[atmcount] = elemname[iat];
						for (int l = 0; l < 3; l++)
							cartpos[atmcount][l] = cartpos[iat][l] + i * latvec[0][l] + j * latvec[1][l]
									+ k * latvec[2][l];
						atmcount++;
					}
		this.latvec = tmplatvec;
		this.updateFract();
		this.updateLatticeConstant();
	}

	public String toString() {
		String ret = "latvec" + System.getProperty("line.separator");
		for (int i = 0; i < 3; i++)
			ret += latvec[i][0] + " " + latvec[i][1] + " " + latvec[i][2] + System.getProperty("line.separator");
		ret += "latconst " + System.getProperty("line.separator");
		ret += latconst[0] + " " + latconst[1] + " " + latconst[2] + " " + latconst[3] + " " + latconst[4] + " "
				+ latconst[5] + System.getProperty("line.separator");
		ret += "atomic coordinates " + System.getProperty("line.separator");
		for (int i = 0; i < nat; i++)
			ret += elemname[i] + " " + String.valueOf(cartpos[i][0]) + " " + String.valueOf(cartpos[i][1]) + " "
					+ String.valueOf(cartpos[i][2]) + " " + String.valueOf(fractpos[i][0]) + " "
					+ String.valueOf(fractpos[i][1]) + " " + String.valueOf(fractpos[i][2])
					+ System.getProperty("line.separator");
		ret += String.valueOf(nat) + System.getProperty("line.separator");
		ret += "debug output" + System.getProperty("line.separator");
		for (int i = 0; i < nat; i++)
			ret += elemname[i] + " " + String.valueOf(cartpos[i][0]) + " " + String.valueOf(cartpos[i][1]) + " "
					+ String.valueOf(cartpos[i][2]) + System.getProperty("line.separator");
		return ret;
	}

	void setLatticeVector(double[][] latvec) {
		this.latvec = latvec;
	}

	void setLatticeConstant(double[] latconst) {
		this.latconst = latconst;
	}

	double[][] getNormalizedLatticeVector() {
		double[][] ret = new double[3][3];
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				ret[i][j] = latvec[i][j] / latconst[i];
		return ret;
	}

	void updateFract(int ii) {
		double[][] latvect = new double[3][3];
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				latvect[i][j] = latvec[j][i];

		for (int i = 0; i < nat; i++) {
			double[] Dpos = cartpos[i];
			double[][] bvec = new double[3][1];
			for (int i1 = 0; i1 < 3; i1++)
				bvec[i1][0] = Dpos[i1];

			// --- Jama ---
			Matrix A = new Matrix(latvect);
			Matrix b = new Matrix(bvec);
			Matrix x = A.solve(b);
			// --- Jama ---

			this.fractpos[i][ii] = x.get(ii, 0);
		}
	}

	static double[][] getCart(int nat, double[][] fract, double[][] latvec) {
		double[][] cart = new double[nat][3];
		double[] cp = new double[3];
		for (int i = 0; i < nat; i++) {
			for (int ii = 0; ii < 3; ii++) {
				cp[ii] = 0.0;
				for (int k = 0; k < 3; k++)
					cp[ii] += fract[i][k] * latvec[k][ii];
				cart[i][ii] = cp[ii];
			}
		}
		return cart;
	}

	static double[][] getFract(int nat, double[][] cart, double[][] latvec) {
		double[][] latvect = new double[3][3];
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				latvect[i][j] = latvec[j][i];
		double[][] fractpos = new double[nat][3];

		for (int i = 0; i < nat; i++) {
			double[] Dpos = cart[i];
			double[][] bvec = new double[3][1];
			for (int i1 = 0; i1 < 3; i1++)
				bvec[i1][0] = Dpos[i1];

			// --- Jama ---
			Matrix A = new Matrix(latvect);
			Matrix b = new Matrix(bvec);
			Matrix x = A.solve(b);
			// --- Jama ---

			for (int j = 0; j < 3; j++)
				fractpos[i][j] = x.get(j, 0);
		}
		return fractpos;
	}

	static void pack(double[] fr) {
		if (fr[2] > 1)
			fr[2] = fr[2] - Math.floor(fr[2]);
		else if (fr[2] < 0)
			fr[2] = fr[2] + Math.ceil(-fr[2]);
	}

	void updateFract() {
		double[][] latvect = new double[3][3];
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				latvect[i][j] = latvec[j][i];
		this.fractpos = new double[nat][3];

		for (int i = 0; i < nat; i++) {
			double[] Dpos = cartpos[i];
			double[][] bvec = new double[3][1];
			for (int i1 = 0; i1 < 3; i1++)
				bvec[i1][0] = Dpos[i1];

			// --- Jama ---
			Matrix A = new Matrix(latvect);
			Matrix b = new Matrix(bvec);
			Matrix x = A.solve(b);
			// --- Jama ---

			for (int i1 = 0; i1 < 3; i1++)
				this.fractpos[i][i1] = x.get(i1, 0);
		}

	}

	void updateCart(int ii) {
		for (int i = 0; i < nat; i++) {
			double[] cp = new double[3];
			cp[ii] = 0.0;
			for (int k = 0; k < 3; k++)
				cp[ii] += this.fractpos[i][k] * this.latvec[k][ii];
			this.cartpos[i][ii] = cp[ii];
		}
	}

	void updateCart() {
		this.cartpos = new double[nat][3];
		for (int i = 0; i < nat; i++) {
			double[] cp = new double[3];
			for (int j = 0; j < 3; j++) {
				cp[j] = 0.0;
				for (int k = 0; k < 3; k++)
					cp[j] += this.fractpos[i][k] * this.latvec[k][j];
			}
			this.cartpos[i] = cp;
		}
	}

	void updateLatticeConstant() {
		double[] avec = latvec[0];
		double[] bvec = latvec[1];
		double[] cvec = latvec[2];

		double ar = 0.;
		double br = 0.;
		double cr = 0.;
		for (int i = 0; i < 3; i++) {
			ar += Math.pow(avec[i], 2);
			br += Math.pow(bvec[i], 2);
			cr += Math.pow(cvec[i], 2);
		}
		ar = Math.sqrt(ar);
		br = Math.sqrt(br);
		cr = Math.sqrt(cr);

		double cda = 0.;
		double cdb = 0.;
		double adb = 0.;

		for (int i = 0; i < 3; i++) {
			cda += cvec[i] * avec[i];
			cdb += cvec[i] * bvec[i];
			adb += avec[i] * bvec[i];
		}

		double cosbeta = 0.;
		double cosalph = 0.;
		double cosgamm = 0.;

		double beta = 0.;
		double alph = 0.;
		double gamm = 0.;

		try {
			cosbeta = cda / (cr * ar);
			cosalph = cdb / (cr * br);
			cosgamm = adb / (ar * br);

			beta = Math.acos(cosbeta) * ConstParameters.RAD_TO_ANG;
			alph = Math.acos(cosalph) * ConstParameters.RAD_TO_ANG;
			gamm = Math.acos(cosgamm) * ConstParameters.RAD_TO_ANG;
		} catch (Exception e) {
			logger.error("failed to generate lattice constants");
			return;
		}

		this.latconst = new double[] { ar, br, cr, alph, beta, gamm };
	}

	void updateLatticeVector() {
		double[] cellLength = new double[] { latconst[0], latconst[1], latconst[2] };
		double alpha = latconst[3] * ConstParameters.ANG_TO_RAD;
		double beta = latconst[4] * ConstParameters.ANG_TO_RAD;
		double gamma = latconst[5] * ConstParameters.ANG_TO_RAD;

		double temp = 0.;

		try {
			for (int i = 0; i < 3; ++i)
				for (int j = 0; j < 3; ++j)
					latvec[i][j] = 0.;
			latvec[0][0] = 1.;
			latvec[1][0] = Math.cos(gamma);
			latvec[2][0] = Math.cos(beta);
			latvec[1][1] = Math.sin(gamma);
			temp = (Math.cos(alpha) - Math.cos(beta) * Math.cos(gamma)) / Math.sin(gamma);
			latvec[2][1] = temp;
			latvec[2][2] = Math.sqrt(Math.sin(beta) * Math.sin(beta) - temp * temp);
			for (int i = 0; i < 3; ++i)
				for (int j = 0; j < 3; ++j)
					latvec[j][i] *= cellLength[j];
		} catch (Exception e) {
			logger.error("failed to generate lattice vectors");
			return;
		}
		logger.debug(latvec[0][0] + ", " + latvec[0][1] + ", " + latvec[0][2]);
		logger.debug(latvec[1][0] + ", " + latvec[1][1] + ", " + latvec[1][2]);
		logger.debug(latvec[2][0] + ", " + latvec[2][1] + ", " + latvec[2][2]);
	}

	void setCartPos(double[][] cartpos) {
		setCartPos(cartpos, true, true);
	}

	void setCartPos(double[][] cartpos, boolean updateFract, boolean pack) {
		if (cartpos.length != nat) {
			logger.error("inconsistent number of atoms : " + nat + " " + cartpos.length);
			return;
		}
		this.cartpos = cartpos;
		if (updateFract)
			updateFract();
		if (pack)
			pack();
	}
}

class GuidePlane extends Plane {
	private TransformGroup transformGroup;

	private TransformGroup tg;

	private BranchGroup guidePlane;

	private float trans = 0.7f;
	private TransparencyAttributes tattr;

	private boolean visible = false;

	private Logger logger = Logger.getLogger(GuidePlane.class.getName());

	private Point3f axis1 = new Point3f(1, 0, 0);

	private Point3f axis2 = new Point3f(0, 1, 0);

	private Point3f[] initpoints = new Point3f[] { new Point3f(1f, 1f, 0f), new Point3f(1f, -1f, 0f),
			new Point3f(-1f, -1f, 0f), new Point3f(-1f, 1f, 0f) };

	private Color3f color = new Color3f(0.9f, 0.9f, 0.9f);

	GuidePlane(TransformGroup transformGroup) {
		super();
		this.transformGroup = transformGroup;
		guidePlane = new BranchGroup();
		guidePlane.setCapability(BranchGroup.ALLOW_DETACH);
		init();
	}

	private float sizex;
	private float sizey;

	GuidePlane(TransformGroup transformGroup, float sizex, float sizey) {
		float xx0 = -0.5f * sizex;
		float yy0 = -0.5f * sizey;
		float xx1 = 1.5f * sizex;
		float yy1 = 1.5f * sizey;
		initpoints = new Point3f[] { new Point3f(xx1, yy1, 0f), new Point3f(xx1, yy0, 0f), new Point3f(xx0, yy0, 0f),
				new Point3f(xx0, yy1, 0f) };
		this.transformGroup = transformGroup;
		this.sizex = sizex;
		this.sizey = sizey;
		guidePlane = new BranchGroup();
		guidePlane.setCapability(BranchGroup.ALLOW_DETACH);
		init();
	}

	private Point3f jusin = new Point3f();

	void setJusin(Point3f jusin) {
		this.jusin = jusin;
	}

	private void init() {
		for (int i = 0; i < initpoints.length; i++)
			logger.debug("initpoints: " + initpoints[i]);

		QuadArray qarray = new QuadArray(initpoints.length, QuadArray.COORDINATES | QuadArray.COLOR_3);
		qarray.setCoordinates(0, initpoints);
		qarray.setColors(0, new Color3f[] { color, color, color, color });
		Shape3D s3d = new Shape3D();
		try {
			qarray.setCapability(QuadArray.ALLOW_INTERSECT);
		} catch (RestrictedAccessException rae) {
		}
		s3d.setGeometry(qarray);

		Appearance app = new Appearance();
		tattr = new TransparencyAttributes();
		tattr.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
		tattr.setTransparency(trans);
		tattr.setTransparencyMode(TransparencyAttributes.BLENDED);
		app.setTransparencyAttributes(tattr);

		PolygonAttributes pattr = new PolygonAttributes();
		pattr.setCullFace(PolygonAttributes.CULL_NONE);
		pattr.setPolygonMode(PolygonAttributes.POLYGON_FILL);
		pattr.setBackFaceNormalFlip(true);
		app.setPolygonAttributes(pattr);

		s3d.setAppearance(app);
		tg = new TransformGroup();
		tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
		tg.addChild(s3d);
		Transform3D t3d = new Transform3D();
		t3d.setTranslation(new Vector3d((double) 0.5 * sizex, (double) 0.5 * sizey, 0d));
		tg.setTransform(t3d);
		// CylinderCreator creator = new CylinderCreator();
		// BranchGroup nor1 = creator.create(new Point3d(normal),new
		// Point3d(),0.01f);
		// tg.addChild(nor1);
		guidePlane.addChild(tg);
	}

	void setTransparency(float tran) {
		tattr.setTransparency(tran);
	}

	void resetTransparency() {
		tattr.setTransparency(this.trans);
	}

	public Point3f getNormalVector() {
		Transform3D currtrans = new Transform3D();
		tg.getTransform(currtrans);
		Vector3f vnormal = new Vector3f(0, 0, 1);
		currtrans.transform(vnormal);
		normal = new Point3f(vnormal);
		return normal;
	}

	float zoffset = 0f;

	void updateGuidePlane() {
		if (!visible) {
			transformGroup.addChild(guidePlane);
			visible = true;
		}
		logger.debug("axis1 & 2 before transformation: " + axis1 + " " + axis2);
		Vector3f unit = new Vector3f(normal);
		// calculate vectors for rotation matrix
		// rotate object in any orientation, onto Y axis (exception handled
		// below)
		// (see page 418 of _Computer Graphics_ by Hearn and Baker)
		Vector3f uX = new Vector3f();
		Vector3f uY = new Vector3f();
		Vector3f uZ = new Vector3f();
		float magX;
		Transform3D rotateFix = new Transform3D();
		uZ = new Vector3f(unit);
		uX.cross(new Vector3f(0, 1, 0), unit);
		magX = uX.length();
		Transform3D rotateMatrix = null;

		// magX == 0 if object's axis is parallel to Z axis
		if (magX != 0) {
			uX.z = uX.z / magX;
			uX.x = uX.x / magX;
			uX.y = uX.y / magX;
			uY.cross(uZ, uX);

			// create the rotation matrix
			rotateMatrix = new Transform3D(
					new Matrix4d(uX.x, uX.y, uX.z, 0, uY.x, uY.y, uY.z, 0, uZ.x, uZ.y, uZ.z, 0, 0, 0, 0, 1));
			try {
				rotateMatrix.invert();
			} catch (Exception exc) {
				logger.error("invalid rotation matrix : " + String.valueOf(rotateMatrix));
				exc.printStackTrace();
			}
		} else {
			rotateMatrix = new Transform3D();
			rotateMatrix.rotX(Math.PI / 2.0);
		}

		rotateMatrix.transform(axis1);
		rotateMatrix.transform(axis2);

		// rotateMatrix.setTranslation(new Vector3f(origin));
		// Point3f off = new Point3f(origin.x, origin.y, origin.z + zoffset);
		Point3f off = new Point3f(origin.x, origin.y, origin.z);
		rotateMatrix.setTranslation(new Vector3f(off));
		tg.setTransform(rotateMatrix);
		logger.debug("axis1 & 2 after transformation: " + axis1 + " " + axis2);
	}

	Point3f getAxis1() {
		return axis1;
	}

	Point3f getAxis2() {
		return axis2;
	}

	TransformGroup getTransform() {
		return tg;
	}

	/**
	 * Transform3Dを直接セットする.
	 * 
	 * @param t3d
	 *            セットしたいTransform3D
	 */
	void setTransform(Transform3D t3d) {
		tg.setTransform(t3d);
	}

	/**
	 * Transform3Dを直接セットする. 回転のみの場合これを使う.
	 * 
	 * @param t3d
	 *            セットしたいトランスフォーム
	 */
	void setTransformRot(Transform3D t3d) {
		if (!visible) {
			transformGroup.addChild(guidePlane);
			visible = true;
		}
		// tg.setTransform(t3d);
		// t3d.transform(normal);
		Transform3D currtrans = new Transform3D();
		tg.getTransform(currtrans);
		Matrix4d mat = new Matrix4d();
		currtrans.get(mat);

		// Translate to origin
		currtrans.setTranslation(new Vector3d(0.0, 0.0, 0.0));
		currtrans.mul(currtrans, t3d);

		// Set old translation back
		// Vector3d translation = new Vector3d(mat.m03, mat.m13, mat.m23);
		// t3d.setTranslation(translation);
		// currtrans.setTranslation(translation);
		Point3f point = new Point3f(jusin);
		point.add(origin);
		currtrans.setTranslation(new Vector3d(point));

		// Update xform
		tg.setTransform(currtrans);
		// tg.setTransform(t3d);
		// normal = new Point3f(0,0,1);
		currtrans.transform(normal);
	}

	/**
	 * このGuidePlaneをシーンから削除
	 */
	void removeGuidePlane() {
		guidePlane.detach();
		visible = false;
	}

	/**
	 * GuidePlaneが可視か否かを返す.
	 * 
	 * @return 可視ならばtrue.
	 */
	boolean isVisible() {
		return visible;
	}

}