/*
 * $Id: JaWEUtils.java,v 1.2 2004/09/20 10:26:12 koppy Exp $
 * 
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 * 
 * $Log: JaWEUtils.java,v $
 * Revision 1.2  2004/09/20 10:26:12  koppy
 * +javadoc Dokumentation
 *
 * Revision 1.1  2004/08/30 14:50:00  koppy
 * Initiale Version der Workflow-Komponente
 *
 */
package vkc.workflow.util;

import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import org.enhydra.jawe.AbstractGraph;
import org.enhydra.jawe.JaWE;
import org.enhydra.jawe.ProcessEditor;
import org.enhydra.jawe.ProcessGraph;
import org.enhydra.jawe.WorkflowManager;
import org.enhydra.jawe.actions.SaveAsJPG;
import org.enhydra.jawe.actions.SaveAsSVG;
import org.enhydra.jawe.xml.elements.Activities;
import org.enhydra.jawe.xml.elements.Activity;
import org.enhydra.jawe.xml.elements.Package;
import org.enhydra.jawe.xml.elements.WorkflowProcess;
import org.enhydra.jawe.xml.elements.WorkflowProcesses;

import WfBase.BaseException;
import WfBase.InvalidQuery;
import WorkflowModel.WfActivity;
import WorkflowModel.WfActivityIterator;
import WorkflowModel.WfProcess;
import WorkflowService.AdminMisc;
import WorkflowService.NotConnected;
import WorkflowService.PackageAdministration;

import com.sun.image.codec.jpeg.ImageFormatException;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

/**
 * This class represents a utility class for
 * generating JPGs/SVGs of process instances or definitions.
 * 
 * @author Andreas Koop
 */
public class JaWEUtils {
    /*
     * Instantiating JaWE is an expensive operation, so we do it lazily (as
     * needed).
     */
    private static JaWE jawe = null;

    /**
     * This method is used to put the XPDL of the package specified by pkgId
     * into JaWE's internal map. This is only done if it is not already present.
     * JaWE can then be used to generate graphs representing the processes
     * defined in the XPDL.
     * 
     * @param pkgAdmin reference to the PackageAdministration interface
     * @param pkgId the package id
     * @return the appropriate package
     */
    public static Package getPackage(PackageAdministration pkgAdmin,
            String pkgId) throws Exception {
        Package pkg = null;

        if (JaWE.getXMLInterface().getPackageById(pkgId) == null) {
            if (jawe == null) {
                jawe = JaWE.getInstance();
            }

            String content = new String(pkgAdmin.getPackageContent(pkgId),
                    "UTF-8");

            JaWE.getXMLInterface().putPkgIdToFileContentMapping(pkgId, content);
            pkg = jawe.openDocument(pkgId, false, true);
        } else {
            pkg = JaWE.getXMLInterface().getPackageById(pkgId);
        }

        return pkg;
    }
    
    /**
     * This method is used to put the XPDL of the package specified by pkgId
     * into JaWE's internal map. This is only done if it is not already present.
     * JaWE can then be used to generate graphs representing the processes
     * defined in the XPDL.
     * 
     * @param pkgAdmin reference to the PackageAdministration interface
     * @param pkgId the package id
     * @return the appropriate package
     */
    public static Package getPackage(
            org.enhydra.shark.api.client.wfservice.PackageAdministration pkgAdmin,
            String pkgId) throws UnsupportedEncodingException, org.enhydra.shark.api.client.wfbase.BaseException {
        Package pkg = null;

        if (JaWE.getXMLInterface().getPackageById(pkgId) == null) {
            if (jawe == null) {
                jawe = JaWE.getInstance();
            }

            String content = new String(pkgAdmin.getPackageContent(pkgId),
                    "UTF-8");

            JaWE.getXMLInterface().putPkgIdToFileContentMapping(pkgId, content);
            pkg = jawe.openDocument(pkgId, false, true);
        } else {
            pkg = JaWE.getXMLInterface().getPackageById(pkgId);
        }

        return pkg;
    }

    /**
     * This method returns a ProcessGraph representing the wfProcess provided.
     * The graph is visually prepared for best presentation as an image and can
     * be directly used to generate JPGs and SVGs.
     */
    private static ProcessGraph getProcessGraph(AdminMisc adminMisc,
            PackageAdministration pkgAdmin, WfProcess wfProcess)
            throws NotConnected, BaseException, InvalidQuery, Exception {
        String pkgId = adminMisc.getProcessMgrPkgId(wfProcess.manager().name());

        String pDefId = adminMisc.getProcessDefinitionId(wfProcess.key());

        ProcessGraph graph = getProcessDefinitionGraph( pkgAdmin,
                pkgId, pDefId);

        highlightRunningActivities(adminMisc, graph, wfProcess);

        return graph;
    }
    
    /**
     * This method returns a ProcessGraph representing the wfProcess provided.
     * The graph is visually prepared for best presentation as an image and can
     * be directly used to generate JPGs and SVGs.
     */
    private static ProcessGraph getProcessGraph(
            org.enhydra.shark.api.client.wfservice.AdminMisc adminMisc,
            org.enhydra.shark.api.client.wfservice.PackageAdministration pkgAdmin,
            org.enhydra.shark.api.client.wfmodel.WfProcess wfProcess) 
    throws org.enhydra.shark.api.client.wfbase.InvalidQuery, UnsupportedEncodingException, org.enhydra.shark.api.client.wfbase.BaseException{
        String pkgId = adminMisc.getProcessMgrPkgId(wfProcess.manager().name());

        String pDefId = adminMisc.getProcessDefinitionId(wfProcess.key());

        ProcessGraph graph = getProcessDefinitionGraph(pkgAdmin,
                pkgId, pDefId);

        highlightRunningActivities(adminMisc, graph, wfProcess);

        return graph;
    }
    
    /**
     * This method returns a ProcessGraph representing the process 
     * definition graph provided.
     * The graph is visually prepared for best presentation as an image and can
     * be directly used to generate JPGs and SVGs.
     * 
     * @param pkgAdmin
     * @param packageId the package id
     * @param procDefId the process definition id
     * @return reference to the ProcessGraph object
     * @throws Exception
     */
    private static ProcessGraph getProcessDefinitionGraph(
            PackageAdministration pkgAdmin, String packageId, String procDefId)
            throws Exception {
        Package pkg = getPackage(pkgAdmin, packageId);

        jawe.getPackageEditor().setNewPackage(pkg, false);
        pkg.setReadOnly(true);

        WorkflowProcesses wps = (WorkflowProcesses) pkg
                .get("WorkflowProcesses");

        WorkflowProcess wp = wps.getWorkflowProcess(procDefId);

        ProcessGraph graph = (ProcessGraph) jawe
                .getPackageEditor().getProcessObject(wp)
                .getImplementationEditor().getGraph();

        graph.setEditable(false);
        graph.setMoveable(false);
        ((ProcessEditor) graph.getEditor()).setButtonsEnabled(false);

        // Without this line we get blank black images:
        graph.setSize(graph.getPreferredSize());

        return graph;
    }
    
    /**
     * This method returns a ProcessGraph representing the process 
     * definition graph provided.
     * The graph is visually prepared for best presentation as an image and can
     * be directly used to generate JPGs and SVGs.
     * 
     * @param pkgAdmin
     * @param packageId the package id
     * @param procDefId the process definition id
     * @return reference to the ProcessGraph object
     * @throws Exception
     */
    private static ProcessGraph getProcessDefinitionGraph(
            org.enhydra.shark.api.client.wfservice.PackageAdministration pkgAdmin,
            String packageId, String procDefId) throws UnsupportedEncodingException, org.enhydra.shark.api.client.wfbase.BaseException{
        
        Package pkg = getPackage(pkgAdmin, packageId);

        jawe.getPackageEditor().setNewPackage(pkg, false);
        pkg.setReadOnly(true);

        WorkflowProcesses wps = (WorkflowProcesses) pkg
                .get("WorkflowProcesses");

        WorkflowProcess wp = wps.getWorkflowProcess(procDefId);

        ProcessGraph graph = (ProcessGraph) jawe
                .getPackageEditor().getProcessObject(wp)
                .getImplementationEditor().getGraph();

        graph.setEditable(false);
        graph.setMoveable(false);
        ((ProcessEditor) graph.getEditor()).setButtonsEnabled(false);

        // Without this line we get blank black images:
        graph.setSize(graph.getPreferredSize());

        return graph;
    }
    

    /**
     * Adapted from the Shark Admin ProcessViewer.updateSelection(). This method
     * "selects" running activities, making them appear orange.
     */
    private static void highlightRunningActivities(AdminMisc adminMisc,
            ProcessGraph graph, WfProcess wfProcess) throws InvalidQuery,
            BaseException {
        graph.clearSelection();

        Activities allActs = (Activities) graph.get("Activities");

        WfActivityIterator wai = wfProcess.get_iterator_step();
        wai.set_query_expression("state.startsWith(\"open\")");
        wai.goto_start();
        WfActivity[] allRunningActs = wai.get_next_n_sequence(0);

        for (int i = 0; i < allRunningActs.length; ++i) {
            try {
                String procId = allRunningActs[i].container().key();
                String actId = allRunningActs[i].key();

                /*
                 * NB If the following exception does NOT occur, then the
                 * current activity is part of a block activity and so we skip
                 * it.
                 */
                try {
                    String blockId = adminMisc
                            .getBlockActivityId(procId, actId);

                    if (blockId != null && blockId.trim().length() > 0) {
                        continue;
                    }
                } catch (Exception ex) {
                    /*
                     * Do nothing - we're just checking that we're not dealing
                     * with an activity that's part of a block.
                     */
                }

                Activity act = allActs.getActivity(adminMisc
                        .getActivityDefinitionId(procId, actId));

                WorkflowManager wm = graph.getWorkflowManager();
                Object actToHighlight = wm.getActivity(act.getID());
                if (actToHighlight != null) {
                    graph.addSelectionCell(actToHighlight);
                }
            } catch (Exception ex) {
                /*
                 * Don't bomb-out if we can't select this particular activity as
                 * there's no reason we won't be able to select the others.
                 */
            }
        }
    }
    
    /**
     * Adapted from the Shark Admin ProcessViewer.updateSelection(). This method
     * "selects" running activities, making them appear orange.
     */
    private static void highlightRunningActivities(
            org.enhydra.shark.api.client.wfservice.AdminMisc adminMisc,
            ProcessGraph graph, 
            org.enhydra.shark.api.client.wfmodel.WfProcess wfProcess) 
    	throws org.enhydra.shark.api.client.wfbase.InvalidQuery, 
    			org.enhydra.shark.api.client.wfbase.BaseException {
        graph.clearSelection();

        Activities allActs = (Activities) graph.get("Activities");

        org.enhydra.shark.api.client.wfmodel.WfActivityIterator wai = 
            wfProcess.get_iterator_step();
        wai.set_query_expression("state.startsWith(\"open\")");
        wai.goto_start();
        org.enhydra.shark.api.client.wfmodel.WfActivity[] allRunningActs = wai.get_next_n_sequence(0);

        for (int i = 0; i < allRunningActs.length; ++i) {
            try {
                String procId = allRunningActs[i].container().key();
                String actId = allRunningActs[i].key();

                /*
                 * NB If the following exception does NOT occur, then the
                 * current activity is part of a block activity and so we skip
                 * it.
                 */
                try {
                    String blockId = adminMisc
                            .getBlockActivityId(procId, actId);

                    if (blockId != null && blockId.trim().length() > 0) {
                        continue;
                    }
                } catch (Exception ex) {
                    /*
                     * Do nothing - we're just checking that we're not dealing
                     * with an activity that's part of a block.
                     */
                }

                Activity act = allActs.getActivity(adminMisc
                        .getActivityDefinitionId(procId, actId));

                WorkflowManager wm = graph.getWorkflowManager();
                Object actToHighlight = wm.getActivity(act.getID());
                if (actToHighlight != null) {
                    graph.addSelectionCell(actToHighlight);
                }
            } catch (Exception ex) {
                /*
                 * Don't bomb-out if we can't select this particular activity as
                 * there's no reason we won't be able to select the others.
                 */
            }
        }
    }

    /**
     * Generate a JPG image representing the specified process, with running
     * activities highlighted.
     * 
     * @param wfProcess
     *            The process we wish to represent as a JPG.
     * @param file
     *            The name of the file to create.
     * @throws Exception
     */
    public static void saveProcessJPG(AdminMisc adminMisc,
            PackageAdministration pkgAdmin, WfProcess wfProcess, String file)
            throws Exception {
        SaveAsJPG.saveGraphAsJPG(file, getProcessGraph(adminMisc, pkgAdmin,
                wfProcess));
    }
    
    /**
     * Generate a JPG image representing the specified process, with running
     * activities highlighted and write it to the given outputstream.
     * 
     * @param adminMisc
     * @param pkgAdmin
     * @param wfProcess
     * @param out
     * @throws org.enhydra.shark.api.client.wfbase.InvalidQuery
     * @throws org.enhydra.shark.api.client.wfbase.BaseException
     * @throws ImageFormatException
     * @throws IOException
     */
    public static void writeProcessGraphAsJPG(
            org.enhydra.shark.api.client.wfservice.AdminMisc adminMisc,
            org.enhydra.shark.api.client.wfservice.PackageAdministration pkgAdmin,
            org.enhydra.shark.api.client.wfmodel.WfProcess wfProcess,
            OutputStream out) throws org.enhydra.shark.api.client.wfbase.InvalidQuery, 
            				org.enhydra.shark.api.client.wfbase.BaseException, 
            				ImageFormatException, IOException{
        
        writeGraph(getProcessGraph(adminMisc, pkgAdmin,
                wfProcess), out ); 
        
    }
    
    /**
     * Generate a JPG image representing the specified process, with running
     * activities highlighted and write it to the given outputstream.
     * 
     * @param adminMisc
     * @param pkgAdmin
     * @param wfProcess
     * @param out
     * @throws org.enhydra.shark.api.client.wfbase.InvalidQuery
     * @throws org.enhydra.shark.api.client.wfbase.BaseException
     * @throws ImageFormatException
     * @throws IOException
     */
    public static void writeProcessGraphAsJPG(
            AdminMisc adminMisc,
            PackageAdministration pkgAdmin,
            WfProcess wfProcess,
            OutputStream out) throws Exception{
        
        writeGraph(getProcessGraph(adminMisc, pkgAdmin,
                wfProcess), out ); 
        
    }
    
    
    /**
     * Generate a JPG image representing the specified process DEFINITION, with running
     * activities highlighted and write it to the given outputstream.
     * 
     * @param pkgAdmin
     * @param pkgId
     * @param pDefId
     * @param out
     * @throws Exception
     */
    public static void writeProcessDefGraphAsJPG(
            org.enhydra.shark.api.client.wfservice.PackageAdministration pkgAdmin,
            String pkgId,
            String pDefId,
            OutputStream out) throws Exception{
        
        writeGraph(getProcessDefinitionGraph(pkgAdmin,
                pkgId, pDefId), out ); 
        
    }
    
    /**
     * Generate a JPG image representing the specified process DEFINITION, with running
     * activities highlighted and write it to the given outputstream.
     * 
     * @param pkgAdmin
     * @param pkgId
     * @param pDefId
     * @param out
     * @throws Exception
     */
    public static void writeProcessDefGraphAsJPG(
            PackageAdministration pkgAdmin,
            String pkgId,
            String pDefId,
            OutputStream out) throws Exception{
        
        writeGraph(getProcessDefinitionGraph(pkgAdmin,
                pkgId, pDefId), out ); 
        
    }
    
    
    /**
     * Write the given abstract graph to the given outputstream.
     * 
     * @param graph the abstract graph
     * @param out an outputstream
     * @throws ImageFormatException
     * @throws IOException
     */
    private static void writeGraph( AbstractGraph graph, OutputStream out) 
    	throws ImageFormatException, IOException{
        
        BufferedImage img = null;
        Object[] cells = graph.getRoots();

        if (cells.length > 0) {
           Rectangle2D bounds = graph.getCellBounds(cells);
           graph.toScreen(bounds);

           // Create a Buffered Image
           img = new BufferedImage((int)bounds.getWidth(),(int)bounds.getHeight(),BufferedImage.TYPE_INT_RGB);
           Graphics2D graphics = img.createGraphics();
           graph.paint(graphics);
        }

        JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
        encoder.encode(img);
        out.flush();
        out.close();
    }

    /**
     * Generate an SVG image representing the specified process, with running
     * activities highlighted.
     * 
     * @param adminMisc
     *            A AdminMisc instance that is connected to the
     *            workflow server running the process.
     * @param wfProcess
     *            The process we wish to represent as an SVG.
     * @param file
     *            The name of the file to create.
     * @throws Exception
     */
    public static void saveProcessSVG(AdminMisc adminMisc,
            PackageAdministration pkgAdmin, WfProcess wfProcess, String file)
            throws Exception {
        SaveAsSVG.saveGraphAsSVG(file, getProcessGraph(adminMisc, pkgAdmin,
                wfProcess));
    }

    /**
     * Generate a JPG image representing the specified process definition
     * and save it to the given file.
     */
    public static void saveProcessDefinitionJPG(
            PackageAdministration pkgAdmin, String packageId, String procDefId,
            String file) throws Exception {
        SaveAsJPG.saveGraphAsJPG(file, getProcessDefinitionGraph(
                pkgAdmin, packageId, procDefId));
    }
    
    /**
     * Generate an SVG image representing the specified process definition
     * and save it to the given file.
     */
    public static void saveProcessDefinitionSVG(
            PackageAdministration pkgAdmin, String packageId, String procDefId,
            String file) throws Exception {
        SaveAsSVG.saveGraphAsSVG(file, getProcessDefinitionGraph(
                pkgAdmin, packageId, procDefId));
    }
}