/*
 * Project: Cassandra
 * 
 * (c) Copyright: Artenum SARL, 24 rue Louis Blanc, 
 *                75010, 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, 24 rue Louis Blanc,
 *    75010, PARIS, FRANCE, e-mail: contact@artenum.com
 */
package com.artenum.cassandra.pipeline.io;

import java.awt.Point;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import vtk.vtkActor;
import vtk.vtkDataObject;
import vtk.vtkLookupTable;
import vtk.vtkMapper;
import vtk.vtkObject;
import vtk.vtkScalarBarActor;

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

/**
 * This class export the Cassandra's pipeline into an equivalent Python script,
 * directly loadable into the Cassandra/Kerdiwen JyConsole base system.
 * 
 * @author Julien Forest, Artenum SARL
 * 
 * @version 0.9
 * 
 */
public class PipelineToJythonExporter implements PipelineExporter {

    private Hashtable selectedVtkObjectList;
    private ArrayList<String> exportedScript;

    private static String eol = System.getProperty("line.separator");
    private static String scriptTab = "    ";

    private File fileOut;

    private boolean exportHeader = true;
    private boolean exportNodes = true;
    private boolean exportConnectivity = true;
    private boolean cassandraContext = true;
    private boolean keepCassandraPlugIn = true;

    public static final double REF_FILE_FORMAT_VERSION = 1.0;
    
    private String scriptHeader = "# Exported from Cassandra's pipeline (see http://www.artenum.com/cassandra)" + eol;
    private String scriptContextSectionRadical = "# loading/deployment context" + eol;
    private String scriptImportSectionRadical = "# imported modules " + eol;
    private String scriptInstanciationSectionRadical = "# objects instanciation" + eol;
    private String scriptConnectivitySectionRadical = "# pipeline connectivity" + eol;
    private String scriptCassandraReloadingSectionRadical = "# reloading section for Cassandra's pipeline through the JyConsole" + eol
            + "# set CASSANDRA_CONTEXT to 1 for reloading into the reloading into the pipeline active" + eol + "# operationnal in Cassandra only. " + eol
            + "if CASSANDRA_CONTEXT:" + eol + scriptTab + "pipeline  = cassandra.getPipeLineManager()" + eol;
    private String scriptPlugInConnectivitySectionRadical = "# plug-ins connectivity" + eol;

    private String scriptImportSection = scriptImportSectionRadical;
    private String scriptContextSection = scriptContextSectionRadical;
    private String scriptInstanciationSection = scriptInstanciationSectionRadical;
    private String scriptConnectivitySection = scriptConnectivitySectionRadical;
    private String scriptPlugInConnectivitySection = scriptPlugInConnectivitySectionRadical;
    private String scriptCassandraReloadingSection = scriptCassandraReloadingSectionRadical;

    private PipeLineManager pipeline;
    
    public PipelineToJythonExporter() {

    }

    @Override
    public void setOutputFile(File file) {
        fileOut = file;
    }

    @Override
    public void setOutputFile(String canonicalFileName) {
        fileOut = new File(canonicalFileName);
    }

    /**
     * Call the export function and write the file.
     * 
     */
    public void write() {
        export();
        try {
            FileWriter fw = new FileWriter(fileOut, false);

            if (exportHeader) {
                fw.write(scriptHeader);
                fw.write("FILE_FORMAT_VERSION = " + REF_FILE_FORMAT_VERSION +eol);
                fw.write(eol);
                fw.flush();
            }

            if (exportNodes) {
                fw.write(scriptContextSection);
                fw.write(eol);
                fw.write(scriptImportSection);
                fw.write(eol);
                fw.flush();

                fw.write(scriptInstanciationSection);
                fw.write(eol);
                fw.flush();
            }

            if (exportConnectivity) {
                fw.write(scriptConnectivitySection);
                fw.write(eol);
                fw.flush();
            }

            if (cassandraContext) {
                fw.write(scriptCassandraReloadingSection);
                fw.write(eol);
                fw.write(scriptPlugInConnectivitySection);
                fw.flush();
            }
            
            fw.close();
            System.out.println("Pipeline saved.");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setPipeline(PipeLineManager pipeline){
        this.pipeline = pipeline;
    }
    
    private void resetSectionHeaders() {
        scriptImportSection = scriptImportSectionRadical;
        scriptInstanciationSection = scriptInstanciationSectionRadical;
        scriptConnectivitySection = scriptConnectivitySectionRadical;
        scriptCassandraReloadingSection = scriptCassandraReloadingSectionRadical;
    }

    @Override
    public void export() {

        Enumeration keyList;
        Object key;
        CassandraObject obj;
        vtkObject vtkObj;
        //String tmpCanonicalClassName;
        String tmpObjectName;
        String tmpLine = "";
        String tmpImportLine;
        String tmpConnectivityLine = "";
        String tmpPlugInsConnectivityLine = "";
        CassandraPlugin parentPlugIn;
        Class plugInCanonicalClass;

        HashMap<Integer, vtkObject> exportedVtkObjArray = new HashMap<Integer, vtkObject>();
        HashMap<Integer, String> exportedVtkObjCanonicalNameArray = new HashMap<Integer, String>();

        ArrayList<Integer> inputConnectivityList;

        Integer tmpId;
        String tmpPlugInPropertyInstanceName;
        CassandraPlugInProperty plugInProperty;
        HashMap <Object, Object> propertyMap;
        Object keyProp; 
        Object tmpPro;

        exportedScript = new ArrayList<String>();

        // reset the headers
        resetSectionHeaders();

        // check/update the compliance of the export flags
        if (cassandraContext) exportConnectivity = true;
        if (exportConnectivity) exportNodes = true;

        // definition of the deployment context
        scriptContextSection = scriptContextSection + "CASSANDRA_CONTEXT = " + (cassandraContext ? 1 : 0) + eol;

        // declaration, import and instantiation section
        if (exportNodes) {
            keyList = selectedVtkObjectList.keys();
            while (keyList.hasMoreElements()) {
                key = keyList.nextElement();
                obj = (CassandraObject) selectedVtkObjectList.get(key);

                // management of Cassandra plug-ins
                if (obj.getVtkObject() instanceof FilterImpl) {
                    System.out.println("Cassandra filter found");
                    
                    //FIXME mettre un control d'erreur ici. 
                    plugInCanonicalClass = ((FilterImpl) obj.getVtkObject()).getParentPlugin().getClass();

                    
                    if (keepCassandraPlugIn) {
                        System.out.println("Keep Cassandra plugin");

                        tmpImportLine = "if CASSANDRA_CONTEXT:" + eol + scriptTab + "from com.artenum.cassandra.util import CassandraToolBox" + eol + scriptTab
                                + "from " + plugInCanonicalClass.getPackage().getName() + " import " + plugInCanonicalClass.getSimpleName().toString() + eol;
                        scriptImportSection = scriptImportSection + tmpImportLine;

                        tmpObjectName = plugInCanonicalClass.getSimpleName().toString() + "_" + obj.getId();
                        exportedVtkObjCanonicalNameArray.put(obj.getId(), tmpObjectName);

                        tmpLine = "if CASSANDRA_CONTEXT:" + eol + scriptTab + tmpObjectName + " = " + plugInCanonicalClass.getSimpleName().toString()
                                + "(cassandra.getPipeLineManager(), cassandra.getPluginManager(), CassandraToolBox.getParentFrame(cassandra.getDefaultUI()), 0)";
                        scriptInstanciationSection = scriptInstanciationSection + tmpLine + eol;
                    } else {
                        System.out.println("Decompose plug-in's internal components");

                    }

                // management of VTK objects
                } else if (obj.getVtkObject() instanceof vtkObject) {
                    vtkObj = (vtkObject) obj.getVtkObject();

                    tmpImportLine = "from " + vtkObj.getClass().getPackage().getName() + " import " + vtkObj.getClass().getSimpleName().toString() + eol;
                    scriptImportSection = scriptImportSection + tmpImportLine;

                    tmpObjectName = vtkObj.getClass().getSimpleName().toString() + "_" + obj.getId();
                    System.out.println(pipeline.getCassandraVtkObjectByTrueVtkObject(vtkObj) + " expoterd as " + tmpObjectName);
                    exportedVtkObjCanonicalNameArray.put(obj.getId(), tmpObjectName);
                    exportedVtkObjArray.put(obj.getId(), vtkObj);

                    tmpLine = tmpObjectName + " = " + vtkObj.getClass().getSimpleName().toString() + "()";
                    scriptInstanciationSection = scriptInstanciationSection + tmpLine + eol;

                } else {
                    System.out.println("Object class not supported. Can not be exported.");
                }
            }
        }

        // connectivity section
        if (exportConnectivity) {
            keyList = selectedVtkObjectList.keys();
            while (keyList.hasMoreElements()) {
                key = keyList.nextElement();
                obj = (CassandraObject) selectedVtkObjectList.get(key);

                // management of Cassandra's plug-ins
                if (obj.getVtkObject() instanceof FilterImpl) {
                    System.out.println("Cassandra filter found");
                    
                    // recover the parent plug-in of the current Cassandra filter
                    parentPlugIn = ((FilterImpl) obj.getVtkObject()).getParentPlugin();

                    // management of the corresponding property
                    plugInProperty = parentPlugIn.getPlugInProperty();
                    propertyMap = plugInProperty.getPropertyMap();

                    // construction of the needed property as Jython script
                    tmpImportLine = "if CASSANDRA_CONTEXT:" + eol + scriptTab + "from com.artenum.cassandra.util import CassandraToolBox" + eol + scriptTab
                    + "from " + plugInProperty.getClass().getPackage().getName() + " import " + plugInProperty.getClass().getSimpleName().toString() + eol;
                    scriptImportSection = scriptImportSection + tmpImportLine;
            
                    // set the name of the current instance of the plugin property
                    tmpPlugInPropertyInstanceName = plugInProperty.getClass().getSimpleName() + "_" + obj.getId();
                    
                    // setting of the plug-in properties
                    tmpPlugInsConnectivityLine =   "if CASSANDRA_CONTEXT:" + eol
                                                 + scriptTab + tmpPlugInPropertyInstanceName + " = " + plugInProperty.getClass().getSimpleName() + "()" + eol;
                    
                    
                    for ( Map.Entry<Object, Object> currentEntry : propertyMap.entrySet() ) {     
                        // we manage the case if the property is a vtkObject 
                        if (currentEntry.getValue() instanceof vtkObject) {
                           
                            vtkObject tmpVtkObject = (vtkObject) currentEntry.getValue();
                            
                            if( exportedVtkObjCanonicalNameArray.containsValue(tmpVtkObject) ){
                                System.out.println("found in exported list");
                            } else {
                                System.out.println("not found in exported list");
                            }
                            
                            for ( Entry<Integer, vtkObject> tmpEntry : exportedVtkObjArray.entrySet() ){  
                                if (tmpVtkObject.equals(tmpEntry.getValue()) ){
                                    // the Jython line
                                    tmpPlugInsConnectivityLine = tmpPlugInsConnectivityLine
                                                               + scriptTab
                                                               + tmpPlugInPropertyInstanceName + ".put( \"" + currentEntry.getKey() + "\", "
                                                               + exportedVtkObjCanonicalNameArray.get(tmpEntry.getKey()) + ")" + eol;
                                    break;
                                }
                            }
                            
                        }else {
                            // the Jython line
                            tmpPlugInsConnectivityLine =  tmpPlugInsConnectivityLine 
                                                        + scriptTab
                                                        + tmpPlugInPropertyInstanceName + ".put( \"" + currentEntry.getKey() + "\", " + currentEntry.getValue() + ")" + eol;
                            }
                        }
                    //the jython line
                    tmpPlugInsConnectivityLine =   tmpPlugInsConnectivityLine 
                                                 + scriptTab
                                                 + exportedVtkObjCanonicalNameArray.get(obj.getId()) + ".initAndUpdate(" + tmpPlugInPropertyInstanceName + " )" + eol;
                    //gather everything
                    scriptPlugInConnectivitySection = scriptPlugInConnectivitySection + tmpPlugInsConnectivityLine;
                    
                // management of VTK objects
                } else if (obj.getVtkObject() instanceof vtkObject) {
                    vtkObj = (vtkObject) obj.getVtkObject();

                    // we base the connectivity on the input one
                    inputConnectivityList = obj.getInputConnectivityList();
                    if (!inputConnectivityList.isEmpty()) {
                        Iterator<Integer> iter = inputConnectivityList.iterator();
                        while (iter.hasNext()) {
                            tmpId = iter.next();

                            if (obj.getVtkObject() instanceof vtkMapper) {
                                tmpConnectivityLine = exportedVtkObjCanonicalNameArray.get(obj.getId()) + ".SetInput(" + exportedVtkObjCanonicalNameArray.get(tmpId) + ")";

                            } else if (obj.getVtkObject() instanceof vtkDataObject) {

                                // FIXME
                                // tmpConnectivityLine =
                                // objCanonicalNameArray.get(obj.getId()) +
                                // ".SetInput(" +
                                // objCanonicalNameArray.get(tmpId) + ")";

                            } else if (obj.getVtkObject() instanceof vtkActor) {
                                tmpConnectivityLine = exportedVtkObjCanonicalNameArray.get(obj.getId()) + ".SetMapper(" + exportedVtkObjCanonicalNameArray.get(tmpId) + ")";

                            } else if (obj.getVtkObject() instanceof vtkScalarBarActor) {
                                tmpConnectivityLine = exportedVtkObjCanonicalNameArray.get(obj.getId()) + ".SetLookupTable(" + exportedVtkObjCanonicalNameArray.get(tmpId) + ")";

                            } else if (obj.getVtkObject() instanceof vtkLookupTable) {
                                tmpConnectivityLine = exportedVtkObjCanonicalNameArray.get(tmpId) + ".SetLookupTable(" + exportedVtkObjCanonicalNameArray.get(obj.getId()) + ")";
                            } else {
                                System.out.println("not supported yet ");
                            }
                            scriptConnectivitySection = scriptConnectivitySection + tmpConnectivityLine + eol;
                        }

                    }
                }
            }
        }

        // Cassandra's pipeline reloading section
        if (cassandraContext) {
            keyList = selectedVtkObjectList.keys();
            while (keyList.hasMoreElements()) {
                key = keyList.nextElement();
                obj = (CassandraObject) selectedVtkObjectList.get(key);

                if (obj.getVtkObject() instanceof FilterImpl) {
                    System.out.println("Cassandra filter found");

                    //FIXME
                    //scriptCassandraReloadingSection = scriptCassandraReloadingSection + tmpLine + eol;
                } else if (obj.getVtkObject() instanceof vtkObject) {
                    vtkObj = (vtkObject) obj.getVtkObject();

                    Point cellPosition = ((VtkObjectCellAdapter) obj.getMetaData().get(CassandraObject.CELL)).getPosition();
                    
                    if (obj.getVtkObject() instanceof vtkMapper) {
                        tmpLine = scriptTab + "pipeline.addMapper(" + exportedVtkObjCanonicalNameArray.get(obj.getId()) 
                                                                    + ", \"" + exportedVtkObjCanonicalNameArray.get(obj.getId()) 
                                                                    + "\", " + cellPosition.x +", " + cellPosition.y + ")";
                    } else if (obj.getVtkObject() instanceof vtkDataObject) {
                        tmpLine = scriptTab + "pipeline.addDataSet(" + exportedVtkObjCanonicalNameArray.get(obj.getId()) + ", \"" + exportedVtkObjCanonicalNameArray.get(obj.getId())
                        + "\", " + cellPosition.x +", " + cellPosition.y + ")";
                    } else if (obj.getVtkObject() instanceof vtkActor) {
                        tmpLine = scriptTab + "pipeline.addActor(" + exportedVtkObjCanonicalNameArray.get(obj.getId()) + ", \"" + exportedVtkObjCanonicalNameArray.get(obj.getId())
                        + "\", " + cellPosition.x +", " + cellPosition.y + ")";
                    } else if (obj.getVtkObject() instanceof vtkScalarBarActor) {
                        tmpLine = scriptTab + "pipeline.addScalarBar(" + exportedVtkObjCanonicalNameArray.get(obj.getId()) + ", \""
                                + exportedVtkObjCanonicalNameArray.get(obj.getId()) + "\", " + cellPosition.x +", " + cellPosition.y + ")";
                    } else if (obj.getVtkObject() instanceof vtkLookupTable) {
                        tmpLine = scriptTab + "pipeline.addLookupTable(" + exportedVtkObjCanonicalNameArray.get(obj.getId()) + ", \""
                                + exportedVtkObjCanonicalNameArray.get(obj.getId()) + "\", " + cellPosition.x +", " + cellPosition.y + ")";
                    } else {
                        System.out.println("not supported yet ");
                    }
                    scriptCassandraReloadingSection = scriptCassandraReloadingSection + tmpLine + eol;
                }
            }
        }

    }

    @Override
    public void setSelectedVtkObjectList(Hashtable selectedVtkObjectList) {
        this.selectedVtkObjectList = selectedVtkObjectList;
    }

    /**
     * @return the Cassandra context, i.e if the generated Python script are generated to be reloadable into Cassandra or not. 
     */
    public boolean getCassandraContext() {
        return cassandraContext;
    }

    public void setCassandraReloadable(boolean cassandraReloadable) {
        this.cassandraContext = cassandraReloadable;
    }

    public String getScriptImportSectionRadical() {
        return scriptImportSectionRadical;
    }

    public void setScriptImportSectionRadical(String scriptImportSectionRadical) {
        this.scriptImportSectionRadical = scriptImportSectionRadical;
    }

    public String getScriptInstanciationSectionRadical() {
        return scriptInstanciationSectionRadical;
    }

    public void setScriptInstanciationSectionRadical(String scriptInstanciationSectionRadical) {
        this.scriptInstanciationSectionRadical = scriptInstanciationSectionRadical;
    }

    public String getScriptConnectivitySectionRadical() {
        return scriptConnectivitySectionRadical;
    }

    public void setScriptConnectivitySectionRadical(String scriptConnectivitySectionRadical) {
        this.scriptConnectivitySectionRadical = scriptConnectivitySectionRadical;
    }

    public String getScriptCassandraReloadingSectionRadical() {
        return scriptCassandraReloadingSectionRadical;
    }

    public void setScriptCassandraReloadingSectionRadical(String scriptCassandraReloadingSectionRadical) {
        this.scriptCassandraReloadingSectionRadical = scriptCassandraReloadingSectionRadical;
    }

}
