/*
 * (c) Copyright: Artenum SARL, 101-103 Boulevard Mac Donald,
 *                75019, Paris, France 2005.
 *                http://www.artenum.com
 *
 * License:
 *
 *  This program is free software; you can redistribute it
 *  and/or modify it under the terms of the Q Public License;
 *  either version 1 of the License.
 *
 *  This program is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE. See the Q Public License for more details.
 *
 *  You should have received a copy of the Q Public License
 *  License along with this program;
 *  if not, write to:
 *    Artenum SARL, 101-103 Boulevard Mac Donald,
 *    75019, PARIS, FRANCE, e-mail: contact@artenum.com
 */
package com.artenum.cassandra.plugin.cutting;

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.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.vtkCutter;
import vtk.vtkDataObject;
import vtk.vtkDataSet;
import vtk.vtkDataSetMapper;
import vtk.vtkImplicitPlaneWidget;
import vtk.vtkInteractorStyleSwitch;
import vtk.vtkLookupTable;
import vtk.vtkPlane;
import vtk.vtkPolyDataMapper;
import vtk.vtkScalarBarActor;
import vtk.vtkTransform;

import java.awt.Frame;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

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, Julien Forest, Benoit Thiebault, Jeremie Turbet
 * @version 2.0
 */
public class CuttingPlanePlugin 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 CuttingPlaneControlUI controlUI;

    // Vtk data
    private vtkDataSet lastDataSet;
    private vtkLookupTable lookupTable;
    private vtkScalarBarActor scalarBar;
    private vtkPlane sourcePlane;
    private vtkCutter planeCut;
    private vtkPolyDataMapper cutMapper;
    private vtkActor cutActor;
    private vtkScalarBarActor scalBar;
    private Filter filterImpl;

    // Pipeline VtkObjects
    private CassandraObject outputCassActor;
    private CassandraObject outputCassMapper;
    private CassandraObject outputCassDataset;
    private CassandraObject outputCassLookupTable;
    private CassandraObject outputCassScalarBar;
    private CassandraObject cassFilter;

    private vtkImplicitPlaneWidget planWidget;

    private Point cellPosition;

    // Persistence keys

    /** key of the input data set in the property map */
    public static final String PERSISTENCE_KEY_INPUT_DATASET = "persistence.key.inputVtkDataSet";
    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";
    /** key of the output data set in the property map */
    public static final String PERSISTENCE_KEY_OUPUT_DATASET = "persistence.key.outputDataSet";
    /** key of the output mapper in the property map */
    public static final String PERSISTENCE_KEY_OUPUT_MAPPER = "persistence.key.outputMapper";
    /** key of the output lookupt table in the property map */
    public static final String PERSISTENCE_KEY_OUPUT_LOOKUP_TABLE = "persistence.key.outputlookUpTable";
    /** key of the output actor in the property map */
    public static final String PERSISTENCE_KEY_OUTPUT_ACTOR = "persistence.key.outputActor";
    /** key of the output scalar bar set in the property map */
    public static final String PERSISTENCE_KEY_OUTPUT_SCALAR_BAR = "persistence.key.ouputScalarBar";
    /** key of the cell x-position in the pipeline editor in the property map */
    public static final String PERSISTENCE_KEY_CELL_POS_X = "persistence.key.cell.PosX";
    /** key of the cell y-position in the pipeline editor in the property map */
    public static final String PERSISTENCE_KEY_CELL_POS_Y = "persistence.key.cell.PosY";

    /**
     * Main contructor 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 CuttingPlanePlugin(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 CuttingPlanePlugin(PipeLineManager pipelineManager, PluginManager pluginManager, Frame owner, boolean initPipeline) {
        initPlugIn(pipelineManager, pluginManager, owner);
        if (initPipeline) {
            initPipeline();
        }
    }

    public void initPlugIn(PipeLineManager pipelineManager, PluginManager pluginManager, Frame owner) {
        // Init default variable
        this.name = "Cutting plane";
        this.pipelineManager = pipelineManager;
        this.pluginManager = pluginManager;
        
        // Cassandra filter 
        this.filterImpl = new FilterImpl();
        filterImpl.addRemoveListener(this);
        filterImpl.setParentPlugin(this);

        // Init all the control UI
        controlUI = new CuttingPlaneControlUI(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 (planeCut == null) {
            planeCut = new vtkCutter();
            this.outputCassDataset = pipelineManager.addDataSet(planeCut.GetOutput(), "Cutting plane");
        } else
            outputCassDataset = pipelineManager.getCassandraVtkObjectByTrueVtkObject(planeCut);
        planeCut.SetCutFunction(sourcePlane);

        if (cutMapper == null) {
            cutMapper = new vtkPolyDataMapper();
            outputCassMapper = pipelineManager.addMapper(cutMapper, "Cutting plane");
        } else
            outputCassMapper = pipelineManager.getCassandraVtkObjectByTrueVtkObject(cutMapper);
        cutMapper.SetInput(planeCut.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 (cutActor == null) {
            cutActor = new vtkActor();
            outputCassActor = pipelineManager.addActor(cutActor, "Cutting plane");
        } else outputCassActor = pipelineManager.getCassandraVtkObjectByTrueVtkObject(cutActor);
        cutActor.SetMapper(cutMapper);

        // Build lookupTable
        if (lookupTable == null) {
            lookupTable = new vtkLookupTable();
            outputCassLookupTable = pipelineManager.addLookupTable(lookupTable, "Clipping plane");
        } else outputCassLookupTable = pipelineManager.getCassandraVtkObjectByTrueVtkObject(lookupTable);
        lookupTable.SetHueRange(0.66667, 0);
        lookupTable.Build();
        cutMapper.SetLookupTable(lookupTable);

        // Scalar bar
        if (scalBar == null) {
            scalBar = new vtkScalarBarActor();
            outputCassScalarBar = pipelineManager.addScalarBar(scalBar, "Clipping plane");
        } else outputCassScalarBar = pipelineManager.getCassandraVtkObjectByTrueVtkObject(scalBar);
        scalBar.SetLookupTable(lookupTable);

        // Load the result data in the Cassandra pipeline
        cassFilter = pipelineManager.addFilter(filterImpl, "Cutting plane");
        
        //FIXME in the plug in UI? TBC
        //pipelineManager.setActorVisible(pipelineManager.addScalarBar(scalBar, "Cutting plane"), true);

        //
        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());
        }

        //FIXME in the plug in UI? TBC
        // to see directly the control panel when we instantiate the plugin
        //controlUI.setVisible(true);
    }

    /**
     * Update the data in the defined pipeline
     * 
     * @param cx
     * @param cy
     * @param cz
     * @param nx
     * @param ny
     * @param nz
     * @param vtkDataSetIn
     */
    public void updateCuttingPlane(double cx, double cy, double cz, double nx, double ny, double nz, vtkDataSet vtkDataSetIn) {
        if (vtkDataSetIn == null) {
            return;
        }
        
        //FIXME : should be moved / handle by the evente lsitener of the pipeline. Not very clean.
        outputCassLookupTable = pipelineManager.getCassandraVtkObjectByTrueVtkObject((vtkLookupTable) cutMapper.GetLookupTable());
        lookupTable = (vtkLookupTable) outputCassLookupTable.getVtkObject();
        outputCassScalarBar = pipelineManager.getVtkObject(outputCassLookupTable.getOutputConnectivityList().get(0));
        scalarBar = (vtkScalarBarActor) outputCassScalarBar.getVtkObject();
        //

        outputCassActor.setValide(true);

        //
        sourcePlane.SetOrigin(cx, cy, cz);
        sourcePlane.SetNormal(nx, ny, nz);
        planeCut.SetInput(vtkDataSetIn);
        if (vtkDataSetIn.GetPointData().GetScalars() != null || vtkDataSetIn.GetCellData().GetScalars() != null) {
            if (((lastDataSet == null) || !lastDataSet.equals(vtkDataSetIn)) && cutMapper.GetLookupTable().equals(lookupTable)) {
                cutMapper.SetScalarRange(vtkDataSetIn.GetScalarRange());
                lookupTable.SetTableRange(vtkDataSetIn.GetScalarRange());
                lastDataSet = vtkDataSetIn;
            }
        }

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

        // Graph
        filterImpl.getInputDataSet().clear();
        filterImpl.getInputDataSet().add(vtkDataSetIn);
        filterImpl.getOutputDataSet().clear();
        filterImpl.getOutputDataSet().add(planeCut.GetOutput());
        filterImpl.getAlgorithm().add(sourcePlane);
        filterImpl.getVtkImplicitFunction().clear();
        filterImpl.getVtkImplicitFunction().add(sourcePlane);
        pipelineManager.notifyConnectivityChange(cassFilter);

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

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

    public JPopupMenu getContextualMenu() {
        return contextualMenu;
    }

    public void remove() {
        pluginManager.removePlugin(this);
        pipelineManager.removeVtkObject(outputCassActor);
        pipelineManager.removeVtkObject(outputCassMapper);
        pipelineManager.removeVtkObject(outputCassDataset);
        pipelineManager.removeVtkObject(cassFilter);
        planWidget.Off();
    }

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

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

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

        // System.out.println("initialisation of the widget");
        planWidget = new vtkImplicitPlaneWidget();
        planWidget.SetInteractor(pipelineManager.getCassandraView().getIren());

        // to make the interaction ON and be able yto 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.

        // 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());
        System.out.println(planWidget.GetNormal()[0] + " " + planWidget.GetNormal()[1] + " " + planWidget.GetNormal()[2]);

        // 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().getIren().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");
    }

    /**
    *  
    */
    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]);
    }

    public Filter getInternalFilter() {
        return filterImpl;
    }

    /**
     * return the properties of the current plug-in
     */
    @Override
    public CassandraPlugInProperty getPlugInProperty() {
        //FIXME : should be moved / handle by the evente lsitener of the pipeline. Not very clean.
        outputCassLookupTable = pipelineManager.getCassandraVtkObjectByTrueVtkObject((vtkLookupTable) cutMapper.GetLookupTable());
        lookupTable = (vtkLookupTable) outputCassLookupTable.getVtkObject();
        outputCassScalarBar = pipelineManager.getVtkObject(outputCassLookupTable.getOutputConnectivityList().get(0));
        scalarBar = (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, planeCut.GetInput());
        
        prop.put(PERSISTENCE_KEY_OUPUT_DATASET, planeCut.GetOutput());
        prop.put(PERSISTENCE_KEY_OUPUT_MAPPER, cutMapper);        
        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;
    }

    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;
        
        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.planeCut = new vtkCutter();
        this.planeCut.SetOutput((vtkDataObject) prop.getSafely(PERSISTENCE_KEY_OUPUT_DATASET, null)); // FIXME
        tmpInputDataSet = (vtkDataSet) prop.getSafely(PERSISTENCE_KEY_INPUT_DATASET, (vtkDataSet) (planeCut.GetInput()));

        lookupTable = (vtkLookupTable) prop.getSafely(PERSISTENCE_KEY_OUPUT_LOOKUP_TABLE, null); // FIXME
        cutMapper = (vtkPolyDataMapper) prop.getSafely(PERSISTENCE_KEY_OUPUT_MAPPER, null); // FIXME
        cutActor = (vtkActor) prop.getSafely(PERSISTENCE_KEY_OUTPUT_ACTOR, null); // FIXME
        scalarBar = (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.updateCuttingPlane(cx, cy, cy, nx, ny, cy, tmpInputDataSet);
        
        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));
    }

}
