/* ====================================================================
 *
 * The ObjectStyle Group Software License, Version 1.0
 *
 * Copyright (c) 2005 The ObjectStyle Group,
 * and individual authors of the software.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        ObjectStyle Group (http://objectstyle.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "ObjectStyle Group" and "Cayenne"
 *    must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact andrus@objectstyle.org.
 *
 * 5. Products derived from this software may not be called "ObjectStyle"
 *    nor may "ObjectStyle" appear in their names without prior written
 *    permission of the ObjectStyle Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the ObjectStyle Group.  For more
 * information on the ObjectStyle Group, please see
 * <http://objectstyle.org/>.
 *
 */

package org.objectstyle.wolips.wodclipse.core.woo;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.objectstyle.wolips.bindings.wod.TypeCache;
import org.objectstyle.wolips.eomodeler.core.model.EOAttribute;
import org.objectstyle.wolips.eomodeler.core.model.EODataSource;
import org.objectstyle.wolips.eomodeler.core.model.EODataSourceFactory;
import org.objectstyle.wolips.eomodeler.core.model.EODatabaseDataSource;
import org.objectstyle.wolips.eomodeler.core.model.EODetailDataSource;
import org.objectstyle.wolips.eomodeler.core.model.EODisplayGroupSortOrdering;
import org.objectstyle.wolips.eomodeler.core.model.EOEntity;
import org.objectstyle.wolips.eomodeler.core.model.EOFetchSpecification;
import org.objectstyle.wolips.eomodeler.core.model.EOModelMap;
import org.objectstyle.wolips.eomodeler.core.model.EOModelVerificationFailure;
import org.objectstyle.wolips.eomodeler.core.model.EORelationship;
import org.objectstyle.wolips.eomodeler.core.model.EOSortOrdering;
import org.objectstyle.wolips.eomodeler.core.utils.IPropertyChangeSource;
import org.objectstyle.wolips.wodclipse.core.completion.WodParserCache;

public class DisplayGroup implements IPropertyChangeSource {

  public static final String NAME = "name";
  
  public static final String CLASS_NAME = "className";

  public static final String CLASS_NAME_INDEX = "classNameIndex";
  
  public static final String CLASS_NAME_LIST = "classNameList";

  public static final String QUALIFICATION_INDEX = "qualificationIndex";

  public static final String QUALIFICATION_LIST = "qualificationList";

  public static final String ENTITY_NAME = "entityName";

  public static final String ENTITY_LIST = "entityList";

  public static final String MASTER_ENTITY_NAME = "masterEntityName";

  public static final String DETAIL_KEY_NAME = "detailKeyName";

  public static final String DETAIL_KEY_LIST = "detailKeyList";

  public static final String SORT_LIST = "sortList";

  public static final String HAS_MASTER_DETAIL = "hasMasterDetail";

  public static final String SORT_ORDER = "sortOrder";

  public static final String SORT_ORDER_KEY = "sortOrderKey";

  public static final String FETCH_SPEC_LIST = "fetchSpecList";

  public static final String FETCH_SPEC_NAME = "fetchSpecName";

  public static final String ENTRIES_PER_BATCH = "entriesPerBatch";

  public static final String FETCHES_ON_LOAD = "fetchesOnLoad";

  public static final String SELECTS_FIRST_OBJECT = "selectsFirstObject";

  public static final String EDITING_CONTEXT = "editingContext";

  public static final String ASCENDING = "Ascending";

  public static final String DESCENDING = "Descending";

  public static final String NOT_SORTED = "Not Sorted";

  public static final String FETCH_SPEC_NONE = "<None>";

  public static final String[] SORT_OPTIONS = new String[] { ASCENDING, DESCENDING, NOT_SORTED, };

  private static final String QUALIFIER_PREFIX = "%@*";

  private static final String QUALIFIER_SUFFIX = "*%@";

  private static final String QUALIFIER_CONTAINS = "*%@*";

  private static final String[] QUALIFICATION_LABELS = new String[] { "Prefix", "Contains", "Suffix", };

  private static final String[] QUALIFICATION_FORMATS = new String[] { QUALIFIER_PREFIX, QUALIFIER_CONTAINS, QUALIFIER_SUFFIX, };

  private String _originalName;
  private String _name = "newDisplayGroup";
  private WooModel _wooModel;
  private int _qualificationIndex;
  private List<String> _entityList;
  private EOEntity _entity;
  private String _entityName;
  private EOEntity _masterEntity;
  private String _masterEntityName;
  private String _detailKeyName;
  private boolean _hasMasterDetail;
  private boolean _fetchesOnLoad;
  private String _className;
  private int _classNameIndex;
  private List<String> _classNameList;
  private String _qualifierFormat;
  private int _entriesPerBatch;
  private List<String> _localKeys;
  private boolean _selectsFirstObject;

  private PropertyChangeSupport _changeSupport;

  private EODataSource _dataSource;
  private EODatabaseDataSource _databaseDataSource;
  private EODetailDataSource _detailDataSource;
  private EOSortOrdering _sortOrder;
  private boolean _isSorted;

  public DisplayGroup(WooModel model) {
    _wooModel = model;
    _wooModel.getModelGroup();
    _databaseDataSource = new EODatabaseDataSource(_wooModel.getModelGroup());
    _detailDataSource = new EODetailDataSource(_wooModel.getModelGroup());
    _dataSource = _databaseDataSource;
    _qualificationIndex = 0;
    _qualifierFormat = QUALIFICATION_FORMATS[0];
    _className = "WODisplayGroup";
    _classNameIndex = getClassNameList().indexOf(_className);
    _isSorted = false;
    _hasMasterDetail = false;
    _changeSupport = new PropertyChangeSupport(this);
    _sortOrder = new EODisplayGroupSortOrdering();
    _selectsFirstObject = false;
  }

  public void addPropertyChangeListener(final PropertyChangeListener listener) {
    _changeSupport.addPropertyChangeListener(listener);
  }

  public void addPropertyChangeListener(final String name, final PropertyChangeListener listener) {
    _changeSupport.addPropertyChangeListener(name, listener);

  }

  protected void firePropertyChange(final String propertyName, final Object oldValue, final Object newValue) {
    if (oldValue != newValue || (oldValue != null && !oldValue.equals(newValue)) || (newValue != null && !newValue.equals(oldValue))) {
      _wooModel.markAsDirty();
      _changeSupport.firePropertyChange(propertyName, oldValue, newValue);
    }
  }
  
  public String getClassName() {
    return _className;
  }

  public int getClassNameIndex() {
    return _classNameIndex;
  }
  
  public List<String> getClassNameList() {
    if (_classNameList == null) {
      _classNameList = new ArrayList<String>();
      try {
        IJavaProject project = JavaCore.create(_wooModel.getProject());
        IType displayGroupType = project.findType("com.webobjects.appserver.WODisplayGroup");
        List<IType> subTypes = WodParserCache.getTypeCache().getSubtypesOfInProject(displayGroupType, project);
        for (IType type : subTypes) {
          _classNameList.add(type.getElementName());
        }
      } catch (JavaModelException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    return _classNameList;
  }
  
  public List<String> getDetailKeyList() {
    if (_masterEntity != null) {
      List<String> keyList = new ArrayList<String>();
      Set<EORelationship> relationships = _masterEntity.getRelationships();
      for (EORelationship relation : relationships) {
        if (relation.getToMany() != null && relation.getToMany().booleanValue()) {
          keyList.add(relation.getName());
        }
      }
      return keyList;
    }
    return null;
  }

  public String getDetailKeyName() {
    return _detailKeyName;
  }

  public String getEditingContext() {
    return _databaseDataSource.getEditingContext();
  }

  public EOEntity getEntity() {
    return _entity;
  }

  public List<String> getEntityList() {
    if (_entityList == null) {
      _entityList = new ArrayList<String>(_wooModel.getModelGroup().getNonPrototypeEntityNames());
    }
    return _entityList;
  }

  public String getEntityName() {
    return _entityName;
  }

  public int getEntriesPerBatch() {
    return _entriesPerBatch;
  }

  public boolean getFetchesOnLoad() {
    return _fetchesOnLoad;
  }

  public List<String> getFetchSpecList() {
    if (_entity != null) {
      try {
        Set<EOFetchSpecification> fetchSpecs = _entity.getSortedFetchSpecs();
        if (fetchSpecs.size() > 0) {
          List<String> fetchSpecList = new ArrayList<String>(fetchSpecs.size() + 1);
          fetchSpecList.add(FETCH_SPEC_NONE);

          for (EOFetchSpecification fspec : fetchSpecs) {
            fetchSpecList.add(fspec.getName());
          }
          return fetchSpecList;
        }
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
    return null;
  }

  public String getFetchSpecName() {
    String name = _databaseDataSource.getFetchSpecification().getName();
    return name;
  }

  public List<String> getLocalKeys() {
    return _localKeys;
  }

  public EOEntity getMasterEntity() {
    return _masterEntity;
  }

  public String getMasterEntityName() {
    return _masterEntityName;
  }

  public String getName() {
    return _name;
  }
  
  public String getOriginalName() {
    return _originalName;
  }

  public int getQualificationIndex() {
    return _qualificationIndex;
  }

  public String[] getQualificationList() {
    return QUALIFICATION_LABELS.clone();
  }

  public boolean getSelectsFirstObject() {
    return _selectsFirstObject;
  }

  public List<String> getSortList() {
    if (_entity != null) {
      Set<EOAttribute> attributes = _entity.getSortedClassAttributes();
      List<String> attribList = new ArrayList<String>(attributes.size());
      for (EOAttribute attribute : attributes) {
        attribList.add(attribute.getName());
      }
      return attribList;
    }
    return new ArrayList<String>();
  }

  public String getSortOrder() {
    String order = null;
    if (!_isSorted) {
      order = NOT_SORTED;
    }
    else {
      String selector = _sortOrder.getSelectorName();
      if (selector.equals(EOSortOrdering.SELECTOR_ASCENDING)) {
        order = ASCENDING;
      }
      if (selector.equals(EOSortOrdering.SELECTOR_DESCENDING)) {
        order = DESCENDING;
      }
    }
    return order;
  }

  public String getSortOrderKey() {
    return _sortOrder.getKey();
  }

  public WooModel getWooModel() {
    return _wooModel;
  }

  public boolean isHasMasterDetail() {
    return _hasMasterDetail;
  }

  @SuppressWarnings("unchecked")
  public void loadFromMap(final EOModelMap map, final Set<EOModelVerificationFailure> failures) {
    _originalName = _name;
    _className = map.getString("class", true);
    _localKeys = map.getList("localKeys");
    if (map.containsKey("numberOfObjectsPerBatch")) {
      _entriesPerBatch = map.getInteger("numberOfObjectsPerBatch");
    }
    if (map.containsKey("selectsFirstObjectAfterFetch")) {
      _selectsFirstObject = map.getBoolean("selectsFirstObjectAfterFetch");
    }
    if (map.containsKey("fetchesOnLoad")) {
      _fetchesOnLoad = map.getBoolean("fetchesOnLoad");
    }
    _qualifierFormat = map.getString("formatForLikeQualifier", true);

    List<String> qualificationFormatList = Arrays.asList(QUALIFICATION_FORMATS);
    _qualificationIndex = qualificationFormatList.indexOf(_qualifierFormat);

    if (map.containsKey("dataSource")) {
      EOModelMap dataSourceMap = new EOModelMap(map.getMap("dataSource"));
      try {
    	  EODataSource dataSource = EODataSourceFactory.createDataSourceFromMap(dataSourceMap, _wooModel.getModelGroup());
    	  dataSource.loadFromMap(dataSourceMap, failures);
    	  _dataSource = dataSource;
      } catch (Throwable e) {
    	  e.printStackTrace();
      }

      if (_dataSource instanceof EODetailDataSource) {
        EODetailDataSource ds = (EODetailDataSource) _dataSource;
        _hasMasterDetail = true;
        setMasterEntityName(ds.getMasterClass());
        setDetailKeyName(ds.getDetailKey());
        _detailDataSource = ds;
      }
      else if (_dataSource instanceof EODatabaseDataSource) {
        EODatabaseDataSource ds = (EODatabaseDataSource) _dataSource;
        _hasMasterDetail = false;
        setEntityName(ds.getEntityName());
        _databaseDataSource = ds;
      }

      if (_entityName == null) {
        // XXX Invalid display group
        if (dataSourceMap.containsKey("fetchSpecification")) {
          EOModelMap fetchSpecMap = new EOModelMap(dataSourceMap.getMap("fetchSpecification"));
          _entityName = fetchSpecMap.getString("entityName", true);
        }
        else if (dataSourceMap.containsKey("masterClassDescription")) {
          _entityName = dataSourceMap.getString("masterClassDescription", true);
        }
      }
    }

    List<Map<Object, Object>> sortOrderList = map.getList("sortOrdering");
    if (sortOrderList != null) {
      for (Map<Object, Object> sortOrdering : sortOrderList) {
        if (sortOrdering != null) {
          EOModelMap sortOrderingMap = new EOModelMap(sortOrdering);
          _sortOrder.loadFromMap(sortOrderingMap);
          _isSorted = true;
        }
      }
    }
  }

  public void removePropertyChangeListener(final PropertyChangeListener listener) {
    _changeSupport.removePropertyChangeListener(listener);
  }

  public void removePropertyChangeListener(final String name, final PropertyChangeListener listener) {
    _changeSupport.removePropertyChangeListener(name, listener);
  }
  
  public void setClassName(final String className) {
    String oldClass = getClassName();
    String _name = className;

    if ((className == null || !getClassNameList().contains(className))) {
      _name = "WODisplayGroup";
    }

    _className = _name;
    firePropertyChange(CLASS_NAME, oldClass, _name);
    setClassNameIndex(getClassNameList().indexOf(_name));
  }

  public void setClassNameIndex(final int index) {
    int oldIndex = _classNameIndex;
    _classNameIndex = index;
    _className = _classNameList.get(index);
    firePropertyChange(CLASS_NAME_INDEX, oldIndex, _classNameIndex);
  }  
  
  public void setDetailKeyName(final String key) {
    String oldDetailKeyName = _detailKeyName;
    _detailKeyName = key;
    if (key != null && isHasMasterDetail()) {
      EORelationship relation = _masterEntity.getRelationshipNamed(key);
      if (relation != null) {
        setEntity(relation.getDestination().getEntity());
      }
      else {
        setEntity(_masterEntity);
      }
    }

    _detailDataSource.setDetailKey(_detailKeyName);
    firePropertyChange(DETAIL_KEY_NAME, oldDetailKeyName, _detailKeyName);
  }

  public void setEditingContext(final String ec) {
    String oldEditingContext = _databaseDataSource.getEditingContext();
    _databaseDataSource.setEditingContext(ec);
    firePropertyChange(EDITING_CONTEXT, oldEditingContext, _databaseDataSource.getEditingContext());
  }

  private void setEntity(final EOEntity entity) {
    if (entity == null || !entity.equals(_entity)) {
      List<String> oldFetchSpecList = getFetchSpecList();
      List<String> oldSortList = getSortList();

      _entity = entity;
      if (_entity != null) {
        setEntityName(_entity.getName());
      }
      if (!isHasMasterDetail()) {
        setMasterEntity(entity);
      }

      _databaseDataSource.getFetchSpecification().setEntity(entity);
      firePropertyChange(FETCH_SPEC_LIST, oldFetchSpecList, getFetchSpecList());
      setFetchSpecName(null);

      firePropertyChange(SORT_LIST, oldSortList, getSortList());
      setSortOrderKey(null);
    }
  }

  public void setEntityName(final String entity) {
    String oldEntityName = _entityName;
    if (entity == null || !entity.equals(_entityName)) {
      _entityName = entity;
      firePropertyChange(ENTITY_NAME, oldEntityName, _entityName);
      try {
        EOEntity eoentity = _wooModel.getModelGroup().getEntityNamed(entity);
        setEntity(eoentity);
      }
      catch (Exception e) {
        e.printStackTrace();
        setEntity(null);
      }
    }
  }

  public void setEntriesPerBatch(final int entriesPerBatch) {
    int oldEntriesPerBatch = _entriesPerBatch;
    _entriesPerBatch = entriesPerBatch;
    firePropertyChange(ENTRIES_PER_BATCH, oldEntriesPerBatch, _entriesPerBatch);
  }

  public void setFetchesOnLoad(final boolean fetchesOnLoad) {
    boolean oldFetchesOnLoad = _fetchesOnLoad;
    _fetchesOnLoad = fetchesOnLoad;
    firePropertyChange(FETCHES_ON_LOAD, oldFetchesOnLoad, _fetchesOnLoad);
  }

  public void setFetchSpecName(final String fetchSpec) {
    String _fetchSpec = fetchSpec;
    String oldFetchSpecName = getFetchSpecName();
    EOFetchSpecification myFetchSpec = null;

    if (fetchSpec != null && _entity != null) {
      myFetchSpec = _entity.getFetchSpecNamed(fetchSpec);
    }

    if (myFetchSpec != null) {
      _databaseDataSource.setFetchSpecification(myFetchSpec);
    }
    else {
      EOFetchSpecification newFetchSpec = new EOFetchSpecification(null);
      newFetchSpec.setEntity(_entity);
      newFetchSpec.setDeep(true);
      _databaseDataSource.setFetchSpecification(newFetchSpec);
    }

    if (fetchSpec == null && getFetchSpecList() != null) {
      _fetchSpec = FETCH_SPEC_NONE;
    }

    firePropertyChange(FETCH_SPEC_NAME, oldFetchSpecName, _fetchSpec);
  }

  public void setHasMasterDetail(final boolean hasMasterDetail) {
    if (_hasMasterDetail == hasMasterDetail) {
      return;
    }
    boolean oldHasMasterDetail = _hasMasterDetail;
    _hasMasterDetail = hasMasterDetail;

    if (hasMasterDetail) {
      _dataSource = _detailDataSource;
    }
    else {
      _dataSource = _databaseDataSource;
    }

    firePropertyChange(HAS_MASTER_DETAIL, oldHasMasterDetail, _hasMasterDetail);
  }

  public void setLocalKeys(final List<String> localKeys) {
    // Unused
    _localKeys = localKeys;
  }

  private void setMasterEntity(final EOEntity entity) {
    List<String> oldDetailKeyList = getDetailKeyList();
    if (entity == null || !entity.equals(_masterEntity)) {
      _masterEntity = entity;
      if (_masterEntity != null) {
        setMasterEntityName(_masterEntity.getName());
      }

      if (isHasMasterDetail()) {
        setEntity(entity);
      }

      _detailDataSource.setMasterClass(_masterEntityName);

      firePropertyChange(DETAIL_KEY_LIST, oldDetailKeyList, getDetailKeyList());
    }
  }

  public void setMasterEntityName(final String entity) {
    String oldMasterEntityName = _masterEntityName;

    if (entity == null || !entity.equals(_masterEntityName)) {
      _masterEntityName = entity;
      firePropertyChange(MASTER_ENTITY_NAME, oldMasterEntityName, _masterEntityName);
      try {
        EOEntity eoentity = _wooModel.getModelGroup().getEntityNamed(entity);
        setMasterEntity(eoentity);
      }
      catch (Exception e) {
        setMasterEntity(null);
      }
    }
  }
  
  public void setName(final String name) {
    String oldName = _name;
    _name = name;
    firePropertyChange(NAME, oldName, _name);
  }

  public void setQualificationIndex(final int qualification) {
    int oldQualificationIndex = _qualificationIndex;
    _qualificationIndex = qualification;
    _qualifierFormat = QUALIFICATION_FORMATS[_qualificationIndex];
    firePropertyChange(QUALIFICATION_INDEX, oldQualificationIndex, _qualificationIndex);
  }

  public void setSelectsFirstObject(final boolean value) {
    boolean oldSelectsFirstObject = _selectsFirstObject;
    _selectsFirstObject = value;
    firePropertyChange(SELECTS_FIRST_OBJECT, oldSelectsFirstObject, _selectsFirstObject);
  }

  public void setSortOrder(final String order) {
    String oldSortOrder = getSortOrder();
    if (order.equals(ASCENDING)) {
      _sortOrder.setSelectorName(EOSortOrdering.SELECTOR_ASCENDING);
      _isSorted = true;
    }
    if (order.equals(DESCENDING)) {
      _sortOrder.setSelectorName(EOSortOrdering.SELECTOR_DESCENDING);
      _isSorted = true;
    }
    if (order.equals(NOT_SORTED)) {
      _isSorted = false;
    }
    firePropertyChange(SORT_ORDER, oldSortOrder, getSortOrder());
  }

  public void setSortOrderKey(final String key) {
    String oldSortOrderKey = getSortOrderKey();
    String _key = key;

    if (key == null && getSortList().size() > 0) {
      _key = getSortList().get(0);
    }

    _sortOrder.setKey(_key);
    firePropertyChange(SORT_ORDER_KEY, oldSortOrderKey, _key);
  }

  public void setWooModel(final WooModel model) {
    _wooModel = model;
  }

  public EOModelMap toMap() {
    // XXX Need to validate model before saving
    if (_entity == null) {
      //return null;
    }
    EOModelMap modelMap = new EOModelMap();
    modelMap.setString("class", _className, true);
    modelMap.setMap("dataSource", _dataSource.toMap(), true);
    modelMap.setBoolean("fetchesOnLoad", _fetchesOnLoad, EOModelMap.YESNO);
    modelMap.setString("formatForLikeQualifier", _qualifierFormat, true);
    modelMap.setList("localKeys", _localKeys, true);
    modelMap.setInteger("numberOfObjectsPerBatch", _entriesPerBatch);
    modelMap.setBoolean("selectsFirstObjectAfterFetch", _selectsFirstObject, EOModelMap.YESNO);
    if (_isSorted) {
      List<Object> sortOrderingList = new ArrayList<Object>();
      sortOrderingList.add(_sortOrder.toMap());
      modelMap.setList("sortOrdering", sortOrderingList, true);
    }
    _originalName = _name;
    return modelMap;
  }
}
