/*
!=======================================================================
!
!  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;
        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;
    }

}
