// Dstl (c) Crown Copyright 2017
package uk.gov.dstl.baleen.annotators.patterns.data;

import java.util.Collection;

import com.google.common.base.Strings;

import uk.gov.dstl.baleen.types.language.Interaction;
import uk.gov.dstl.baleen.types.language.WordToken;
import uk.gov.dstl.baleen.types.semantic.Entity;
import uk.gov.dstl.baleen.types.semantic.Relation;

/**
 * A constraint which applies to a relation, constraining its type, source entity type, and target
 * entity type.
 */
public class RelationConstraint {
  private final String type;
  private final String source;
  private final String target;
  private final String pos;
  private final String subType;
  private final char posChar;

  /**
   * Instantiates a new relation constraint.
   *
   * @param type the type
   * @param source the source
   * @param target the target
   */
  public RelationConstraint(
      final String type,
      final String subType,
      final String pos,
      final String source,
      final String target) {
    this.type = type;
    this.subType = subType;
    this.pos = pos;
    if (pos != null && pos.length() > 0) {
      this.posChar = Character.toLowerCase(pos.charAt(0));
    } else {
      this.posChar = '?';
    }
    this.source = source;
    this.target = target;
  }

  /**
   * Gets the source.
   *
   * @return the source
   */
  public String getSource() {
    return source;
  }

  /**
   * Gets the target.
   *
   * @return the target
   */
  public String getTarget() {
    return target;
  }

  /**
   * Gets the POS.
   *
   * @return the part of speech
   */
  public String getPos() {
    return pos;
  }

  /**
   * Gets the sub type.
   *
   * @return the sub type
   */
  public String getSubType() {
    return subType;
  }

  /**
   * Gets the type.
   *
   * @return the type
   */
  public String getType() {
    return type;
  }

  /**
   * Checks if this instance is valid.
   *
   * @return true, if is valid
   */
  public boolean isValid() {
    return !isNullOrEmpty(type, subType, pos, source, target);
  }

  /**
   * Returns true if any of the strings in args are null or empty
   *
   * @param args
   * @return
   */
  private boolean isNullOrEmpty(String... args) {
    for (String a : args) {
      if (Strings.isNullOrEmpty(a)) return true;
    }

    return false;
  }

  /**
   * Check is the relation provided matches this constraint.
   *
   * <p>No inheritence and matches the full (not short) type name.
   *
   * @param relation the relation
   * @param symmetric the symmetric (ie source and type can be swapped)
   * @return true, if successful
   */
  public boolean matches(final Relation relation, final boolean symmetric) {
    final Entity sourceEntity = relation.getSource();
    final Entity targetEntity = relation.getTarget();

    final String sourceType = sourceEntity.getType().getName();
    final String targetType = targetEntity.getType().getName();

    final boolean relationType =
        type.equalsIgnoreCase(relation.getRelationshipType())
            && subType.equalsIgnoreCase(relation.getRelationSubType());

    if (!symmetric) {
      return relationType && typesEqual(sourceType, targetType);
    } else {
      return relationType
          && (typesEqual(sourceType, targetType) || typesEqual(targetType, sourceType));
    }
  }

  private boolean typesEqual(String sourceType, String targetType) {
    return sourceType.equalsIgnoreCase(source) && targetType.equalsIgnoreCase(target);
  }

  /**
   * Check if the interaction provided matches this constraint
   *
   * @return true, if successful
   */
  public boolean matches(Interaction interaction, Collection<WordToken> words) {
    String interactionType = interaction.getRelationshipType();
    String interactionSubType = interaction.getRelationSubType();

    boolean typeMatch =
        type.equalsIgnoreCase(interactionType) && subType.equalsIgnoreCase(interactionSubType);
    if (words == null || words.isEmpty()) {
      return typeMatch;
    } else {
      return typeMatch
          && words.stream()
              .anyMatch(w -> posChar == Character.toLowerCase(w.getPartOfSpeech().charAt(0)));
    }
  }

  @Override
  public int hashCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + (source == null ? 0 : source.hashCode());
    result = PRIME * result + (target == null ? 0 : target.hashCode());
    result = PRIME * result + (type == null ? 0 : type.hashCode());
    result = PRIME * result + (subType == null ? 0 : subType.hashCode());
    result = PRIME * result + (pos == null ? 0 : pos.hashCode());

    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    final RelationConstraint other = (RelationConstraint) obj;
    if (source == null) {
      if (other.source != null) {
        return false;
      }
    } else if (!source.equals(other.source)) {
      return false;
    }
    if (target == null) {
      if (other.target != null) {
        return false;
      }
    } else if (!target.equals(other.target)) {
      return false;
    }
    if (type == null) {
      if (other.type != null) {
        return false;
      }
    } else if (!type.equals(other.type)) {
      return false;
    }
    if (subType == null) {
      if (other.subType != null) {
        return false;
      }
    } else if (!subType.equals(other.subType)) {
      return false;
    }
    if (pos == null) {
      if (other.pos != null) {
        return false;
      }
    } else if (!pos.equals(other.pos)) {
      return false;
    }
    return true;
  }

  @Override
  public String toString() {
    return this.source + "-" + this.type + "-" + this.target;
  }
}
