/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2015 
 * © 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 monitoring.web.model.integration;

import ilog.cpl.datasource.DataSourceEvent;
import ilog.cpl.datasource.DataSourceListener;
import ilog.cpl.datasource.DataSourceObjectEvent;
import ilog.cpl.datasource.IlpDefaultDataSource;
import ilog.cpl.model.IlpObject;
import ilog.cpl.util.IlpFilter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import monitoring.web.model.integration.beans.TGOObject;

/**
 * Base class for implementations of <code>IntegratedDataModelProvider</code> 
 * that is used in this sample to integrated the TGO data model with MyFaces' 
 * data models.
 */
abstract public class AbstractIntegratedDataModelProvider implements IntegratedDataModelProvider {

  /**
   * The TGO data source.
   */
  protected IlpDefaultDataSource dataSource;
  /**
   * The integrated data model.
   */
  protected Object dataModel;
  /**
   * The filter used to prevent objects from being integrated.
   */
  protected IlpFilter filter;
  /**
   * Listener used to be notified of changes in the data source so that we can 
   * have a "live" integrated model. 
   */
  protected DataSourceChangeListener dataSourceChangeListener;
  /**
   * Map used to store the mapping between TGO model objects and 
   * integrated model objects.
   */
  protected Map<IlpObject,TGOObject> tgoToModelObjectMap;
  /**
   * Comparator used to sort business objects (IlpObject) 
   */
  private Comparator<IlpObject> compareIlp = new Comparator<IlpObject>() {
    public int compare(IlpObject object1, IlpObject object2) {
      // Compare IlpObject's 'name' attribute, which is a String
      if (null == object1) {
        return null == object2 ? 0 : -1;
      } else {
        Object name1 = object1.getAttributeValue("name");
        Object name2 = object2.getAttributeValue("name");
        if (null == name1) {
          return null == name2 ? 0 : -1;
        } else if (null == name2) {
          return 1;
        } else {
          return name1.toString().compareTo(name2.toString());
        }
      }
    }
  };

  /**
   * Creates internal structures. 
   */
  public AbstractIntegratedDataModelProvider() {
    dataSourceChangeListener = new DataSourceChangeListener();
    tgoToModelObjectMap = new HashMap<IlpObject,TGOObject>();
  }

  //////////////////////////////////////////////////////////////////////////////
  //IntegratedDataModelProvider Implementation
  //////////////////////////////////////////////////////////////////////////////

  /**
   * Implementation that attaches the appropriate listeners on the provided 
   * data source. Calling this method automatically refreshes the integrated model.
   * <p>
   * <code>null</code> is valid value.
   * 
   * @see monitoring.web.model.integration.IntegratedDataModelProvider#setDataSource(ilog.cpl.datasource.IlpDefaultDataSource)
   */
  public void setDataSource(IlpDefaultDataSource dataSource) {

    if (this.dataSource != null) {

      //Remove our current listener
      dataSource.removeDataSourceListener(dataSourceChangeListener);
      //Clear old data source
      this.dataSource.clear();
    }

    //Save the new datasource
    this.dataSource = dataSource;

    if (this.dataSource != null) {

      //Start listening on the new datasource
      this.dataSource.addDataSourceListener(dataSourceChangeListener);

      //refresh the data model
      refreshModel();
    } else {

      //simply empty the model
      emptyModel();
    }
  }

  /**
   * @see monitoring.web.model.integration.IntegratedDataModelProvider#getDataSource()
   */
  public IlpDefaultDataSource getDataSource() {
    return dataSource;
  }

  /**
   * Automatically refreshes the integrated model.
   * <p>
   * <code>null</code> is a valid value and indicates that no filtering should be done.
   * 
   * @see monitoring.web.model.integration.IntegratedDataModelProvider#setFilter(ilog.cpl.util.IlpFilter)
   */
  public void setFilter(IlpFilter filter) {

    if (this.filter != filter) {

      //Save the new filter
      this.filter = filter;

      //Refresh the data model regardeless if the filter is null
      //filter == null => all objects are shown
      refreshModel();
    }
  }

  /**
   * @see monitoring.web.model.integration.IntegratedDataModelProvider#getFilter()
   */
  public IlpFilter getFilter() {
    return filter;
  }

  /** 
   * @see monitoring.web.model.integration.IntegratedDataModelProvider#getDataModel()
   */
  public Object getDataModel() {
    return dataModel;
  }

  /**
   * @see monitoring.web.model.integration.IntegratedDataModelProvider#getModelObject(ilog.cpl.model.IlpObject)
   */
  public TGOObject getModelObject(IlpObject tgoObject) {
    return tgoToModelObjectMap.get(tgoObject);
  }

  //////////////////////////////////////////////////////////////////////////////
  //Subclass Methods
  //////////////////////////////////////////////////////////////////////////////

  /**
   * Filters the provided collection of objects using the current filter. It
   * returns a list of objects that excludes the objects that were filtered out.
   */
  protected List<IlpObject> filterObjects(Collection<IlpObject> objects) {
    List<IlpObject> filteredObjects = new ArrayList<IlpObject>();
    if (getFilter() == null) {
      filteredObjects.addAll(objects);
    } else if (objects != null) {
      for (IlpObject object : objects) {
        if (filter.accept(object)) {
          filteredObjects.add(object);
        }
      }
    }
    // Sort list
    Collections.sort(filteredObjects, compareIlp);
    return filteredObjects;
  }

  /**
   * Empties the internal structures that map the TGO model objects to the 
   * integrated model objects.
   * <p>
   * It does not modify the actual integrated model. 
   */
  protected void emptyModel() {
    tgoToModelObjectMap.clear();
  }

  /**
   * Stores the mapping between the TGO model object and the integrated 
   * model object.
   */
  protected void setTGOObjectForModelObject(IlpObject tgoObject, TGOObject modelObject) {
    tgoToModelObjectMap.put(tgoObject, modelObject);
  }

  /**
   * Listens on changes in the underlying datasource so that the model can be 
   * maintained in synch with the datasource.
   * <p>
   * This implementation is for the purposes of a demo, therefore not optimal.
   */
  class DataSourceChangeListener implements DataSourceListener {

    /**
     * Refresh the model.
     */
    public void batchEnded(DataSourceEvent e) {
      refreshModel();
    }
    /**
     * Do nothing.
     */
    public void batchStarted(DataSourceEvent e) {
    }
    /**
     * Do nothing. The integrated model objects in this sample are simply wrappers
     * of the TGO model objects. So attribute changes are made visible instantly.
     */
    public void objectAttributeChanged(DataSourceObjectEvent e) {
    }
    /**
     * Refresh the model.
     */
    public void objectsAdded(DataSourceEvent e) {
      refreshModel();
    }
    /**
     * Refresh the model.
     */
    public void objectsRemoved(DataSourceEvent e) {
      refreshModel();
    }
    /**
     * Refresh the model.
     */
    public void objectStructureChanged(DataSourceObjectEvent e) {
      refreshModel();
    }
  }
}