/**
 * Copyright (C) 2008  Bull S. A. S.
 * Bull, Rue Jean Jaures, B.P.68, 78340, Les Clayes-sous-Bois
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation
 * version 2.1 of the License.
 * This library 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 GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA  02110-1301, USA.
 **/
package org.exoplatform.services.workflow.impl.bonita;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.exoplatform.container.PortalContainer;
import org.exoplatform.download.DownloadService;
import org.exoplatform.download.InputStreamDownloadResource;
import org.exoplatform.services.workflow.FileDefinition;
import org.exoplatform.services.workflow.Form;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;


/**
 * This class represents a Form that is defined in a configuration file.
 * Compared with the automatic one, this type of Form allows to customize the
 * shown panel, by filtering displayed fields, setting attributes of components
 * or choosing their renderer.
 * 
 * Created by Bull R&D
 * @author Brice Revenant
 * @author Le Gall Rodrigue <rodrigue.le-gall@bull.net>
 * Feb 27, 2006
 */
public class SpecifiedFormImpl implements Form {
  /** Customized view corresponding to this Form or empty String if unset */
  private String customizedView = null;
  
  /** URL of the icon corresponding to this Form */
  private byte[] icon = null;
  
  private byte[] stateImageBytes = null;
  
  /** Indicates if this Form corresponds to a delegated view */
  private boolean isDelegatedView = false;
  
  /** Localized Resource Bundle corresponding to this Form */
  private ResourceBundle resourceBundle = null;
  
  /** Name of the State corresponding to this Form */
  private String activity = null;
  
  /** Submit buttons corresponding to this Form */
  private List<Map<String, Object>> submitButtons;
  
  /** Variables corresponding to this Form */
  private List<Map<String, Object>> variables;
  
  /** Validation Message print after submit form */
  private String message;
  
  /* (non-Javadoc)
   * @see org.exoplatform.services.workflow.Form#getCustomizedView()
   */
  public String getCustomizedView() {
    return this.customizedView;
  }
  
  /* (non-Javadoc)
   * @see org.exoplatform.services.workflow.Form#getIconURL()
   */
  public String getIconURL() {
    String url;
    
    if(icon != null) {
      url = this.publishImage(icon);
    }
    else {
      url = "";
    }
    
    return url;
  }
  
  /* (non-Javadoc)
   * @see org.exoplatform.services.workflow.Form#getResourceBundle()
   */
  public ResourceBundle getResourceBundle() {
    return this.resourceBundle;
  }
  
  /* (non-Javadoc)
   * @see org.exoplatform.services.workflow.Form#getStateImageURL()
   */
  public String getStateImageURL() {
	  String url;
	    
	    if(this.stateImageBytes != null) {
	      url = this.publishImage(this.stateImageBytes);
	    }
	    else {
	      url = "";
	    }
	    
	    return url;
  }
  
  /* (non-Javadoc)
   * @see org.exoplatform.services.workflow.Form#getStateName()
   */
  public String getStateName() {
    return this.activity;
  }
  
  /* (non-Javadoc)
   * @see org.exoplatform.services.workflow.Form#getSubmitButtons()
   */
  public List getSubmitButtons() {
    return this.submitButtons;
  }

  /**
   * Make an image available from the download service
   * 
   * @param  image bytes describing the image
   * @return String giving the download URL of the published image
   */
  private String publishImage(byte[] image) {
    DownloadService dS = (DownloadService) PortalContainer.getInstance().
      getComponentInstanceOfType(DownloadService.class);
    InputStream iS = new ByteArrayInputStream(image);
    String id = dS.addDownloadResource(
      new InputStreamDownloadResource(iS, "image/gif"));
    return dS.getDownloadLink(id);
  }
  
  /* (non-Javadoc)
   * @see org.exoplatform.services.workflow.Form#getVariables()
   */
  public List getVariables() {
    return this.variables;
  }

  /* (non-Javadoc)
   * @see org.exoplatform.services.workflow.Form#isCustomizedView()
   */
  public boolean isCustomizedView() {
    return (this.customizedView != null) && (! "".equals(this.customizedView));
  }

  /* (non-Javadoc)
   * @see org.exoplatform.services.workflow.Form#isDelegatedView()
   */
  public boolean isDelegatedView() {
    return this.isDelegatedView;
  }
  
  /**
   * This constructor instantiates a Form specified in a File Definition, based
   * on a State name and a Locale. 
   * 
   * @param processId	   contains the id of the process
   * @param fileDefinition contains the definition of Process in which
   *                       information of Forms should be found
   * @param activity      identifies the State for which to create the Form
   * @param locale         specifies the Locale for which to create the Form
   */
  public SpecifiedFormImpl(String		  processId,
		  				   FileDefinition fileDefinition,
                           String         activity,
                           Locale         locale) {
    // Retrieve information from the File Definition
    this.customizedView  = fileDefinition.getCustomizedView(activity);
    this.isDelegatedView = fileDefinition.isDelegatedView(activity);
    this.resourceBundle  = fileDefinition.getResourceBundle(activity, locale);
    if(this.resourceBundle == null) {
    	// No resource bundle are provided so we use the autogenerated one
    	AutomaticFormImpl tmpForm = new AutomaticFormImpl(processId, activity, locale);
    	this.resourceBundle = tmpForm.getResourceBundle();
    }
    this.activity        = activity;
    this.variables       = fileDefinition.getVariables(activity);

    try {
      // Retrieve the icon URL
      this.icon          = fileDefinition.getEntry(this.activity +
                                                   "-icon.gif");
    }
    catch(Exception e) {
      // No provided icon
      this.icon          = null;
    }
    
    try {
        // Retrieve the activity image URL
        this.stateImageBytes          = fileDefinition.getEntry(this.activity +
                                                     "-state.gif");
      }
      catch(Exception e) {
        // No provided icon
        this.icon          = null;
      }

    /*
     * Initialize the buttons. The list is left empty when the Form corresponds
     * to a start Process one. In Bonita, we consider this is the Workflow duty
     * to determine which activity comes next hence a single default button for
     * all activities.
     */
    this.submitButtons = new ArrayList<Map<String, Object>>();
    if(! "".equals(activity)) {
      List<Map<String, Object>>  ret = new ArrayList<Map<String, Object>>(); 
      XPath xPath                    = XPathFactory.newInstance().newXPath();
      final String expression        = "/forms/form[state-name=\"" +
                                       activity +
                                       "\"]/submitbutton";

      NodeList nodeSet;
	try {
		nodeSet = (NodeList) xPath.evaluate(expression,
		    								getFormsDefinition(fileDefinition),
		                                    XPathConstants.NODESET);
	
      for(int i = 0 ; i < nodeSet.getLength() ; i ++) {
    	  Map<String, Object> attributes = new HashMap<String, Object>();
          Element element = (Element) nodeSet.item(i);
          Attr component = (Attr) element.getAttributeNode("variable");
          if(component != null) {
            attributes.put("variable", component.getValue());
          }
          component = (Attr) element.getAttributeNode("name");
          if(component != null) {
            attributes.put("name", component.getValue());
          }
          attributes.put("transition", "");
          this.submitButtons.add(attributes);
      }
	} catch (XPathExpressionException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
    }
  }

    public String getMessage() {
        return this.message;
    }

    public boolean hasMessage() {
        boolean out = false;
        if(this.message!=null) {
            out = !this.message.trim().equals("");
        }
        return out;
    }
    
    /**
     * Retrieves the whole Form definition as a DOM Element
     * 
     * @return a DOM Element corresponding to the Form definition file
     */
    public Element getFormsDefinition(FileDefinition f) {
      Element ret           = null;
      byte[] xpdlDefinition;
	try {
		xpdlDefinition = f.getEntry("forms.xml");
	

      try {
        if(xpdlDefinition == null) {
          // The XPDL file definition is not contained by the archive. Throw an
          // exception that will be used as a cause by the outer catch block.
          throw new Exception("The XPDL definition file was not retrieved.");
        }
        Element formDefinition;
          // The definition has not been parsed yet
          InputStream inputStream =
            new ByteArrayInputStream(xpdlDefinition);
          DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
          DocumentBuilder builder = factory.newDocumentBuilder();
          formDefinition = builder.parse(inputStream).getDocumentElement();
        // Return what is contained by the cache
        ret = formDefinition;
      }
      catch(Exception e) {
        throw new RuntimeException("Error while parsing the XPDL Definition", e);
      }
	} catch (Exception e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	}
      return ret;
    }
    
}
