/*
 * (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.isolevel;

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;

import vtk.vtkActor;
import vtk.vtkAssembly;
import vtk.vtkContourFilter;
import vtk.vtkCutter;
import vtk.vtkDataObject;
import vtk.vtkDataSet;
import vtk.vtkDataSetMapper;
import vtk.vtkLookupTable;
import vtk.vtkPlane;
import vtk.vtkPolyDataMapper;
import vtk.vtkPolyDataNormals;
import vtk.vtkScalarBarActor;

import com.artenum.cassandra.pipeline.CassandraObject;
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.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;

/**
 * <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 IsoLevelPlugin implements CassandraPlugin, RemoveListener {
    private String name;
    private PipeLineManager pipelineManager;
    private PluginManager pluginManager;
    private JPopupMenu contextualMenu;
    private IsoLevelControlUI controlUI;
    private int nbUpdate = 0;

    // Vtk data
    private vtkDataSet lastDataSet;
    private vtkLookupTable lookupTable;
    private vtkScalarBarActor scalarBar;
    private vtkPlane plane;
    private vtkCutter planeCut;
    private vtkPolyDataMapper isoLevelMapper;
    private vtkActor isoLevelActor;
    private vtkContourFilter contour;
    private vtkPolyDataNormals normals;
    private vtkAssembly assembly;

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

    private boolean normalFlag;

    private Point cellPosition;

    private double minValue;
    private double maxValue;

    // private boolean loadOutputPipelineComponents = true;

    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";
    public static final String PERSISTENCE_KEY_LEVEL_VALUE = "persistence.key.level.value";
    public static final String PERSISTENCE_KEY_LEVEL_FLAG = "persistence.key.level.flag";
    public static final String PERSISTENCE_KEY_LEVEL_NUMBER = "persistence.key.level.number";
    public static final String PERSISTENCE_KEY_LEVEL_MIN = "persistence.key.level.min";
    public static final String PERSISTENCE_KEY_LEVEL_MAX = "persistence.key.level.max";

    public IsoLevelPlugin(final PipeLineManager pipelineManager, final PluginManager pluginManager, final Frame owner) {
        initPlugIn(pipelineManager, pluginManager, owner);
        initPipeline();
    }

    public IsoLevelPlugin(final PipeLineManager pipelineManager, final PluginManager pluginManager, final Frame owner,
            final boolean initPipeline) {
        initPlugIn(pipelineManager, pluginManager, owner);
        if (initPipeline) {
            initPipeline();
        }
    }

    public IsoLevelPlugin(final PipeLineManager pipelineManager, final PluginManager pluginManager, final Frame owner,
            final int initPipeline) {
        initPlugIn(pipelineManager, pluginManager, owner);
        if (initPipeline == 1) {
            initPipeline();
        }
    }

    public void initPlugIn(final PipeLineManager pipelineManager, final PluginManager pluginManager, final Frame owner) {

        this.name = "Iso level";

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

        this.pipelineManager = pipelineManager;
        this.pluginManager = pluginManager;

        // Init control UI
        this.controlUI = new IsoLevelControlUI(pipelineManager, this, owner);

        // Init contextual menu
        this.contextualMenu = new JPopupMenu("Iso level menu");
        final JMenuItem showControl = new JMenuItem("Show control");
        showControl.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                IsoLevelPlugin.this.controlUI.setVisible(true);
            }
        });
        this.contextualMenu.add(showControl);
        this.contextualMenu.addSeparator();
        final JMenuItem remove = new JMenuItem("Remove");
        remove.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                remove();
            }
        });
        this.contextualMenu.add(remove);
    }

    /**
     * intialise 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() {
        // Here write your processing pipeline

        if (this.contour == null) {
            this.contour = new vtkContourFilter();
            this.outputCassDataset = this.pipelineManager.addDataSet(this.contour.GetOutput(), "Contour");
        } else {
            this.outputCassDataset = this.pipelineManager
                    .getCassandraVtkObjectByTrueVtkObject(this.contour.GetOutput());
        }
        this.contour.SetValue(0, 0.1); // to have a default value

        this.normals = new vtkPolyDataNormals();
        this.normals.SetInput(this.contour.GetOutput());
        this.normals.SetFeatureAngle(45);

        if (this.isoLevelMapper == null) {
            this.isoLevelMapper = new vtkPolyDataMapper();
            this.outputCassMapper = this.pipelineManager.addMapper(this.isoLevelMapper, "IsoLevel");
        } else {
            this.outputCassMapper = this.pipelineManager.getCassandraVtkObjectByTrueVtkObject(this.isoLevelMapper);
        }

        // to see something by default
        this.isoLevelMapper.ScalarVisibilityOn();

        // and something colored
        this.isoLevelMapper.ColorByArrayComponent("Magnitude", 0);

        if (this.isoLevelActor == null) {
            this.isoLevelActor = new vtkActor();
            this.outputCassActor = this.pipelineManager.addActor(this.isoLevelActor, "IsoLevel");
        } else {
            this.outputCassActor = this.pipelineManager.getCassandraVtkObjectByTrueVtkObject(this.isoLevelActor);
        }

        this.isoLevelActor.SetMapper(this.isoLevelMapper);

        //
        this.filterImpl.getOutputDataSet().add(this.contour.GetOutput());
        this.filterImpl.getOutputDataSet().add(this.normals.GetOutput());
        this.filterImpl.addRemoveListener(this);

        // Build lookupTable
        if (this.lookupTable == null) {
            this.lookupTable = new vtkLookupTable();
            this.outputCassLookupTable = this.pipelineManager.addLookupTable(this.lookupTable, "IsoLevel");
        } else {
            this.outputCassLookupTable = this.pipelineManager.getCassandraVtkObjectByTrueVtkObject(this.lookupTable);
        }

        this.lookupTable.SetHueRange(0.66667, 0);
        this.lookupTable.Build();
        this.isoLevelMapper.SetLookupTable(this.lookupTable);

        // Scalar bar
        if (this.scalarBar == null) {
            this.scalarBar = new vtkScalarBarActor();
            this.outputCassScalarBar = this.pipelineManager.addScalarBar(this.scalarBar, "Iso Level");
        } else {
            this.outputCassScalarBar = this.pipelineManager.getCassandraVtkObjectByTrueVtkObject(this.scalarBar);
        }

        this.scalarBar.SetLookupTable(this.lookupTable);
        this.pipelineManager.setActorVisible(this.outputCassScalarBar, true);

        if (this.normals.GetOutput() == null) {
            this.outputCassDatasetN = this.pipelineManager.addDataSet(this.normals.GetOutput(), "Normal");
        } else {
            this.outputCassDatasetN = this.pipelineManager.getCassandraVtkObjectByTrueVtkObject(this.normals
                    .GetOutput());
        }

        //
        this.cassFilter = this.pipelineManager.addFilter(this.filterImpl, "IsoLevel");
        this.cassFilter.getMetaData().put(CassandraObject.POPUP_MENU, getContextualMenu());

        //
        this.outputCassActor.setValide(false);
    }

    /**
     * Set the internal component from an external pipeline like in the Jython script reloading procedure.
     * 
     */
    @Deprecated
    public void setInternalPipelineComponents(final vtkContourFilter contour, final vtkDataSetMapper cutMapper,
            final vtkActor cutActor) {
        this.contour = contour;
    }

    /**
     * Update the setting of the current plug-in / processing filter.
     * 
     * @param vtkDataSet
     *            set the input dataset to be processed.
     * @param level
     *            set the value of the level if levelFlag equals 0.
     * @param nbLevel
     *            set the number of levels if levelFlag equals 1.
     * @param levelFlag
     *            set if only one (if equal to 0) or several levels (if equal to 1) are defined. If only one level is
     *            selected the corresponding value is defined by the level parameter. Otherwise, the values are
     *            uniformly distributed in nbLevel between the min and max values.
     * @param min
     *            set the minimum value of levels if levelFlag equals 1.
     * @param max
     *            set the maxima value of levels if levelFlag equals 1.
     */
    public void updateIsoLevel(final vtkDataSet vtkDataSet, final double level, final int nbLevel, final int levelFlag,
            final double min, final double max) {
        if (vtkDataSet == null) {
            System.out.println("Error input data set null");
            return;
        }

        this.minValue = min;
        this.maxValue = max;

        // FIXME : should be moved / handle by the evente lsitener of the pipeline. Not very clean.
        this.outputCassLookupTable = this.pipelineManager.getCassandraVtkObjectByTrueVtkObject(this.isoLevelMapper
                .GetLookupTable());
        this.lookupTable = (vtkLookupTable) this.outputCassLookupTable.getVtkObject();
        this.outputCassScalarBar = this.pipelineManager.getVtkObject(this.outputCassLookupTable
                .getOutputConnectivityList().get(0));
        this.scalarBar = (vtkScalarBarActor) this.outputCassScalarBar.getVtkObject();
        //

        this.filterImpl.getInputDataSet().clear();
        this.filterImpl.getInputDataSet().add(vtkDataSet);
        this.pipelineManager.notifyConnectivityChange(this.outputCassDataset);
        this.cellPosition = ((VtkObjectCellAdapter) this.cassFilter.getMetaData().get(CassandraObject.CELL))
                .getPosition();

        this.outputCassActor.setValide(true);
        this.contour.SetInput(vtkDataSet);
        setNormal(this.normalFlag); // normalFlag);
        this.contour.ComputeScalarsOn();
        this.isoLevelMapper.ColorByArrayComponent("Magnitude", 0);

        if (levelFlag == 0) {
            this.contour.SetNumberOfContours(1);
            this.contour.SetValue(0, level);
        } else {
            this.contour.GenerateValues(nbLevel, min, max);
        }

        /*
         * if (vtkDataSet.GetPointData().GetScalars() != null) { isoLevelMapper.SetScalarRange
         * (vtkDataSet.GetPointData().GetScalars().GetRange()); }
         */
        if (((this.lastDataSet == null) || !this.lastDataSet.equals(vtkDataSet))
                && this.isoLevelMapper.GetLookupTable().equals(this.lookupTable)) {
            this.isoLevelMapper.SetScalarRange(vtkDataSet.GetScalarRange());
            this.lookupTable.SetTableRange(vtkDataSet.GetScalarRange());
            this.lastDataSet = vtkDataSet;
        }

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

    public void setControls(final boolean normalFlagIn, final boolean scalarFlag) {
        this.normalFlag = normalFlagIn;
    }

    private void setNormal(final boolean normalFlag) {
        if (normalFlag == true) {
            // FIXME test a revoir
            if (this.normals.GetOutput() != null) {
                this.isoLevelMapper.SetInput(this.normals.GetOutput());
            } else {
                System.out.println("No data to generate normals for!");
                this.isoLevelMapper.SetInput(this.contour.GetOutput());
            }
        } else {
            this.isoLevelMapper.SetInput(this.contour.GetOutput());
        }
    }

    @Override
    public String getName() {
        return this.name;
    }

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

    @Override
    public JPopupMenu getContextualMenu() {
        return this.contextualMenu;
    }

    @Override
    public String toString() {
        return getName();
    }

    public CassandraObject getOutputDataSet() {
        return this.outputCassDataset;
    }

    public CassandraObject getActor() {
        return this.outputCassActor;
    }

    @Override
    public Filter getInternalFilter() {
        return this.filterImpl;
    }

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

    @Override
    public CassandraPlugInProperty getPlugInProperty() {

        // FIXME : should be moved / handle by the evente lsitener of the pipeline. Not very clean.
        this.outputCassLookupTable = this.pipelineManager.getCassandraVtkObjectByTrueVtkObject(this.isoLevelMapper
                .GetLookupTable());
        this.lookupTable = (vtkLookupTable) this.outputCassLookupTable.getVtkObject();
        this.outputCassScalarBar = this.pipelineManager.getVtkObject(this.outputCassLookupTable
                .getOutputConnectivityList().get(0));
        this.scalarBar = (vtkScalarBarActor) this.outputCassScalarBar.getVtkObject();
        //

        final SimplePlugInProperty prop = new SimplePlugInProperty();

        prop.put(PERSISTENCE_KEY_INPUT_DATASET, this.contour.GetInput());
        prop.put(PERSISTENCE_KEY_OUPUT_DATASET, this.contour.GetOutput());
        prop.put(PERSISTENCE_KEY_OUPUT_MAPPER, this.isoLevelMapper);
        prop.put(PERSISTENCE_KEY_OUPUT_LOOKUP_TABLE, this.lookupTable);
        prop.put(PERSISTENCE_KEY_OUTPUT_ACTOR, this.isoLevelActor);
        prop.put(PERSISTENCE_KEY_OUTPUT_SCALAR_BAR, this.scalarBar);

        this.cellPosition = ((VtkObjectCellAdapter) this.cassFilter.getMetaData().get(CassandraObject.CELL))
                .getPosition();
        prop.put(PERSISTENCE_KEY_CELL_POS_X, this.cellPosition.x);
        prop.put(PERSISTENCE_KEY_CELL_POS_Y, this.cellPosition.y);

        if (this.contour.GetNumberOfContours() > 1) {
            prop.put(PERSISTENCE_KEY_LEVEL_FLAG, 1);
            prop.put(PERSISTENCE_KEY_LEVEL_NUMBER, this.contour.GetNumberOfContours());
        } else {
            prop.put(PERSISTENCE_KEY_LEVEL_FLAG, 0);
            prop.put(PERSISTENCE_KEY_LEVEL_NUMBER, 1);
        }
        prop.put(PERSISTENCE_KEY_LEVEL_MIN, this.minValue);
        prop.put(PERSISTENCE_KEY_LEVEL_MAX, this.maxValue);

        return prop;
    }

    @Override
    public void initAndUpdate(final CassandraPlugInProperty prop) {

        vtkDataSet tmpInputDataSet;

        int cellPosX;
        int cellPosY;

        int localLevelFlag;
        int localNbLevel;
        int localLevel;

        this.contour = new vtkContourFilter();
        this.contour.SetOutput((vtkDataObject) prop.getSafely(PERSISTENCE_KEY_OUPUT_DATASET, null)); // FIXME
        tmpInputDataSet = (vtkDataSet) prop.getSafely(PERSISTENCE_KEY_INPUT_DATASET, (this.contour.GetInput()));

        this.lookupTable = (vtkLookupTable) prop.getSafely(PERSISTENCE_KEY_OUPUT_LOOKUP_TABLE, null); // FIXME
        this.isoLevelMapper = (vtkPolyDataMapper) prop.getSafely(PERSISTENCE_KEY_OUPUT_MAPPER, null); // FIXME
        this.isoLevelActor = (vtkActor) prop.getSafely(PERSISTENCE_KEY_OUTPUT_ACTOR, null); // FIXME
        this.scalarBar = (vtkScalarBarActor) prop.getSafely(PERSISTENCE_KEY_OUTPUT_SCALAR_BAR, null); // FIXME

        this.minValue = prop.getSafely(PERSISTENCE_KEY_LEVEL_MIN, -10.0);
        this.maxValue = prop.getSafely(PERSISTENCE_KEY_LEVEL_MIN, 10.0);

        localLevelFlag = prop.getSafely(PERSISTENCE_KEY_LEVEL_FLAG, 0);
        localNbLevel = prop.getSafely(PERSISTENCE_KEY_LEVEL_NUMBER, 1);
        localLevel = prop.getSafely(PERSISTENCE_KEY_LEVEL_VALUE, 1);

        // because their are already loaded now
        // loadOutputPipelineComponents = false;

        this.initPipeline();
        this.controlUI.setSelectedInput(this.pipelineManager.getCassandraVtkObjectByTrueVtkObject(tmpInputDataSet)); // FIXME:
        // nasty
        this.controlUI.setLevelType(localLevelFlag);
        this.updateIsoLevel(tmpInputDataSet, localLevel, localNbLevel, localLevelFlag, this.minValue, this.maxValue);

        cellPosX = prop.getSafely(PERSISTENCE_KEY_CELL_POS_X, -1);
        cellPosY = prop.getSafely(PERSISTENCE_KEY_CELL_POS_Y, -1);
        if (cellPosX > 0 && cellPosY > 0) {
            ((VtkObjectCellAdapter) this.cassFilter.getMetaData().get(CassandraObject.CELL)).setPosition(new Point(
                    cellPosX, cellPosY));
        }

    }
}
