/*
 !=======================================================================
 !
 !  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
 !
 !  Created on 2006/07/18, 20:37
 !  AUTHOR(S): KOGA, Junichiro
 !
 !  The license of the code and contact address:
 !  See the files, COPYRIGHT and LICENSE (or LICENSE_J.pdf)
 !
 !=======================================================================
 */

package ciss.phase_viewer.acviewer;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DepthComponentFloat;
import javax.media.j3d.GraphicsContext3D;
import javax.media.j3d.ImageComponent;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.VirtualUniverse;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import javax.vecmath.Point3f;

import org.apache.log4j.Logger;
import org.freehep.util.export.ExportDialog;
import org.freehep.util.export.ExportFileType;

import ciss.phase_viewer.acviewer.vrml.VRMLExportFileType;
import ciss.phase_viewer.acviewer.vrml.VRMLExporter;
import ciss.phase_viewer.atomcoord.VolumetricData;
import ciss.phase_viewer.common.Drawable;
import ciss.phase_viewer.common.Utils;
import ciss.phase_viewer.graph.export.EPSExportFileType;
import ciss.phase_viewer.graph.export.PDFExportFileType;
import ciss.phase_viewer.graph.export.SVGExportFileType;
import ciss.phase_viewer.imageviewer.ImagePrinter;
import ciss.phase_viewer.mainpanel.ChaseFrame;
import ciss.phase_viewer.mainpanel.InternalFrameChase;
import ciss.phase_viewer.settings.GlobalProperties;
import ciss.phase_viewer.settings.PropertiesManager;

import com.sun.j3d.utils.universe.SimpleUniverse;

/**
 * Java3Dで三次元空間を描画するための, ベースクラス. 今のところ, 原子配置ビューアーとブリユアンゾーンビューアーに 利用されている.
 * 
 * @author
 */
public abstract class BaseJ3DPanel extends InternalFrameChase implements
        Drawable, J3DPanel {
    protected static Dimension defaultSize = new Dimension(512, 540);

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

    /** 描画される, 一番上のBranchGroup */
    protected BranchGroup rootBranch;

    /** 利用されるCanvas3D */
    protected Canvas3D canvas3d;

    /** 利用されるSimpleUniverse; より複雑なUniverseを利用する場合は別途作成 */
    protected SimpleUniverse simpleU;

    /** 関連付けられたGUIを格納するためのVector */
    protected Vector associatedGUIs = new Vector();

    /** エクスポート時に利用する */
    protected JLabel exportLabel;

    /** プラグインを処理するためのオブジェクト */
    protected PluginParser pluginParser;

    /** ベースとなるべきディレクトリー. エクスポートの際などに利用される */
    protected String parentDir = System.getProperty("user.home");

    /** 描画に関わる, いくつかの情報を保持するオブジェクト */
    protected J3DDataManager dataManager;

    /** 再描画を行うべきか否かを判定するためのフラグ */
    protected boolean doRedraw = true;

    /** ビューアー用のプロパティー */
    protected GlobalProperties prop = PropertiesManager
            .getGlobalProperties(PropertiesManager.PROPERTIES_ACV);

    private static boolean USE_JCANVAS3D = new Boolean(PropertiesManager
            .getGlobalProperties(PropertiesManager.PROPERTIES_ACV).getProperty(
                    "canvas3d_use_jcanvas3d")).booleanValue();

    /**
     * ルートフレームのタイトルとその大きさを指定する.
     * 
     * @param title
     *            ルートフレームのタイトル
     * @param initSize
     *            ルートフレームの大きさ
     */
    public BaseJ3DPanel(String title, Dimension initSize) {
        super(title, true, true, true, true, initSize, ChaseFrame.AUTOMATIC,
                false);
        initBaseJ3DPanel();
        // setVisible(true);
    }

    /**
     * プラグイン情報の記述されたファイルへのパスを返す; デフォルトは原子配置ビューアーのそれ
     * 
     * @return プラグイン情報の記述されたファイルへのパス
     */
    protected String getPluginPath() {
        return PluginParser.PLUGIN_ACV;
    }

    protected JPanel jCanvas3D;

    /**
     * Java3Dの画面を, 'JCanvas3D'クラスを利用して描画する場合true;
     * canvas3d_use_jcanvas3dプロパティーによって指定可.
     * 
     * @return JCanvas3Dを利用するならtrue.
     */
    public static boolean useJCanvas3D() {
        return new Boolean(PropertiesManager.getGlobalProperties(
                PropertiesManager.PROPERTIES_ACV).getProperty(
                "canvas3d_use_jcanvas3d")).booleanValue();
    }

    private void initBaseJ3DPanel() {
        addInternalFrameListener(new InternalFrameAdapter() {
            public void internalFrameClosed(InternalFrameEvent ev) {
                destroy();
            }
        });

        // addWindowListener(new WindowAdapter() {
        // public void windowClosing(WindowEvent we) {
        // destroy();
        // }
        //
        // public void windowClosed(WindowEvent we) {
        // destroy();
        // }
        // });
        // System.out.println("creating canvas");

        // GraphicsConfiguration gc = null;
        // gc = SimpleUniverse.getPreferredConfiguration();
        // int retries = 0;
        // while (gc == null || retries < 100) {
        // gc = SimpleUniverse.getPreferredConfiguration();
        // retries++;
        // }
        // System.out.println("finished.");

        // if (gc == null) {
        // logger
        // .error("falied to obtain a suitable graphics configuration...
        // retry.");
        // dispose();
        // return;
        // }

        if (useJCanvas3D()) {
            JCanvas3DWrapper wra = new JCanvas3DWrapper();
            jCanvas3D = wra.getPanel();
            getContentPane().setLayout(new BorderLayout());
            getContentPane().add(jCanvas3D);
            canvas3d = ((JCanvas3DWrapper) wra).getCanvas3D();
        } else {
            try {
                // gc=new GraphicsConfigTemplate3D();
                // GraphicsConfigTemplate3D gconfigTemplate = new
                // GraphicsConfigTemplate3D();
                // GraphicsConfiguration gc = GraphicsEnvironment
                // .getLocalGraphicsEnvironment().getDefaultScreenDevice()
                // .getBestConfiguration(gconfigTemplate);
                GraphicsConfiguration gc = SimpleUniverse
                        .getPreferredConfiguration();
                if (gc == null)
                    gc = new Window(new Frame()).getGraphicsConfiguration();
                canvas3d = new ChaseCanvas3D(gc);
                jCanvas3D = new JPanel();
                jCanvas3D.add(canvas3d);
                Dimension canvsize = getSize();
                canvsize.height = canvsize.height - 90;
                canvsize.width = canvsize.width - 10;
                canvas3d.setSize(canvsize);
                // getContentPane().add(jCanvas3D);
                getContentPane().add(canvas3d);
                if (Utils.parseJREVersion() < 170)
                    setLayer(new Integer(1));
            } catch (Exception exc) {
                exc.printStackTrace();
            }
        }

        simpleU = new SimpleUniverse(canvas3d);
        simpleU.getViewingPlatform().setNominalViewingTransform();

        VirtualUniverse.setJ3DThreadPriority(Thread.MAX_PRIORITY);
        dataManager = new J3DDataManager();
        dataManager.setParent(this);

        if (useJCanvas3D()) {
            jCanvas3D.addMouseMotionListener(new MouseMotionListener() {
                public void mouseDragged(MouseEvent e) {
                }

                public void mouseMoved(MouseEvent e) {
                    jCanvas3D.requestFocusInWindow();
                }
            });
        } else {
            jCanvas3D.addMouseMotionListener(new MouseMotionListener() {
                public void mouseDragged(MouseEvent e) {
                }

                public void mouseMoved(MouseEvent e) {
                }
            });
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getRootBranch()
     */
    public BranchGroup getRootBranch() {
        return this.rootBranch;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getPluginParser()
     */
    public PluginParser getPluginParser() {
        if (pluginParser == null) {
            pluginParser = new PluginParser(this, getPluginPath());
        }
        return this.pluginParser;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#isHeavyWeight()
     */
    public boolean isHeavyWeight() {
        return true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getUniverse()
     */
    public SimpleUniverse getUniverse() {
        return this.simpleU;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#destroy()
     */
    public void destroy() {
        if (associatedGUIs != null) {
            for (int i = 0; i < associatedGUIs.size(); i++) {
                Object editor = associatedGUIs.get(i);
                if (editor instanceof JInternalFrame) {
                    javax.swing.JInternalFrame window = (javax.swing.JInternalFrame) associatedGUIs
                            .get(i);
                    if (window != null) {
                        window.dispose();
                    }
                } else if (editor instanceof JFrame) {
                    javax.swing.JFrame window = (javax.swing.JFrame) associatedGUIs
                            .get(i);
                    if (window != null) {
                        window.dispose();
                    }
                }
            }
        }
        try {
            removeFromDesktop();
            removeAll();
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    /**
     * 色々な処理をすることによって, なるべくメモリーを解放する努力をするメソッド
     */
    protected void removeFromDesktop() {
        if (rootBranch != null) {
            rootBranch.detach();
        }

        ((ChaseTransformGroup) getRootTransform()).cleanUp();

        rootBranch = null;
        if (canvas3d != null) {
            try {
                canvas3d.stopRenderer();
            } catch (Exception ex) {
            }
        }

        // canvas3d = null;
        if (simpleU != null) {
            simpleU.removeAllLocales();
            simpleU.getViewer().setViewingPlatform(null);
            simpleU.getViewer().getView().removeCanvas3D(canvas3d);
            simpleU.cleanup();
        }
        canvas3d = null;
        simpleU = null;
        postCleanUp();
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#postCleanUp()
     */
    public void postCleanUp() {
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getDisposeOnExit()
     */
    public Vector getDisposeOnExit() {
        return this.associatedGUIs;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * ciss.phase_viewer.acviewer.J3DPanel#addDisposeOnExit(java.awt.Window)
     */
    public void addDisposeOnExit(Window window) {
        int version = Utils.parseJREVersion();
        if (!useJCanvas3D() && version < 170) {
            Point pos = getLocation();
            pos.x = pos.x + getSize().width;
            window.setLocation(pos);
        }
        associatedGUIs.addElement(window);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * ciss.phase_viewer.acviewer.J3DPanel#addDisposeOnExit(ciss.phase_viewer
     * .mainpanel.InternalFrameChase)
     */
    public void addDisposeOnExit(InternalFrameChase window) {
        int version = Utils.parseJREVersion();
        if (!useJCanvas3D() && version < 170) {
            Point pos = getLocation();
            pos.x = pos.x + getSize().width;
            window.setLocation(pos);
        }
        associatedGUIs.addElement(window);
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#export(java.lang.String)
     */
    public void export(String fileName) throws IOException {
        doExport(fileName);
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#export()
     */
    public void export() throws IOException {
        doExport("");
    }

    /**
     * エクスポートする際に利用される, デフォルトの接頭語を返す. デフォルトは"atomic_config"
     * 
     * @return エクスポートの際に利用されるファイル名の接頭部
     */
    protected String getExportFileNamePrefix() {
        return "atomic_config";
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getParentDirectory()
     */
    public String getParentDirectory() {
        return this.parentDir;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#doExportVRML(java.lang.String)
     */
    public void doExportVRML(String fileName) throws IOException {
        VRMLExporter exp = new VRMLExporter(this);
        exp.doExport(fileName);
    }

    /**
     * プリンターへ出力
     */
    public void print() {
        BufferedImage image = this.createImage();
        ImagePrinter printer = new ImagePrinter(image);
        printer.printImage();
    }

    private void doExport(String fileName) throws IOException {
        BufferedImage image = this.createImage();
        if (fileName.trim().length() == 0) {
            int width = canvas3d.getSize().width;
            int height = canvas3d.getSize().height;
            ImageIcon icon = new ImageIcon(image);
            exportLabel = new JLabel(icon);
            exportLabel.setSize(width, height);
            ExportFileType png = org.freehep.graphicsio.exportchooser.ImageExportFileType
                    .getInstance("png");
            ExportFileType jpg = org.freehep.graphicsio.exportchooser.ImageExportFileType
                    .getInstance("jpg");
            ExportFileType eps = new EPSExportFileType(this);
            PDFExportFileType pdfexport = new PDFExportFileType(this);
            SVGExportFileType svgexport = new SVGExportFileType(this);
            VRMLExportFileType vrmlexport = new VRMLExportFileType(this);
            // ---freeHEP---
            ExportDialog exportD = new ExportDialog("export", false);

            Properties prop = new Properties();
            prop.setProperty("org.freehep.util.export.ExportDialog.SaveAsFile",
                    parentDir + File.separator + getExportFileNamePrefix());
            exportD.setUserProperties(prop);
            exportD.addExportFileType(png);
            exportD.addExportFileType(jpg);
            // exportD.addExportFileType(eps);
            exportD.addExportFileType(pdfexport);
            exportD.addExportFileType(svgexport);
            exportD.addExportFileType(vrmlexport);
            exportD.showExportDialog((Component) this, "export view as ...",
                    exportLabel, "atomic_config");
            // ---freeHEP---
        } else {
            ImageIO.write((RenderedImage) image, "jpg", new File(fileName));
            logger.info("exported image to file: " + fileName);
        }
        image = null;
    }

    /**
     * エクスポートの際に呼ばれる, こともある
     */
    public void draw(Graphics2D g2d, Rectangle2D rec) {
        exportLabel.print(g2d);
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getCanvas()
     */
    public Canvas3D getCanvas() {
        return this.canvas3d;
    }

    public JPanel getJCanvas3D() {
        return this.jCanvas3D;
    }

    private BufferedImage createImage() {
        int width = canvas3d.getSize().width;
        int height = canvas3d.getSize().height;

        BufferedImage bi = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        ImageComponent2D ic2d = new ImageComponent2D(ImageComponent.FORMAT_RGB,
                bi);
        DepthComponentFloat dcf = new DepthComponentFloat(width, height);
        javax.media.j3d.Raster readRaster = new javax.media.j3d.Raster(
                new Point3f(0.0f, 0.0f, 0.0f),
                javax.media.j3d.Raster.RASTER_COLOR, 0, 0, width, height, ic2d,
                dcf);

        GraphicsContext3D gc3d = canvas3d.getGraphicsContext3D();
        gc3d.flush(true);
        gc3d.readRaster(readRaster);

        ImageComponent2D ic = readRaster.getImage();
        return ic.getImage();
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getJ3DDataManager()
     */
    public J3DDataManager getJ3DDataManager() {
        return this.dataManager;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#doRedraw()
     */
    public boolean doRedraw() {
        return doRedraw;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#doRedraw(boolean)
     */
    public void doRedraw(boolean doRedraw) {
        this.doRedraw = doRedraw;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#prepareRedraw()
     */
    public void prepareRedraw() {
        try {
            canvas3d.getView().stopView();
        } catch (Exception exc) {
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#setProjectionPolicy(int)
     */
    public void setProjectionPolicy(int projectionPolicy) {
        canvas3d.getView().setProjectionPolicy(projectionPolicy);
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getProjectionPolicy()
     */
    public int getProjectionPolicy() {
        return canvas3d.getView().getProjectionPolicy();
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#postRedraw()
     */
    public void postRedraw() {
        try {
            canvas3d.getView().startView();
        } catch (Exception exc) {
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getJ3DProperties()
     */
    public GlobalProperties getJ3DProperties() {
        return prop;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getAssociatedVolumetricData()
     */
    public abstract VolumetricData[] getAssociatedVolumetricData();

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getRootTransform()
     */
    public abstract TransformGroup getRootTransform();

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#getRotatingTransform()
     */
    public TransformGroup[] getRotatingTransform() {
        return new TransformGroup[] { getRootTransform() };
    }

    /*
     * (non-Javadoc)
     * 
     * @see ciss.phase_viewer.acviewer.J3DPanel#supportsFrame()
     */
    public abstract boolean supportsFrame();
}
