/*
 * 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 jdbc;

import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.sql.RowSet;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JToolBar;

import ganttChart.GanttExample;
import ilog.views.gantt.IlvActivity;
import ilog.views.gantt.IlvActivityFactory;
import ilog.views.gantt.IlvGanttChart;
import ilog.views.gantt.IlvGanttModel;
import ilog.views.gantt.IlvHierarchyChart;
import ilog.views.gantt.IlvResource;
import ilog.views.gantt.IlvResourceFactory;
import ilog.views.gantt.IlvTimeInterval;
import ilog.views.gantt.action.IlvAction;
import ilog.views.gantt.model.table.IlvTableActivity;
import ilog.views.gantt.model.table.IlvTableGanttModel;
import ilog.views.gantt.model.table.IlvTableResource;
import ilog.views.gantt.project.IlvGanttProjectConfiguration;
import ilog.views.gantt.property.IlvActivityReservationsProperty;
import ilog.views.gantt.property.IlvResourceNameProperty;
import ilog.views.gantt.swing.IlvJTableColumn;
import ilog.views.gantt.swing.IlvStringColumn;
import ilog.views.util.convert.IlvConvert;
import ilog.views.util.convert.IlvConvertException;
import ilog.views.util.convert.IlvConverter;
import ilog.views.util.data.IlvRowSetTableModel;
import ilog.views.util.data.IlvTableModelMapper;
import ilog.views.util.data.IlvTableModelMappingException;
import ilog.views.util.styling.IlvStylingException;
import ilog.views.util.swing.IlvSwingUtil;
import shared.swing.ExampleFrame;

/**
 * This example shows how to use the ilog.views.gantt.model.table package to
 * fill Gantt data from a database and conversly update the database on Gantt
 * chart data changes. This class works together with the JdbcGanttActions
 * class.
 */
public class JdbcGanttExample extends GanttExample {

  /**
   * The various menu and toolbar actions.
   */
  protected IlvAction commitAction;
  protected IlvAction rollbackAction;
  protected IlvAction refreshAction;
  protected IlvAction reinitAction;
  protected IlvAction reconnectAction;

  private Map<? extends Object, Object> helpProperties;

  /**
   * Did the connection failed? Not yet.
   */
  boolean tryConnectionFailed = false;

  /**
   * Parameters for the database access.
   */
  static final String databaseURL = "jdbc:mysql:///test_ilog_demos";
  static final String user = "";
  static final String passwd = "";
  static final String driverName = "com.mysql.jdbc.Driver";
  static final int CACHE_SIZE = Integer.MAX_VALUE; // 0

  private static final Class<?>[] from = new Class[] { java.util.Date.class };
  private static final Class<?>[] to = new Class[] { Timestamp.class };

  // converter that allows to convert Date object created by the Gantt
  // to objects required by the database type
  static {
    IlvConvert.addConverter(new IlvConverter() {
      Override
      public Class<?>[] fromTypes() {
        return from;
      }

      Override
      public Class<?>[] toTypes() {
        return to;
      }

      Override
      SuppressWarnings("rawtypes")
      public Object convert(Object value, Class toType) throws IlvConvertException {
        // this is needed to overcome a mySQL limitation on Timestamp
        // object precision and to avoid the synchronization problems
        // it creates with CachedRowsSet
        return new Timestamp((((java.util.Date) value).getTime() / 1000) * 1000);
      }
    });
  }

  /**
   * Creates the Gantt chart.
   */
  Override
  protected IlvHierarchyChart createChart() {
    return new IlvGanttChart() {
      Override
      protected IlvJTableColumn[] createDefaultTableColumns() {
        IlvJTableColumn[] columns = super.createDefaultTableColumns();
        // we want to display a Name not an ID for the resource
        // (id is not very meaningfull in our database table)
        IlvStringColumn resourceCol = new IlvStringColumn(columns[4].getColumn().getHeaderValue(),
            new IlvActivityReservationsProperty(this, new IlvResourceNameProperty()),
            columns[4].getColumn().getIdentifier());
        resourceCol.setChildrenEditable(false);
        resourceCol.setParentsEditable(false);
        columns[4] = resourceCol;
        return columns;
      }
    };
  }

  /**
   * Creates and returns the Gantt data model.
   */
  Override
  protected IlvGanttModel createGanttModel() {
    try {
      Class.forName(driverName);
    } catch (ClassNotFoundException e) {
      // cannot happen, it is in the class path...
    }
    IlvTableModelMapper activityMapper = createActivityMapper();
    IlvTableModelMapper constraintMapper = null;
    IlvTableModelMapper resourceMapper = null;
    IlvTableModelMapper reservationMapper = null;
    IlvTableGanttModel model = null;
    if (activityMapper != null) {
      constraintMapper = createConstraintMapper();
      resourceMapper = createResourceMapper();
      reservationMapper = createReservationMapper();
      // otherwise no need to bother with other cases as connection is broken
    }
    try {
      // alternatively a ilog.views.gantt.jdbc.IlvJDBCGanttModel could be used
      // see its javadoc for information on how to use it but then only
      // autocommit
      // would be available.
      model = new IlvTableGanttModel(activityMapper, resourceMapper, constraintMapper, reservationMapper);
    } catch (IlvTableModelMappingException e) {
      // should not happen
      IlvSwingUtil.showErrorDialog(this, e);
    }
    return model;
  }

  void retryConnection() {
    if (tryConnectionFailed) {
      setConnectionFailedStatus(false);
      tryConnectionFailed = false;
      IlvTableModelMapper activityMapper = createActivityMapper();
      IlvTableModelMapper constraintMapper = null;
      IlvTableModelMapper resourceMapper = null;
      IlvTableModelMapper reservationMapper = null;
      if (activityMapper != null) {
        constraintMapper = createConstraintMapper();
        resourceMapper = createResourceMapper();
        reservationMapper = createReservationMapper();
        // otherwise no need to bother with other cases as connection is broken
      }
      IlvTableGanttModel model = (IlvTableGanttModel) chart.getGanttModel();
      model.setActivityMapper(activityMapper);
      model.setConstraintMapper(constraintMapper);
      model.setResourceMapper(resourceMapper);
      model.setReservationMapper(reservationMapper);
      try {
        model.initializeMapping();
      } catch (IlvTableModelMappingException e) {
        // should not happen
        IlvSwingUtil.showErrorDialog(this, e);
      }
    }
  }

  /**
   * Creates the activity mapper
   */
  private IlvTableModelMapper createActivityMapper() {
    IlvTableModelMapper activityMapper = null;
    try {
      RowSet rowSet = IlvRowSetTableModel.createRowSet(databaseURL, user, passwd, "select * from activities", false,
          CACHE_SIZE);
      IlvRowSetTableModel activities = new IlvRowSetTableModel(rowSet);
      activityMapper = IlvTableGanttModel.createActivityMapper(activities, 0, 1, 2, 3, 4);
    } catch (SQLException e) {
      Logger.getLogger("ganttdemo").log(Level.INFO, e.getMessage(), e);
      if (!tryConnectionFailed) {
        // if it did not failed yet
        IlvSwingUtil.showErrorDialog(this,
            "cannot connect to the database\n" + "please start the database and try to reconnect");
        setConnectionFailedStatus(true);
      }
    }
    return activityMapper;
  }

  /**
   * Creates the constraint mapper
   */
  private IlvTableModelMapper createConstraintMapper() {
    IlvTableModelMapper constraintMapper = null;
    try {
      RowSet rowSet = IlvRowSetTableModel.createRowSet(databaseURL, user, passwd, "select * from constraints", false,
          CACHE_SIZE);
      IlvRowSetTableModel constraints = new IlvRowSetTableModel(rowSet);
      constraintMapper = IlvTableGanttModel.createConstraintMapper(constraints, 0, 1, 2,
          new Object[] { "Start-Start", "Start-End", "End-Start", "End-End" });
    } catch (SQLException e) {
      Logger.getLogger("ganttdemo").log(Level.INFO, e.getMessage(), e);
      if (!tryConnectionFailed) {
        // if it did not failed yet
        IlvSwingUtil.showErrorDialog(this,
            "cannot connect to the database\n" + "please start the database and try to reconnect");
        setConnectionFailedStatus(true);
      }
    }
    return constraintMapper;
  }

  /**
   * Creates the resource mapper
   */
  private IlvTableModelMapper createResourceMapper() {
    IlvTableModelMapper resourceMapper = null;
    try {
      RowSet rowSet = IlvRowSetTableModel.createRowSet(databaseURL, user, passwd, "select * from resources", false,
          CACHE_SIZE);
      IlvRowSetTableModel resources = new IlvRowSetTableModel(rowSet);
      resourceMapper = IlvTableGanttModel.createResourceMapper(resources, 0, 1, 2, 3);
    } catch (SQLException e) {
      Logger.getLogger("ganttdemo").log(Level.INFO, e.getMessage(), e);
      if (!tryConnectionFailed) {
        // if it did not failed yet
        IlvSwingUtil.showErrorDialog(this,
            "cannot connect to the database\n" + "please start the database and try to reconnect");
        setConnectionFailedStatus(true);
      }
    }
    return resourceMapper;
  }

  /**
   * Creates the reservation mapper
   */
  private IlvTableModelMapper createReservationMapper() {
    IlvTableModelMapper reservationMapper = null;
    try {
      RowSet rowSet = IlvRowSetTableModel.createRowSet(databaseURL, user, passwd, "select * from reservations", false,
          CACHE_SIZE);
      IlvRowSetTableModel constraints = new IlvRowSetTableModel(rowSet);
      // in our case first column is activity, second is resource => reverse
      reservationMapper = IlvTableGanttModel.createReservationMapper(constraints, 1, 0);
    } catch (SQLException e) {
      Logger.getLogger("ganttdemo").log(Level.INFO, e.getMessage(), e);
      if (!tryConnectionFailed) {
        // if it did not failed yet
        IlvSwingUtil.showErrorDialog(this,
            "cannot connect to the database\n" + "please start the database and try to reconnect");
        setConnectionFailedStatus(true);
      }
    }
    return reservationMapper;
  }

  /**
   * Change the connection failed status
   */
  private void setConnectionFailedStatus(boolean value) {
    tryConnectionFailed = value;
    // update actions depending on the status of the connection
    if (reconnectAction != null) {
      reconnectAction.setEnabled(tryConnectionFailed);
    }
    if (commitAction != null) {
      commitAction.setEnabled(!tryConnectionFailed);
    }
    if (rollbackAction != null) {
      rollbackAction.setEnabled(!tryConnectionFailed);
    }
    if (refreshAction != null) {
      refreshAction.setEnabled(!tryConnectionFailed);
    }
    if (reinitAction != null) {
      reinitAction.setEnabled(!tryConnectionFailed);
    }
    if (commitAction != null) {
      commitAction.setEnabled(!tryConnectionFailed);
    }
    if (insertRowAction != null) {
      insertRowAction.setEnabled(!tryConnectionFailed);
    }
    if (makeConstraintAction != null) {
      makeConstraintAction.setEnabled(!tryConnectionFailed);
    }
  }

  Override
  SuppressWarnings("unused")
  protected void customizeChart() {
    if (CACHE_SIZE == 0) {
      // if no cache we can use auto increment
      ((IlvTableGanttModel) chart.getGanttModel()).configureHierarchyChart(chart, true);
    } else {
      // if there is a cache we can't because the database is not queried
      // each time a row is added!
      ((IlvTableGanttModel) chart.getGanttModel()).configureHierarchyChart(chart);
      // well actually due to Sun BR 6193078 we have to slightly change it,
      // otherwise we will get NPEs when commiting for the emtpy "originalID"
      // column
      chart.setActivityFactory(new ActivityFactory(chart.getActivityFactory()));
      chart.setResourceFactory(new ResourceFactory(chart.getResourceFactory()));
    }
    super.customizeChart();
    // Let's take the styling of the chart from a Gantt Designer Project File
    IlvGanttProjectConfiguration configuration = new IlvGanttProjectConfiguration();
    try {
      // Read the configuration file.
      URL url = getResourceURL("data/basicGantt.igpr");
      configuration.read(url);
      // get the style from the configuration file
      String[] stylesheets = configuration.getStyleSheets();
      // set the style from the configuration file on the chart itself
      getChart().setStyleSheets(stylesheets);
    } catch (MalformedURLException ex) {
      // cannot happen except if URL is changed
      IlvSwingUtil.showErrorDialog(this, ex);
    } catch (IOException e) {
      // cannot happen except if URL is changed
      IlvSwingUtil.showErrorDialog(this, e);
    } catch (IlvStylingException e) {
      // can happen if CSS content is changed...
      IlvSwingUtil.showErrorDialog(this, "Styling Exception Occured", e);
    }

  }
  // =========================================
  // Menu
  // =========================================

  /**
   * Initializes the actions for the Edit menu.
   */
  Override
  protected void initEditActions() {
    super.initEditActions();
    if (insertRowAction != null) {
      insertRowAction.setEnabled(!tryConnectionFailed);
    }
    if (makeConstraintAction != null) {
      makeConstraintAction.setEnabled(!tryConnectionFailed);
    }
    // indexes of activities are created from the ordering of the rows in the
    // database so remove leveling actions.
    rowDownAction = null;
    rowUpAction = null;
    // action for "Commit"
    commitAction = new JdbcGanttActions.CommitAction(this);
    // action for "Rollback"
    rollbackAction = new JdbcGanttActions.RollbackAction(this);
    // action for "Refresh"
    refreshAction = new JdbcGanttActions.RefreshAction(this);
    // action for "ReInit"
    reinitAction = new JdbcGanttActions.ReInitAction(this);
    // action for "ReConnect"
    reconnectAction = new JdbcGanttActions.ReConnectAction(this);
  }

  // Creates and installs the menu bar.
  Override
  public JMenuBar createMenuBar() {
    JMenuBar menu = super.createMenuBar();

    // Remove the Exit menu item from the Edit menu.
    if (isExitAllowed()) {
      JMenu edit = menu.getMenu(0);
      edit.remove(edit.getItemCount() - 1);
      edit.remove(edit.getItemCount() - 1);
    }

    // Add the File menu.
    menu.add(createDatabaseMenu(), 0);

    return menu;
  }

  /**
   * Creates the "Database" menu.
   */
  public JMenu createDatabaseMenu() {
    JMenu menu = new JMenu("Database");
    menu.setMnemonic(KeyEvent.VK_D);
    setStatusText(menu, "Database operations");

    addAction(menu, reconnectAction);
    menu.addSeparator();
    addAction(menu, commitAction);
    addAction(menu, rollbackAction);
    menu.addSeparator();
    addAction(menu, refreshAction);
    addAction(menu, reinitAction);

    // Menu item for "Exit".
    if (isExitAllowed()) {
      menu.addSeparator();
      addAction(menu, exitAction);
    }

    return menu;
  }

  // =========================================
  // Toolbar
  // =========================================

  /**
   * Populates the toolbar file actions.
   */
  Override
  protected void populateToolBarFileActions(JToolBar toolbar) {
    if (reconnectAction != null) {
      addAction(toolbar, reconnectAction);
    }
    if (commitAction != null) {
      addAction(toolbar, commitAction);
    }
    if (rollbackAction != null) {
      addAction(toolbar, rollbackAction);
    }
    if (refreshAction != null) {
      addAction(toolbar, refreshAction);
    }
    if (reinitAction != null) {
      addAction(toolbar, reinitAction);
    }
  }

  // ==========================================
  // Help Related Methods
  // ==========================================

  Override
  SuppressWarnings("unchecked")
  protected Map<String, Object> getHelpProperties() {
    return (Map<String, Object>) helpProperties;
  }

  Override
  protected void initHelpSystem() {
    helpProperties = new Properties();
    InputStream istream = getClass().getResourceAsStream("help/help.properties");
    if (istream != null) {
      try {
        ((Properties) helpProperties).load(istream);
      } catch (IOException e) {
        IlvSwingUtil.showErrorDialog(this, e);
        helpProperties.clear();
      }
    } else {
      IlvSwingUtil.showErrorDialog(this, "Help properties not found: help/help.properties");
    }
    super.initHelpSystem();
  }

  // =========================================
  // Factory Classes
  // =========================================

  private class ActivityFactory implements IlvActivityFactory {
    private IlvActivityFactory delegate;

    public ActivityFactory(IlvActivityFactory delegate) {
      this.delegate = delegate;
    }

    Override
    public IlvActivity createActivity(IlvTimeInterval interval) {
      IlvActivity activity = delegate.createActivity(interval);
      ((IlvTableActivity) activity).setProperty("originalID", "NA");
      return activity;
    }
  }

  private class ResourceFactory implements IlvResourceFactory {
    private IlvResourceFactory delegate;

    public ResourceFactory(IlvResourceFactory delegate) {
      this.delegate = delegate;
    }

    Override
    public IlvResource createResource() {
      IlvResource resource = delegate.createResource();
      ((IlvTableResource) resource).setProperty("originalID", "NA");
      return resource;
    }
  }

  // =========================================
  // Example Application
  // =========================================

  /**
   * Returns the title of the example.
   *
   * @return The title of the example.
   */
  Override
  public String getTitle() {
    return "Database Read/Write Gantt Chart Example";
  }

  /**
   * Application mainline.
   *
   * @param args
   *          The command line arguments.
   */
  public static void main(String[] args) {
    ExampleFrame.createAndShowGUI(JdbcGanttExample.class);
  }

}