package datalog_ra.base.dataStructures;

import datalog_ra.base.operator.Union;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 *
 * @author Jakub
 */
public class Instance implements Iterable<Relation>{

  private HashMap<Key, Relation> relations = new HashMap<>();

  public Instance() {
  }
  
  public Relation get(String relationName, int arity) {
    return relations.get(new Key(relationName, arity));
  }

  /**
   * Same as retrieving a relation using get, but returns a new empty relation
   * if relation with this name and arity is undefined.
   */
  public Relation forceGet(String relationName, int arity) {
    Relation result = get(relationName, arity);
    if (result == null) {
      return new Relation(relationName, arity);
    }
    return result;
  }
  
  /**
   * Adds Relation newRelation. If a relation called name is allready present it
   * is NOT replaced.
   *
   * @return true if Relation was added and false otherwise
   */
  public boolean add(Relation newRelation) {
    Key key = new Key(newRelation.getName(), newRelation.getArity());
    if (relations.containsKey(key)) {
      return false;
    } else {
      relations.put(key, newRelation);
      return true;
    }
  }

  /**
   * Replaces the Relation called name with Relation relation or
   * adds it, if no such relation exists.
   *
   * @return true if a relation was replaced and false in new one was added.
   */
  public boolean replace(Relation relation) {
    return relations.put(new Key(relation.getName(), relation.getArity()), relation) != null;
  }

  /**
   * Extends the relation called name by provided relation. Or adds a new
   * relation called name to the instance if it did not exist before.
   *
   * @return true if an existing relation was changed and false if new one was
   * added
   */
  public boolean append(Relation relation) {
    Key key = new Key(relation.getName(), relation.getArity());
    Relation existing = relations.get(key);
    if (existing == null) {
      relations.put(key, relation);
      return false;
    }
    relations.put(key, new Relation(new Union(existing.operator(), relation.operator()), relation.getName()));
    return true;
  }

  public Instance copy() {
    Instance result = new Instance();
    for (Map.Entry<Key, Relation> relation : relations.entrySet()) {
      result.relations.put(relation.getKey(), relation.getValue().copy());
    }
    return result;
  }
  
  public int size() {
    return relations.size();
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == null) return false;
    if (obj == this) return true;
    if (!(obj instanceof Instance)) return false;
    Instance instance = (Instance)obj;
    return relations.equals(instance.relations);
  }

  public String toString() {
    StringBuilder result = new StringBuilder();

    for (Map.Entry<Key, Relation> entry : relations.entrySet()) {
      result.append(entry.getValue().toString());
      result.append('\n');
    }

    return result.toString();
  }

  @Override
  public Iterator<Relation> iterator() {
    return new InstanceIterator(this);
  }
  
  private class InstanceIterator implements Iterator {
    private Iterator<Map.Entry<Key, Relation>> iterator;
    public InstanceIterator(Instance instance) {
      iterator = instance.relations.entrySet().iterator();
    }
    
    @Override
    public boolean hasNext() {
      return iterator.hasNext();
    }

    @Override
    public Object next() {
      return iterator.next().getValue();
    }
  }

  private class Key {
    private final String name;
    private final int arity;

    public Key(String name, int arity) {
      this.name = name;
      this.arity = arity;
    }

    @Override
    public boolean equals (Object O) {
      final Key other = (Key)O;
      return name.equals(other.name) && arity == other.arity;
    }

    @Override
    public int hashCode() {
      return name.hashCode() ^ Integer.hashCode(arity);
    }
  }
}
