/*
  Copyright (C) 2006 Helge Hess

  This file is part of JOPE.

  JOPE 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; either version 2, or (at your option) any
  later version.

  JOPE 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 JOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

package org.opengroupware.jope.eoaccess;



/*
 * EORelationship
 * 
 * TODO: complete me
 */
public class EORelationship extends EOProperty {

  protected String   name;
  protected EOEntity entity;
  protected EOEntity destinationEntity;
  protected String   destinationEntityName;
  protected EOJoin[] joins;
  protected int      joinSemantic;
  protected boolean  isToMany;
  protected String   relationshipPath;
  
  /* construction */
  
  public EORelationship
    (String _name, boolean _isToMany, EOEntity _src, String _dest,
     EOJoin[] _joins)
  {
    this.name                  = _name;
    this.entity                = _src;
    this.destinationEntityName = _dest;
    this.joins                 = _joins;
    this.isToMany              = _isToMany;
  }
  
  /* accessors */
  
  public String name() {
    return this.name;
  }
  public boolean isToMany() {
    return this.isToMany;
  }
 
  public EOEntity entity() {
    return this.entity;
  }
  
  public EOEntity destinationEntity() {
    return this.destinationEntity;
  }
  
  public EOJoin[] joins() {
    return this.joins;
  }
  
  public int joinSemantic() {
    return this.joinSemantic;
  }
  
  public boolean isCompound() {
    if (this.joins == null) return false;
    return this.joins.length > 1;
  }

  public boolean isFlattened() {
    return this.relationshipPath != null;
  }
  public String relationshipPath() {
    return this.relationshipPath != null ? this.relationshipPath : this.name();
  }
  
  public EORelationship[] componentRelationships() {
    if (this.relationshipPath == null)
      return null;
    
    EOEntity relentity = this.entity();
    if (relentity == null)
      return null;
    
    String[]         path = this.relationshipPath.split(KeyPathSeparatorRegEx);
    EORelationship[] relships = new EORelationship[path.length]; 
    for (int i = 0; i < path.length; i++) {
      if (relentity == null)
        return null; // TBD: log
      
      relships[i] = relentity.relationshipNamed(path[i]);
      if (relships[i] == null) // TBD: log
        return null;
      
      if (relships[i].isFlattened())
        ; // TBD: pathes containing flattened relships
      
      relentity = relships[i].destinationEntity();
    }
    
    return relships;
  }
  
  public boolean referencesProperty(Object _property) {
    // TODO: look into data-path for flattened relationships
    if (_property == null)
      return false;
    
    if (this.joins != null) {
      for (int i = 0; i < this.joins.length; i++) {
        if (this.joins[i].referencesProperty(_property))
          return true;
      }
    }
    return false;
  }

  public boolean isConnected() {
    return this.destinationEntity != null;
  }
  public void connectRelationshipsInModel(EOModel _model, EOEntity _entity) {
    this.entity = _entity;
    
    if (this.destinationEntityName == null)
      return;
    
    this.destinationEntity = _model.entityNamed(this.destinationEntityName);
    
    if (this.joins != null) {
      for (int i = 0; i < this.joins.length; i++)
        this.joins[i].connectToEntities(this.entity, this.destinationEntity);
    }
  }
  
  /**
   * Locates an inverse relationship in the destination entity.
   * 
   * Example: n:1
   *   person  ( person_id, company_id )
   *   company ( company_id )
   * Person:
   *   toCompany [toOne] ( SRC.company_id = TAR.company_id )
   * Company:
   *   toPerson [toMany] ( SRC.company_id = TAR.company_id )
   * 
   * @return the inverse relationship
   */
  public EORelationship inverseRelationship() {
    // TBD: implement me
    // TBD: consider N:M relationships
    // find a relationship in the target which joins the same columns
    EOJoin[] myJoins = this.joins();
    if (myJoins == null || myJoins.length == 0)
      return null; /* we have no joins?! */
    if (myJoins.length > 1) {
      // TBD: logger
      System.err.println
        ("ERROR: not supporting inverse relationships with multiple joins yet");
      return null;
    }
    
    EOEntity myEntity = this.entity();
    if (myEntity == null)
      return null; /* we have no entity? */
    
    EOEntity dest = this.destinationEntity();
    if (dest == null) return null; // TBD: log?
    
    EORelationship[] rels = dest.relationships();
    if (rels == null || rels.length == 0) return null; /* none found */
    
    for (EORelationship rel: rels) {
      if (myEntity != rel.destinationEntity())
        continue; /* other entity, does not point back */
      
      EOJoin[] relJoins = rel.joins();
      if (relJoins == null || relJoins.length != this.joins.length)
        continue; /* join array sizes do not match */
      
      // TBD: we only support one join for now ...
      EOJoin myJoin = myJoins[0];
      EOJoin enemy  = relJoins[0];
      
      // TBD: equality might not be correct since joins can be directed
      //      (relevant for LEFT/RIGHT joins I guess)
      if (myJoin == enemy || myJoin.equals(enemy) ||
          myJoin.isReciprocalToJoin(enemy))
        return rel;
    }
    
    return null;
  }
  
  /* patterns */
  
  public boolean isPatternRelationship() {
    return false;
  }
  
  /* names */
  
  public void beautifyNames() {
    // TODO
  }
  
  /* constants */
  
  static final int FullOuterJoin  = 1;
  static final int InnerJoin      = 2;
  static final int LeftOuterJoin  = 3;
  static final int RightOuterJoin = 4;
  
  /* description */
  
  public void appendAttributesToDescription(StringBuilder _d) {
    super.appendAttributesToDescription(_d);

    if (this.isPatternRelationship())
      _d.append(" pattern");
    
    if (this.name != null) _d.append(" name=" + this.name);
    
    if (this.entity != null) 
      _d.append(" from=" + this.entity.name());
    if (this.destinationEntity != null)
      _d.append(" to=" + this.destinationEntity.name());
    
    if (this.joins != null && this.joins.length > 0) {
      _d.append(" join=");
      for (EOJoin join: this.joins) {
        _d.append('[');
        
        EOAttribute a = join.sourceAttribute();
        _d.append(a != null ? a.name() : "null");
        
        _d.append("=>");
        
        a = join.destinationAttribute();
        _d.append(a != null ? a.name() : "null");
        
        _d.append(']');
      }
    }
  }
}
