/**
 * Copyright (c) Artenum SARL 2004-2005
 * @author Julien Forest / Sebastien Jourdain
 *
 * All rights reserved. This software can
 * not be used or copy or diffused without
 * an explicit license of Artenum SARL, Paris-France
 */
package com.artenum.cassandra.plugin.clipping;

import com.artenum.cassandra.action.PluginActionListener;
import com.artenum.cassandra.pipeline.Filter;
import com.artenum.cassandra.pipeline.FilterImpl;
import com.artenum.cassandra.pipeline.PipeLineManager;
import com.artenum.cassandra.pipeline.RemoveListener;
import com.artenum.cassandra.pipeline.SimplePipeLineManager;
import com.artenum.cassandra.pipeline.CassandraObject;
import com.artenum.cassandra.pipeline.graph.VtkObjectCellAdapter;
import com.artenum.cassandra.plugin.CassandraPlugInProperty;
import com.artenum.cassandra.plugin.CassandraPlugin;
import com.artenum.cassandra.plugin.PlugInControlUI;
import com.artenum.cassandra.plugin.PluginManager;
import com.artenum.cassandra.plugin.SimplePlugInProperty;

import vtk.vtkActor;
import vtk.vtkClipDataSet;
import vtk.vtkDataObject;
import vtk.vtkDataSet;
import vtk.vtkDataSetMapper;
import vtk.vtkImplicitPlaneWidget;
import vtk.vtkLookupTable;
import vtk.vtkMapper;
import vtk.vtkObject;
import vtk.vtkPlane;
import vtk.vtkScalarBarActor;

import java.awt.Frame;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

/**
 * <pre>
 * &lt;b&gt;Project ref           :&lt;/b&gt; CASSANDRA project
 * &lt;b&gt;Copyright and license :&lt;/b&gt; See relevant sections
 * &lt;b&gt;Status                :&lt;/b&gt; under development
 * &lt;b&gt;Creation              :&lt;/b&gt; 04/03/2005
 * &lt;b&gt;Modification          :&lt;/b&gt;
 * &lt;b&gt;Description  :&lt;/b&gt;
 *             This class defines the VTK processing pipeline itself
 *             and the initialisation of the contextuel GUI of the plugin.
 * 
 * </pre>
 * <table cellpadding="3" cellspacing="0" border="1" width="100%">
 * <tr BGCOLOR="#CCCCFF" CLASS="TableHeadingColor">
 * <td><b>Version number</b></td>
 * <td><b>Author (name, e-mail)</b></td>
 * <td><b>Corrections/Modifications</b></td>
 * </tr>
 * <tr>
 * <td>0.1</td>
 * <td>Sebastien Jourdain, jourdain@artenum.com</td>
 * <td>Creation</td>
 * </tr>
 * </table>
 * 
 * @author Sebastien Jourdain, Artenum SARL
 * @author Julien Forest, Artenum SARL
 * 
 * @version 1.2
 */
public class ClippingPlanePlugin implements CassandraPlugin, RemoveListener {
    // Cassandra elements
    private String name;
    private PipeLineManager pipelineManager;
    private PluginManager pluginManager;
    private JPopupMenu contextualMenu;
    private int nbUpdate = 0;

    // Contextual control GUI of the plugin
    private ClippingPlaneControlUI controlUI;

    // vtk object of the sub-pipeline built into the plug-in
    private vtkDataSet lastDataSet;
    private vtkLookupTable outputLookupTable;
    private vtkScalarBarActor outputScalarBar;
    private vtkPlane sourcePlane;
    private vtkClipDataSet planeClip;
    private vtkDataSetMapper outputMapper;
    private vtkActor outputActor;
    private Filter filterImpl;

    // Cassandra VtkObjects for the pipeline models
    private CassandraObject outputCassActor;
    private CassandraObject outputCassMapper;
    private CassandraObject outputCassDataset;
    private CassandraObject outputCassLookupTable;
    private CassandraObject outputCassScalarBar;
    private CassandraObject cassfilter;

    private vtkImplicitPlaneWidget planWidget;

    //private boolean loadOutputPipelineComponents = true;

    public static final String PERSISTENCE_KEY_CX = "persistence.key.cx";
    public static final String PERSISTENCE_KEY_CY = "persistence.key.cy";
    public static final String PERSISTENCE_KEY_CZ = "persistence.key.cz";
    public static final String PERSISTENCE_KEY_NX = "persistence.key.nx";
    public static final String PERSISTENCE_KEY_NY = "persistence.key.ny";
    public static final String PERSISTENCE_KEY_NZ = "persistence.key.nz";
    public static final String PERSISTENCE_KEY_INPUT_DATASET = "persistence.key.inputVtkDataSet";
    public static final String PERSISTENCE_KEY_INOUT = "persistence.key.insideOut";
    public static final String PERSISTENCE_KEY_OUPUT_DATASET = "persistence.key.outputDataSet";
    public static final String PERSISTENCE_KEY_OUPUT_MAPPER = "persistence.key.outputMapper";
    public static final String PERSISTENCE_KEY_OUPUT_LOOKUP_TABLE = "persistence.key.outputlookUpTable";
    public static final String PERSISTENCE_KEY_OUTPUT_ACTOR = "persistence.key.outputActor";
    public static final String PERSISTENCE_KEY_OUTPUT_SCALAR_BAR = "persistence.key.ouputScalarBar";
    public static final String PERSISTENCE_KEY_CELL_POS_X = "persistence.key.cell.PosX";
    public static final String PERSISTENCE_KEY_CELL_POS_Y = "persistence.key.cell.PosY";

    /**
     * Main constructor and VTK pipeline definition.
     * 
     * @param pipelineManager
     *            is the central pipeline manager of Cassandra.
     * @param pluginManager
     *            is the central list of plugin instance, where a contextual
     *            menu access is given.
     */
    public ClippingPlanePlugin(PipeLineManager pipelineManager, PluginManager pluginManager, Frame owner) {
        initPlugIn(pipelineManager, pluginManager, owner);
        initPipeline();
    }

    /**
     * Constructor used in the pipeline importer
     * 
     * @param pipelineManager
     * @param pluginManager
     * @param owner
     * @param initPipeline
     */
    public ClippingPlanePlugin(PipeLineManager pipelineManager, PluginManager pluginManager, Frame owner, boolean initPipeline) {
        initPlugIn(pipelineManager, pluginManager, owner);
        if (initPipeline) {
            initPipeline();
        }
    }

    /**
     * Default constructor as used during the instantiation in Cassandra.
     * 
     * @param pipelineManager
     * @param pluginManager
     * @param owner
     * @param initPipeline
     */
    public ClippingPlanePlugin(PipeLineManager pipelineManager, PluginManager pluginManager, Frame owner, int initPipeline) {
        initPlugIn(pipelineManager, pluginManager, owner);
        if (initPipeline == 1) {
            initPipeline();
        }
    }

    /**
     * Initialise the plug-in. 
     * 
     * @param pipelineManager - plug-in manager
     * @param pluginManager 
     * @param owner - GUI owner
     */
    public void initPlugIn(PipeLineManager pipelineManager, PluginManager pluginManager, Frame owner) {
        // Init default variable
        this.name = "Cliping plane";
        this.pipelineManager = pipelineManager;
        this.pluginManager = pluginManager;

        // Cassandra filter macro
        filterImpl = new FilterImpl();
        filterImpl.addRemoveListener(this);
        filterImpl.setParentPlugin(this);

        // Init all the control UI
        controlUI = new ClippingPlaneControlUI(pipelineManager, this, owner);

        // Init contextual menu
        contextualMenu = new JPopupMenu("Cutting plane menu");
        JMenuItem showControl = new JMenuItem("Show control");
        showControl.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                controlUI.setVisible(true);
                controlUI.update3DWidgetStatus();
            }
        });
        contextualMenu.add(showControl);
        contextualMenu.addSeparator();
        JMenuItem remove = new JMenuItem("Remove");
        remove.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                remove();
            }
        });
        contextualMenu.add(remove);
    }

    /**
     * Initialises the processing pipeline it-self. Should be called after the
     * initPlugIn. If its elements (i.e internal vtk components and the
     * post-filter pipeline) are not set before, their are built as new
     * instances and registered into the pipeline.
     */
    public void initPipeline() {
        // Define and init the VTK pipeline
        if (sourcePlane == null) {
            sourcePlane = new vtkPlane();
        }
        sourcePlane.SetOrigin(0.0, 0.0, 0.0);
        sourcePlane.SetNormal(0, 0, 1.0);

        if (planeClip == null) {
            planeClip = new vtkClipDataSet();
            this.outputCassDataset = pipelineManager.addDataSet(planeClip.GetOutput(), "Clipping Plane Output");
        } else outputCassDataset = pipelineManager.getCassandraVtkObjectByTrueVtkObject(planeClip);
        planeClip.SetClipFunction(sourcePlane);

        if (outputMapper == null) {
            outputMapper = new vtkDataSetMapper();
            outputCassMapper = pipelineManager.addMapper(outputMapper, "Clipping plane Mapper");
        } else outputCassMapper = pipelineManager.getCassandraVtkObjectByTrueVtkObject(outputMapper);
        outputMapper.SetInput(planeClip.GetOutput());

        // we recover the common lookup table
        /*
         * LookupTable Table = new LookupTable(); Table.Build();
         * cutMapper.SetLookupTable(Table.GetLookupTable());
         * System.out.println("Color map =" +
         * cutMapper.GetLookupTable().Print());
         */
        if (outputActor == null) {
            outputActor = new vtkActor();
            outputCassActor = pipelineManager.addActor(outputActor, "Clipping plane Actor");
        } else outputCassActor = pipelineManager.getCassandraVtkObjectByTrueVtkObject(outputActor);
        outputActor.SetMapper(outputMapper);

        // Build lookupTable
        if (outputLookupTable == null) {
            outputLookupTable = new vtkLookupTable();
            outputCassLookupTable = pipelineManager.addLookupTable(outputLookupTable, "Clipping plane");
        } else outputCassLookupTable = pipelineManager.getCassandraVtkObjectByTrueVtkObject(outputLookupTable);
        outputLookupTable.SetHueRange(0.66667, 0);
        outputLookupTable.Build();
        outputMapper.SetLookupTable(outputLookupTable);
        
        // Scalar bar
        if (outputScalarBar == null) {
            outputScalarBar = new vtkScalarBarActor();
            outputCassScalarBar = pipelineManager.addScalarBar(outputScalarBar, "Clipping plane");
        } else outputCassScalarBar = pipelineManager.getCassandraVtkObjectByTrueVtkObject(outputScalarBar);
        outputScalarBar.SetLookupTable(outputLookupTable);

        // Load the filter in the Cassandra pipeline
        cassfilter = pipelineManager.addFilter(filterImpl, "Clipping Plane Filter");
        cassfilter.getMetaData().put(CassandraObject.POPUP_MENU, getContextualMenu());

        // end of the VTK pipeline
        outputCassActor.setValide(false);

        // just to update the position of the 3D widget if needed
        if (planWidget != null) {
            planWidget.SetNormal(sourcePlane.GetNormal());
            planWidget.SetOrigin(sourcePlane.GetOrigin());
        }
    }

    /**
     * Set the internal component from an external pipeline like in the Jython
     * script reloading procedure.
     * 
     * @param plane
     * @param planeClip
     * @param cutMapper
     * @param cutActor
     */
    @Deprecated
    public void setInternalPipelineComponents(vtkPlane plane, vtkClipDataSet planeClip, vtkDataSetMapper cutMapper, vtkActor cutActor) {
        this.sourcePlane = plane;
        this.planeClip = planeClip;
    }

    /**
     * Update the data in the defined pipeline after its settings.
     * 
     * @param cx
     * @param cy
     * @param cz
     * @param nx
     * @param ny
     * @param nz
     * @param vtkDataSet
     */
    public void updateClippingPlane(double cx, double cy, double cz, double nx, double ny, double nz, vtkDataSet vtkDataSet, boolean insideOut) {
        if (vtkDataSet == null) {
            return;
        }

        outputCassActor.setValide(true);

        //
        sourcePlane.SetOrigin(cx, cy, cz);
        sourcePlane.SetNormal(nx, ny, nz);
        planeClip.SetInput(vtkDataSet);
        planeClip.SetInsideOut(insideOut ? 1 : 0);
        
        //FIXME : should be moved / handle by the event listener of the pipeline. 
        outputCassLookupTable = pipelineManager.getCassandraVtkObjectByTrueVtkObject((vtkLookupTable) outputMapper.GetLookupTable());
        outputCassScalarBar = pipelineManager.getVtkObject(outputCassLookupTable.getOutputConnectivityList().get(0));
        //
               
        if (vtkDataSet.GetPointData().GetScalars() != null || vtkDataSet.GetCellData().GetScalars() != null) {
            if (((lastDataSet == null) || !lastDataSet.equals(vtkDataSet)) && outputMapper.GetLookupTable().equals(outputLookupTable)) {
                outputMapper.SetScalarRange(vtkDataSet.GetScalarRange());
                outputLookupTable.SetTableRange(vtkDataSet.GetScalarRange());
                lastDataSet = vtkDataSet;
            }
        }

        // Set the default actor visible by default
        // if ((nbUpdate++ == 0) &&
        // pipelineManager.getActorList().getData().contains(actor)) {
        if (pipelineManager.getActorList().getData().contains(outputCassActor)) {
            pipelineManager.setActorVisible(outputCassActor, true);
        }
        // and we hide the actor corresponding to the source
        pipelineManager.hideInputActor(outputCassActor, vtkDataSet);

        // registration of internal elements of the filter
        // useful for export of the detailed pipeline if needed
        filterImpl.getInputDataSet().clear();
        filterImpl.getInputDataSet().add(vtkDataSet);
        filterImpl.getOutputDataSet().clear();
        filterImpl.getOutputDataSet().add(planeClip.GetOutput());
        filterImpl.getAlgorithm().add(planeClip);
        filterImpl.getVtkImplicitFunction().clear();
        filterImpl.getVtkImplicitFunction().add(sourcePlane);
        pipelineManager.notifyConnectivityChange(cassfilter);
    }

    /**
     * Default plugin interface
     */
    public String getName() {
        return name;
    }

    public JPopupMenu getContextualMenu() {
        return contextualMenu;
    }

    /**
     * remove the VtkObjects related to this plug-in instance from the pipeline
     * manager.
     */
    public void remove() {
        pluginManager.removePlugin(this);
        pipelineManager.removeVtkObject(outputCassActor);
        pipelineManager.removeVtkObject(outputCassMapper);
        pipelineManager.removeVtkObject(outputCassDataset);
        pipelineManager.removeVtkObject(cassfilter);
    }

    /**
     * String printed in the plug-in manager list
     */
    public String toString() {
        return getName();
    }

    /**
     * Show/hide (and switch On/Off) the 3D widget interactor.
     * 
     * @param widgetFlag
     */
	public void show3DWidget(boolean widgetFlag) {
        if (widgetFlag) {
            if (planWidget == null) {
                init3DWidget();
            }
            planWidget.UpdatePlacement();
            planWidget.On();
        } else {
            if (planWidget != null) {
                planWidget.Off();
            }
        }
    }

    /**
     * Initialize the 3D control widget.
     */
    private void init3DWidget() {

        planWidget = new vtkImplicitPlaneWidget();
        //pipelineManager.getCassandraView().getRenderWindowInteractor().
        planWidget.SetInteractor(pipelineManager.getCassandraView().getRenderWindowInteractor());

        //pipelineManager.getCassandraView().getRenderWindowInteractor().Register(planWidget);
        
        // to make the interaction ON and be able to control the widget
        planWidget.EnabledOn();
        
        //pipelineManager.getCassandraView().deepValidateView(); // Don t ask me
        // why, apparently needed, at least under MacOSX and
        // default swing LAF, to activate the widget interactor.

        //pipelineManager.getCassandraView().validateViewAndGo();
        
        
        //pipelineManager.getCassandraView().Render();
        //pipelineManager.getCassandraView().invalidate();
        //pipelineManager.getCassandraView().validate();
        //pipelineManager.getCassandraView().repaint();
        
        
        
        // size the plan on the input data set. Should be done in first
        // to allow the plan orientation AFTER.
        planWidget.SetInput(lastDataSet);
        planWidget.SetPlaceFactor(1.2);
        planWidget.PlaceWidget();
        // then we orient the plan in the cutting plane.
        planWidget.SetNormal(sourcePlane.GetNormal());

        // just to see something and not only the widget.
        planWidget.GetPlaneProperty().SetOpacity(0.3);

        // we add the local observer for interaction events, i.e the method name
        // in last argument of the current object.
        planWidget.AddObserver("InteractionEvent", this, "interactionCallBack");

        // both not needed.
        // planWidget.AddObserver("CharEvent", this, "widgetCharEvent");
        // planWidget.AddObserver("StartInteractionEvent", this,
        // "startInteractionCallBack");

    }

    private void widgetCharEvent() {
        char code = Character.toLowerCase(pipelineManager.getCassandraView().getRenderWindowInteractor().GetKeyCode());
        System.out.println("touch typed:" + code);
        if (code == 'b') { // FIXME
            planWidget.SetEnabled(1);
        }
    }

    // needed to initialise the interaction control on the widget.
    private void startInteractionCallBack() {
        System.out.println("wiget interaction started");
    }

    /**
     * Handle the 3D call-backs for the 3D widget.
     */
    private void interactionCallBack() {
        sourcePlane.SetNormal(planWidget.GetNormal());
        controlUI.setNormalFieldsValues(planWidget.GetNormal());
        controlUI.setOrigineFieldsValues(planWidget.GetOrigin());
        sourcePlane.SetOrigin(planWidget.GetOrigin()[0], planWidget.GetOrigin()[1], planWidget.GetOrigin()[2]);
    }

    /**
     * Get the Cassandra filter it-self.
     */
    public Filter getInternalFilter() {
        return filterImpl;
    }

    /**
     * return the control panel of the current plug-in.
     */
    @Override
    public PlugInControlUI getControlUI() {
        return controlUI;
    }

    /**
     * update of the current clipping using a
     * CassandraPlugInProperty. This update the setting of the plug-in and call
     * the updateClippingPlane method.
     */
    @Override
    public void initAndUpdate(CassandraPlugInProperty prop) {        
        double cx = 0;
        double cy = 0;
        double cz = 0;

        double nx = 0;
        double ny = 0;
        double nz = 0;

        vtkDataSet tmpInputDataSet;

        boolean inOut = false;
        
        int cellPosX;
        int cellPosY;
        
        cx = prop.getSafely(PERSISTENCE_KEY_CX, 0.0);
        cy = prop.getSafely(PERSISTENCE_KEY_CY, 0.0);
        cz = prop.getSafely(PERSISTENCE_KEY_CZ, 0.0);

        nx = prop.getSafely(PERSISTENCE_KEY_NX, 1.0);
        ny = prop.getSafely(PERSISTENCE_KEY_NY, 0.0);
        nz = prop.getSafely(PERSISTENCE_KEY_NZ, 0.0);
        
        this.planeClip = new vtkClipDataSet();
        this.planeClip.SetOutput((vtkDataObject) prop.getSafely(PERSISTENCE_KEY_OUPUT_DATASET, null)); // FIXME
        inOut = (prop.getSafely(PERSISTENCE_KEY_INOUT, 1) == 1);
        tmpInputDataSet = (vtkDataSet) prop.getSafely(PERSISTENCE_KEY_INPUT_DATASET, (vtkDataSet) (planeClip.GetInput()));

        outputLookupTable = (vtkLookupTable) prop.getSafely(PERSISTENCE_KEY_OUPUT_LOOKUP_TABLE, null); // FIXME
        outputMapper = (vtkDataSetMapper) prop.getSafely(PERSISTENCE_KEY_OUPUT_MAPPER, null); // FIXME
        outputActor = (vtkActor) prop.getSafely(PERSISTENCE_KEY_OUTPUT_ACTOR, null); // FIXME
        outputScalarBar = (vtkScalarBarActor) prop.getSafely(PERSISTENCE_KEY_OUTPUT_SCALAR_BAR, null); // FIXME

        this.initPipeline();
        controlUI.setNormalFieldsValues(nx, ny, nz);
        controlUI.setOrigineFieldsValues(cx, cy, cz);
        controlUI.setSelectedInput(pipelineManager.getCassandraVtkObjectByTrueVtkObject(tmpInputDataSet)); //FIXME: nasty
        this.updateClippingPlane(cx, cy, cy, nx, ny, cy, tmpInputDataSet, inOut);
        cellPosX = prop.getSafely(PERSISTENCE_KEY_CELL_POS_X, -1);
        cellPosY = prop.getSafely(PERSISTENCE_KEY_CELL_POS_Y, -1);
        if (cellPosX > 0 && cellPosY > 0) ((VtkObjectCellAdapter) cassfilter.getMetaData().get(CassandraObject.CELL)).setPosition(new Point(cellPosX, cellPosY));
    }

    /**
     * return the properties of the current plug-in
     */
    @Override
    public CassandraPlugInProperty getPlugInProperty() {
        
        //FIXME : should be moved / handle by the event lsitener of the pipeline. Not very clean.
        outputCassLookupTable = pipelineManager.getCassandraVtkObjectByTrueVtkObject((vtkLookupTable) outputMapper.GetLookupTable());
        outputLookupTable = (vtkLookupTable) outputCassLookupTable.getVtkObject();
        outputCassScalarBar = pipelineManager.getVtkObject(outputCassLookupTable.getOutputConnectivityList().get(0));
        outputScalarBar = (vtkScalarBarActor) outputCassScalarBar.getVtkObject();
        //

        SimplePlugInProperty prop = new SimplePlugInProperty();

        prop.put(PERSISTENCE_KEY_CX, new Double(sourcePlane.GetOrigin()[0]));
        prop.put(PERSISTENCE_KEY_CY, new Double(sourcePlane.GetOrigin()[1]));
        prop.put(PERSISTENCE_KEY_CZ, new Double(sourcePlane.GetOrigin()[2]));

        prop.put(PERSISTENCE_KEY_NX, new Double(sourcePlane.GetNormal()[0]));
        prop.put(PERSISTENCE_KEY_NY, new Double(sourcePlane.GetNormal()[1]));
        prop.put(PERSISTENCE_KEY_NZ, new Double(sourcePlane.GetNormal()[2]));

        prop.put(PERSISTENCE_KEY_INPUT_DATASET, planeClip.GetInput());
        
        prop.put(PERSISTENCE_KEY_INOUT, planeClip.GetInsideOut());
        prop.put(PERSISTENCE_KEY_OUPUT_DATASET, planeClip.GetOutput());
        prop.put(PERSISTENCE_KEY_OUPUT_MAPPER, outputMapper);        
        prop.put(PERSISTENCE_KEY_OUPUT_LOOKUP_TABLE, outputCassLookupTable.getVtkObject());
        prop.put(PERSISTENCE_KEY_OUTPUT_ACTOR, outputCassActor.getVtkObject());
        prop.put(PERSISTENCE_KEY_OUTPUT_SCALAR_BAR, outputCassScalarBar.getVtkObject());
        
        Point cellPosition = ((VtkObjectCellAdapter) cassfilter.getMetaData().get(CassandraObject.CELL)).getPosition();
        prop.put(PERSISTENCE_KEY_CELL_POS_X, cellPosition.x);
        prop.put(PERSISTENCE_KEY_CELL_POS_Y, cellPosition.y);

        return prop;
    }



}
