/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on ----
!  AUTHOR(S): KOGA, Junichiro
!  File : ConfigDataManager.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;

import java.util.Vector;

import org.apache.log4j.Logger;

import ciss.phase_viewer.acviewer.scenegraphelements.TGAtom;
import ciss.phase_viewer.atomcoord.AtomCoords;
import ciss.phase_viewer.common.ElementInfo;
import ciss.phase_viewer.common.TaggedString;

public class ConfigDataManager {
	private Logger logger = Logger.getLogger(ConfigDataManager.class.getName());

	private String[][] XYZ;

	private String[][] originalXYZ;

	private int NumAt;

	private double lenmax;

	private double[] com = new double[] { 0, 0, 0 };

	private boolean cellorigin;

	private double[] cellorigin_vec;

	private boolean validcell = true;

	private AtomCoords coords;

	private AtomCoords coords_buff;

	private double[][] cell;

	private String[] elements;

	private TaggedString[][] auxils;

	private double[][] Dpos;

	private int[] mobile;

	private int[] weight;

	private Vector holdsConfigList = new Vector();

	private int frameNum = 0;

	private MainPanel parent;

	private float scaleatom;

	private int origNumAt;

	public ConfigDataManager(AtomCoords coords) {
		this.coords = coords;
		this.coords_buff = coords.getCopy();
		this.origNumAt = coords.getNumAt();
		init();
	}

	/**
	 * "オリジナルの原子数"とやらを再セット
	 */
	public void resetOrigNumAt() {
		origNumAt = coords.getNumAt();
	}

	private J3DDataManager dataManager;

	/**
	 * ビューアーの設定などを保持したオブジェクトをセット
	 * 
	 * @param dataManager
	 *            上記の通り
	 */
	public void setJ3DDataManager(J3DDataManager dataMan) {
		this.dataManager = dataMan;
	}

	public void setScaleAtom(float scale) {
		scaleatom = 2.2f * scale / (float) (Math.pow((float) origNumAt, 1.0f / 3.0f));
	}

	public float getScaleAtom() {
		return scaleatom;
	}

	private boolean renderingManual = false;

	public void isRenderingManual(boolean renderingManual) {
		this.renderingManual = renderingManual;
	}

	public void setCoords(AtomCoords coords) {
		this.coords = coords;
		init();
		update();
	}

	public void setCoords(AtomCoords coords, int updateType) {
		this.coords = coords;
		init();
		update(updateType);
	}

	public void setCoords(AtomCoords coords, boolean rescale) {
		this.coords = coords;
		init();
		update(rescale);
	}

	public void setCoordsNoUpdate(AtomCoords coords) {
		this.coords = coords;
		init();
	}

	public void doShiftScale() {
		double xmax = -100000.0d, ymax = -100000.0d, zmax = -100000.0d;
		double xmin = -xmax, ymin = -ymax, zmin = -zmax;
		double x, y, z, lenx, leny, lenz;
		double sumX = 0.0d, sumY = 0.0d, sumZ = 0.0d;
		int i;

		for (i = 0; i < Dpos.length; i++) {
			x = Dpos[i][0];
			y = Dpos[i][1];
			z = Dpos[i][2];

			sumX = sumX + x;
			sumY = sumY + y;
			sumZ = sumZ + z;

			if (x < xmin) {
				xmin = x;
			}
			if (y < ymin) {
				ymin = y;
			}
			if (z < zmin) {
				zmin = z;
			}
			if (x > xmax) {
				xmax = x;
			}
			if (y > ymax) {
				ymax = y;
			}
			if (z > zmax) {
				zmax = z;
			}
		}

		lenx = xmax - xmin;
		leny = ymax - ymin;
		lenz = zmax - zmin;

		if (lenx > lenmax) {
			lenmax = lenx;
		}
		if (leny > lenmax) {
			lenmax = leny;
		}
		if (lenz > lenmax) {
			lenmax = lenz;
		}

		if (Dpos.length == 1) {
			lenmax = 1.d;
		}
		if (dataManager == null)
			logger.error("dataManager is null...");
		lenmax = lenmax / ((double) dataManager.getScale());

		// jusin no bunn dake shift
		double jusinX = sumX / (double) Dpos.length;
		double jusinY = sumY / (double) Dpos.length;
		double jusinZ = sumZ / (double) Dpos.length;
		logger.debug("com " + jusinX + " " + jusinY + " " + jusinZ);
		if (cell != null && cell.length > 2) {
			logger.debug("cell " + cell[0][0] + " " + cell[0][1] + " " + cell[0][2]);
			logger.debug("cell " + cell[1][0] + " " + cell[1][1] + " " + cell[1][2]);
			logger.debug("cell " + cell[2][0] + " " + cell[2][1] + " " + cell[2][2]);
			double tx = 0d;
			double ty = 0d;
			double tz = 0d;
			for (int ii = 0; ii < 3; ii++) {
				tx += cell[ii][0];
				ty += cell[ii][1];
				tz += cell[ii][2];
			}
			logger.debug("center of cell " + tx * 0.5d + " " + ty * 0.5d + " " + tz * 0.5d);
			jusinX = tx * 0.5d;
			jusinY = ty * 0.5d;
			jusinZ = tz * 0.5d;
		}
		setCOM(new double[] { jusinX, jusinY, jusinZ });
	}

	private void init() {
		validcell = coords.isCellValid(0);
		NumAt = coords.getNumAt();
		if (validcell) {
			cell = coords.getCellDouble(0);
		}

		Dpos = coords.getPosDouble();
		elements = coords.getElements();
		auxils = coords.getAuxils();
		cellorigin = coords.isInternal();

		if (Dpos == null)
			return;

		double minx = 100000;
		double miny = 100000;
		double minz = 100000;
		double sumX = 0;
		double sumY = 0;
		double sumZ = 0;
		for (int i = 0; i < Dpos.length; i++) {
			if (Dpos[i][0] < minx)
				minx = Dpos[i][0];
			if (Dpos[i][1] < miny)
				miny = Dpos[i][1];
			if (Dpos[i][2] < minz)
				minz = Dpos[i][2];
			sumX += Dpos[i][0];
			sumY += Dpos[i][1];
			sumZ += Dpos[i][2];
		}
		minCoords = new double[] { minx, miny, minz };
		// this.com = new
		// double[]{sumX/Dpos.length,sumY/Dpos.length,sumZ/Dpos.length};
		// for ( int i=0 ; i<Dpos.length ; i++ ) {
		// for ( int j=0 ; j<3 ; j++ ) {
		// Dpos[i][j] -= this.com[j];
		// }
		// }
	}

	private Vector atomData;

	public void registerAtoms(ConfigData data) {
		if (atomData == null)
			atomData = new Vector();
		atomData.add(data);
	}

	public void removeConfigData(ConfigData data) {
		if (holdsConfigList != null)
			holdsConfigList.remove(data);
	}

	public void register(ConfigData data) {
		holdsConfigList.addElement(data);
	}

	/**
	 * 大元のクラスをセット.
	 * 
	 * @param parent
	 *            ACViewerの一番大元のクラス
	 */
	public void setParent(MainPanel parent) {
		this.parent = parent;
	}

	/**
	 * 原子配置になんらかの操作を行った後呼ぶ. このメソッドを呼ぶと,
	 * ConfigDataインターフェースをインプリメントしたクラスのconfigDataUpdate メソッドが呼ばれる.
	 */
	public void update() {
		update(true);
	}

	private void update(int updateType) {
		try {
			if (!renderingManual)
				parent.prepareRedraw();
			int n = holdsConfigList.size();
			// ((TGAtom)parent.getRootTransform()).prepare(true);
			((TGAtom) parent.getRootTransform()).prepare(false);
			((TGAtom) parent.getRootTransform()).atomUpdate();
			// for ( int i=atomData.size()-1 ; i>=0 ; i-- ) {
			// ConfigData data = (ConfigData) atomData.get(i);
			// ConfigDataUpdateEvent e = new
			// ConfigDataUpdateEvent(data,updateType);
			// data.configDataUpdate(true, e);
			// }
			//
			for (int i = n - 1; i >= 0; i--) {
				ConfigData data = (ConfigData) holdsConfigList.get(i);
				ConfigDataUpdateEvent e = new ConfigDataUpdateEvent(data, updateType);
				if (data.needsUpdate()) {
					data.configDataUpdate(true, e);
				}
			}
			if (!renderingManual)
				parent.postRedraw();
			forceUpdate = false;
		} catch (Exception exc) {
			exc.printStackTrace();
		}
	}

	private boolean forceUpdate = false;

	/**
	 * この戻り値がtrueの場合は強制的な更新を行う
	 * 
	 * @return 上記の通り
	 */
	public boolean forceUpdate() {
		return forceUpdate;
	}

	/**
	 * 強制的にシーンを更新したい場合これをtrueにセットする. 更新が終了した暁には再びfalseとなる
	 * 
	 * @param forceUpdate
	 *            強制的な更新を行う場合true
	 */
	public void forceUpdate(boolean forceUpdate) {
		this.forceUpdate = forceUpdate;
	}

	public void update(boolean rescaleOnUpdate) {
		try {
			if (!renderingManual)
				parent.prepareRedraw();
			int n = holdsConfigList.size();
			// ((TGAtom)parent.getRootTransform()).prepare(rescaleOnUpdate);
			((TGAtom) parent.getRootTransform()).prepare(false);
			((TGAtom) parent.getRootTransform()).atomUpdate();
			//
			// for ( int i=atomData.size()-1 ; i>=0 ; i-- ) {
			// ConfigData data = (ConfigData) atomData.get(i);
			// ConfigDataUpdateEvent e = new
			// ConfigDataUpdateEvent(data,ConfigDataUpdateEvent.GENERIC);
			// data.configDataUpdate(rescaleOnUpdate, e);
			// }

			for (int i = n - 1; i >= 0; i--) {
				ConfigData data = (ConfigData) holdsConfigList.get(i);
				ConfigDataUpdateEvent e = new ConfigDataUpdateEvent(data, ConfigDataUpdateEvent.GENERIC);
				if (data.needsUpdate()) {
					data.configDataUpdate(rescaleOnUpdate, e);
				}
			}
			if (!renderingManual)
				parent.postRedraw();
			forceUpdate = false;
		} catch (Exception exc) {
			exc.printStackTrace();
		}
	}

	public AtomCoords getAtomCoords() {
		return this.coords;
	}

	public double[][] getOriginalCellVec() {
		double[][] cell_buff = null;
		if (validcell) {
			cell_buff = coords_buff.getCellDouble(0);
		}
		return cell_buff;
	}

	public double[][] getPos() {
		return this.coords.getPosDouble();
		// return this.Dpos;
	}

	public double[] getPos(int key) {
		// return this.Dpos[key];
		return this.coords.getPosDouble()[key];
	}

	public double[][] getForce() {
		return coords.getForceDouble();
	}

	public double[] getForce(int key) {
		return coords.getForceDouble()[key];
	}

	public double[][] getOriginalPos() {
		return coords_buff.getPosDouble();
	}

	public String[] getOriginalElements() {
		String[] elements_buff = coords_buff.getElements();
		return elements_buff;
	}

	public TaggedString[][] getOriginalAuxils() {
		TaggedString[][] auxils_buff = coords_buff.getAuxils();
		return auxils_buff;
	}

	public double[][] getCell() {
		if (this.coords.getCell() != null && this.coords.getCell()[0] != null) {
			return this.coords.getCell()[0].getDouble();
		}
		return null;
	}

	public void setCell(double[][] cell) {
		this.cell = cell;
	}

	public String[] getElements() {
		return this.elements;
	}

	public int getAtomicNumber(int key) {
		String elemtmp = elements[key];
		ElementInfo ei = ElementInfo.getElementInfo();
		return ei.getNumberFromSymbol(elemtmp);
	}

	public void setElements(String elements, int key) {
		this.elements[key] = elements;
	}

	public void setElements(String[] elements) {
		this.elements = elements;
	}

	public void setAuxils(TaggedString[][] auxils) {
		this.auxils = auxils;
	}

	public void setAuxils(TaggedString[] auxils, int key) {
		this.auxils[key] = auxils;
	}

	public TaggedString[][] getAuxils() {
		return this.auxils;
	}

	public TaggedString[] getAuxils(int key) {
		return this.auxils[key];
	}

	public void setLenMax(double lenmax) {
		this.lenmax = lenmax;
	}

	public double getLenMax() {
		return this.lenmax;
	}

	public double[] getCOM() {
		return this.com;
	}

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

	/**
	 * @return 原子数
	 */
	public int getNumAt() {
		return coords.getNumAt();
	}

	/**
	 * @return セルの原点が指定されているか否か.
	 */
	public boolean hasCellOrigin() {
		return this.cellorigin;
	}

	private double[] minCoords;

	/**
	 * 原子の最小の座標を取得 未スケールの値 この値は, 座標値をセットした時点で作成される
	 * 
	 * @return 上記の通り
	 */
	public double[] getMinCoords() {
		return this.minCoords;
	}

	/**
	 * セルの原点の座標を返す.
	 * 
	 * @param shiftPolicy
	 *            セルをどのようにシフトするかを指定; J3DDataManager.CENTER_OF_CELLないしEDGE_OF_CELL
	 * @return セルの原点の配列.
	 */
	public double[] getCellOriginVector(int shiftPolicy) {
		double[] ret = new double[3];
		double[] offset = new double[] { 0, 0, 0 };
		if (coords.getCell() != null && coords.getCell()[0] != null) {
			offset = coords.getCell()[0].getCellOffset();
		}
		if (hasCellOrigin() || shiftPolicy == J3DDataManager.CENTER_OF_CELL) {
			for (int i = 0; i < 3; i++) {
				ret[i] = -com[i] - offset[i];
			}
		} else {
			for (int i = 0; i < 3; i++) {
				ret[i] = minCoords[i] - com[i] - offset[i];
			}
		}
		return ret;
	}

	/**
	 * セルの原点の座標を返す.
	 * 
	 * @return セルの原点の配列; シフトの仕方は, J3DDataManager.CENTER_OF_CELL.
	 */
	public double[] getCellOriginVector() {
		return getCellOriginVector(J3DDataManager.CENTER_OF_CELL);
	}

	/**
	 * セルが正しく指定されていたか否かを表す真偽値, validcellを設定する.
	 * 
	 * @param validcell
	 *            正しく設定されていれば真.
	 */
	private void setValidCell(boolean validcell) {
		this.validcell = validcell;
	}

	/**
	 * @return セルが正しく設定されていれば真を返す.
	 */
	public boolean getValidCell() {
		return coords.isCellValid(0);
	}

	/**
	 * 何番目のフレームかをセット. できれば自動的に決まるようにしたいけど...
	 * 多分jp..chase.plugins.viewer.frame.Animaterクラスでしかフレーム操作は していないはず.
	 * 
	 * @param frameNum
	 *            このクラスに格納されている原子配置情報は, 何番目のフレームのものか.
	 */
	public void setFrameNum(int frameNum) {
		this.frameNum = frameNum;
	}

	/**
	 * 何番目のフレームかを返す.
	 * 
	 * @return 何番めのフレームか.
	 */
	public int getFrameNum() {
		return this.frameNum;
	}

	/**
	 * 「Java3D仮想空間」の座標から原子の実際の座標を取得する.
	 * 
	 * @param x
	 *            仮想空間のx座標.
	 * @param y
	 *            仮想空間のy座標.
	 * @param z
	 *            仮想空間のz座標.
	 * @return 原子の実際の座標.
	 */
	public double[] getPosfromVirtualWorld(double x, double y, double z) {
		double lenmax = getLenMax();
		double[] jusin = getCOM();
		double[] dnewpos = new double[3];
		dnewpos[0] = x * lenmax + jusin[0];
		dnewpos[1] = y * lenmax + jusin[1];
		dnewpos[2] = z * lenmax + jusin[2];
		return dnewpos;
	}

}
