/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */
package xml;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.KeyStroke;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import ilog.views.gantt.IlvGanttModel;
import ilog.views.gantt.IlvHierarchyChart;
import ilog.views.gantt.action.IlvAction;
import ilog.views.gantt.action.IlvZoomToFitAction;
import ilog.views.gantt.event.GanttModelChangedEvent;
import ilog.views.gantt.event.GanttModelListener;
import ilog.views.gantt.model.IlvDefaultGanttModel;
import ilog.views.gantt.xml.IlvGanttDocumentReader;
import ilog.views.gantt.xml.IlvGanttDocumentWriter;
import ilog.views.gantt.xml.IlvGanttStreamWriter;
import ilog.views.util.IlvLocaleUtil;
import ilog.views.util.swing.IlvSwingUtil;

/**
 * This class groups a number of IlvAction(s) that can be used to open an XML
 * file and to save a Gantt model to an XML file.
 */
public final class XMLGanttActions {

  /**
   * The XML file chooser.
   */
  private static XMLFileChooser fileChooser;

  /**
   * Prevent instance creation.
   */
  private XMLGanttActions() {
  }

  /**
   * Returns the file chooser used to select XML files.
   *
   * @return The XML file chooser.
   */
  private static XMLFileChooser getXMLFileChooser() {
    if (fileChooser == null) {
      fileChooser = new XMLFileChooser();
    }
    return fileChooser;
  }

  /**
   * Returns the current directory of the XML file chooser.
   *
   * @return The current directory of the file chooser.
   */
  public static File getCurrentDirectory() {
    return getXMLFileChooser().getCurrentDirectory();
  }

  /**
   * Sets the current directory of the XML file chooser. Passing in
   * <code>null</code> sets the file chooser to point to the user's default
   * directory.
   *
   * @param dir
   *          The current directory of the file chooser.
   */
  public static void setCurrentDirectory(File dir) {
    getXMLFileChooser().setCurrentDirectory(dir);
  }

  /**
   * Parses the SDXL file at the specified URL and returns the resulting Gantt
   * data model. This method will return <code>null</code> if the selected file
   * does not exist or any other problem is encountered.
   *
   * @param url
   *          The URL of the SDXL data file.
   * @param docReader
   *          The document reader that will be used to parse an XML
   *          <code>Document</code> into a Gantt data model.
   * @param errorComponent
   *          The component that will be used as the parent of any error
   *          dialogs.
   * @return The Gantt data model or <code>null</code>.
   */
  public static IlvGanttModel readXMLURL(URL url, IlvGanttDocumentReader docReader, Component errorComponent) {
    // Create an InputSource
    InputSource source = new InputSource(url.toString());

    // Create the document
    Document document;
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = factory.newDocumentBuilder();
      document = builder.parse(source);
    } catch (Exception ex) {
      IlvSwingUtil.showErrorDialog(errorComponent, ex);
      return null;
    }

    // Create a default Gantt model
    IlvGanttModel model = new IlvDefaultGanttModel();

    // Read the document into the Gantt model.
    try {
      docReader.readGanttModel(document, model);
    } catch (Exception ex) {
      IlvSwingUtil.showErrorDialog(errorComponent, ex);
      return null;
    }

    return model;
  }

  /**
   * Parses the SDXL file at the specified URL and returns the resulting Gantt
   * data model. This method will return <code>null</code> if the selected file
   * does not exist or any other problem is encountered. A default
   * <code>IlvGanttDocumentReader</code> will be used to parse an XML file into
   * a Gantt data model.
   *
   * @param url
   *          The URL of the SDXL data file.
   * @param errorComponent
   *          The component that will be used as the parent of any error
   *          dialogs.
   * @return The Gantt data model or <code>null</code>.
   */
  public static IlvGanttModel readXMLURL(URL url, Component errorComponent) {
    return readXMLURL(url, new IlvGanttDocumentReader(), errorComponent);
  }

  /**
   * An action that opens an XML data file in SDXL format.
   */
  public static class OpenXMLAction extends IlvAction {

    /**
     * The chart to which the action is attached.
     */
    private IlvHierarchyChart chart;

    /**
     * The Gantt document reader.
     */
    private IlvGanttDocumentReader docReader;

    /**
     * The zoom-to-fit action.
     */
    private IlvZoomToFitAction zoomToFitAction;

    /**
     * Creates a new <code>OpenXMLAction</code> for the specified chart.
     *
     * @param chart
     *          The chart.
     * @param docReader
     *          The document reader that will be used to parse an XML
     *          <code>Document</code> into a Gantt data model.
     */
    public OpenXMLAction(IlvHierarchyChart chart, IlvGanttDocumentReader docReader) {
      super("Open...", null, KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_DOWN_MASK), "Open Project",
          "Opens An Existing XML Project.");
      setIcon(XMLGanttActions.class, "images/open.gif");
      this.chart = chart;
      this.docReader = docReader;
      this.zoomToFitAction = new IlvZoomToFitAction(chart, "", null, null, "", "");
    }

    /**
     * Creates a new <code>OpenXMLAction</code> for the specified chart. A
     * default <code>IlvGanttDocumentReader</code> will be used to parse an XML
     * file into a Gantt data model.
     *
     * @param chart
     *          The chart.
     */
    public OpenXMLAction(IlvHierarchyChart chart) {
      this(chart, new IlvGanttDocumentReader());
    }

    /**
     * Returns the chart with which this action is associated.
     *
     * @return The chart.
     */
    public IlvHierarchyChart getChart() {
      return chart;
    }

    /**
     * Parses the SDXL file at the specified URL and returns the resulting Gantt
     * data model. This method will return <code>null</code> if the selected
     * file does not exist or any other problem is encountered.
     *
     * @param url
     *          The URL of the SDXL data file.
     * @return The Gantt data model or <code>null</code>.
     */
    public IlvGanttModel readXMLURL(URL url) {
      return XMLGanttActions.readXMLURL(url, docReader, chart);
    }

    /**
     * Prompts the user for an XML data file to open, parses the file, and
     * returns the resulting Gantt data model. This method will return
     * <code>null</code> if the user cancels, the selected file does not exist,
     * or any other problem is encountered.
     *
     * @return The Gantt data model or <code>null</code>.
     */
    public IlvGanttModel readXMLFile() {
      // Choose an XML file to open.
      String filename = getXMLFileChooser().showOpenDialog(chart);
      if (filename == null) {
        return null;
      }

      // Get the URL of the selected file.
      URL url;
      try {
        url = new File(filename).toURI().toURL();
      } catch (MalformedURLException ex) {
        IlvSwingUtil.showErrorDialog(chart, ex);
        return null;
      }

      // Parse the file.
      return readXMLURL(url);
    }

    /**
     * Returns the zoom-to-fit action that is performed when an XML schedule is
     * opened.
     *
     * @return The zoom-to-fit action.
     */
    protected IlvZoomToFitAction getZoomToFitAction() {
      return zoomToFitAction;
    }

    /**
     * Zooms the chart to fit the data model's time interval.
     */
    protected void zoomToFit() {
      getZoomToFitAction().perform();
    }

    /**
     * This function is called when the user wants to open an XML file.
     *
     * @param event
     *          The event.
     */
    Override
    public void actionPerformed(ActionEvent event) {
      IlvGanttModel model = readXMLFile();
      // Change the Gantt model of the Gantt chart
      if (model != null) {
        chart.setGanttModel(model);
        chart.expandAllRows();
        zoomToFit();
      }
    }

  }

  /**
   * An action that saves the current data model to an XML data file in SDXL
   * format.
   */
  public static class SaveAsXMLAction extends IlvAction {

    /**
     * The chart to which the action is attached.
     */
    private IlvHierarchyChart chart;

    /**
     * The Gantt document writer.
     */
    private IlvGanttDocumentWriter docWriter;

    /**
     * The Gantt stream writer.
     */
    private IlvGanttStreamWriter streamWriter;

    /**
     * Creates a new <code>SaveAsXMLAction</code> for the specified chart.
     *
     * @param chart
     *          The chart.
     * @param docWriter
     *          The document writer that will be used to convert a Gantt data
     *          model into an XML <code>Document</code>.
     * @param streamWriter
     *          The stream writer that will be used to write an XML
     *          <code>Document</code> to an output stream.
     */
    public SaveAsXMLAction(IlvHierarchyChart chart, IlvGanttDocumentWriter docWriter,
        IlvGanttStreamWriter streamWriter) {
      super("Save As...", null, KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_DOWN_MASK), "Save Project As XML",
          "Saves the current project as XML.");
      setIcon(XMLGanttActions.class, "images/save.gif");
      this.chart = chart;
      this.docWriter = docWriter;
      this.streamWriter = streamWriter;
      // We do not want the action to be enabled if the data model is null.
      // So register a Gantt model listener, to update the action only when
      // the data model is not null.
      chart.addGanttModelListener(new GanttModelListener() {
        Override
        public void ganttModelChanged(GanttModelChangedEvent event) {
          computeEnabled();
        }
      });
      computeEnabled();
    }

    /**
     * Computes whether this action should be enabled or not.
     */
    private void computeEnabled() {
      setEnabled(chart.getGanttModel() != null);
    }

    /**
     * Creates a new <code>SaveAsXMLAction</code> for the specified chart. A
     * default <code>IlvGanttDocumentWriter</code> will be used to convert a
     * Gantt data model into an XML <code>Document</code> and a default
     * <code>IlvGanttStreamWriter</code> will be used to write an XML
     * <code>Document</code> to an output stream.
     *
     * @param chart
     *          The chart.
     */
    public SaveAsXMLAction(IlvHierarchyChart chart) {
      this(chart, new IlvGanttDocumentWriter(IlvLocaleUtil.getCurrentLocale()), new IlvGanttStreamWriter());
    }

    /**
     * Returns the chart with which this action is associated.
     *
     * @return The chart.
     */
    public IlvHierarchyChart getChart() {
      return chart;
    }

    /**
     * This function is called when the user wants to save the current Gantt
     * model to an XML file.
     *
     * @param event
     *          The event.
     */
    Override
    public void actionPerformed(ActionEvent event) {
      String filename = getXMLFileChooser().showSaveDialog(chart);
      if (filename == null) {
        return;
      }
      // Create the document
      Document document;
      try {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        document = builder.newDocument();
      } catch (Exception ex) {
        IlvSwingUtil.showErrorDialog(chart, ex);
        return;
      }

      // Write the Gantt model into the document. Then, write the document
      // to the file.
      try {
        docWriter.writeGanttModel(document, chart.getGanttModel());
        FileOutputStream outstream = new FileOutputStream(filename);
        streamWriter.writeDocument(outstream, document);
        outstream.close();
      } catch (Exception ex) {
        IlvSwingUtil.showErrorDialog(chart, ex);
      }
    }
  }

}