/*
 * Mapping.java
 * Copyright (C) 2019 Guowei Chen <[email protected]>
 *
 * Distributed under terms of the GPL license.
 */

package cn.ac.amss.semanticweb.alignment;

import org.apache.jena.ontology.OntModel;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.StmtIterator;

import java.util.Set;
import java.util.HashSet;
import java.util.Objects;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;

public class Mapping implements Iterable<MappingCell>
{
  private Set<MappingCell> m_mapping = null;

  private OntModel m_source = null;
  private OntModel m_target = null;

  private Map<String, Integer> sourceEntity2Id = null;
  private Map<Integer, String> Id2SourceEntity = null;

  private Map<String, Integer> targetEntity2Id = null;
  private Map<Integer, String> Id2TargetEntity = null;

  private Map<Integer, Set<Integer>> sourceId2Targets = null;
  private Map<Integer, Set<Integer>> targetId2Sources = null;

  public Mapping() {
    m_mapping = new HashSet<>();

    sourceEntity2Id = new HashMap<>();
    Id2SourceEntity = new HashMap<>();

    targetEntity2Id = new HashMap<>();
    Id2TargetEntity = new HashMap<>();

    sourceId2Targets = new HashMap<>();
    targetId2Sources = new HashMap<>();
  }

  public boolean add(MappingCell c) {
    String e1 = c.getEntity1();
    String e2 = c.getEntity2();

    int i1 = sourceEntity2Id.getOrDefault(e1, sourceEntity2Id.size());
    sourceEntity2Id.put(e1, i1);

    int i2 = targetEntity2Id.getOrDefault(e2, targetEntity2Id.size());
    targetEntity2Id.put(e2, i2);

    if (!Id2SourceEntity.containsKey(i1)) Id2SourceEntity.put(i1, e1);

    if (!Id2TargetEntity.containsKey(i2)) Id2TargetEntity.put(i2, e2);

    Set<Integer> targets = sourceId2Targets.get(i1);
    if (targets == null) {
      sourceId2Targets.put(i1, new HashSet<>(Arrays.asList(i2)));
    } else {
      targets.add(i2);
    }

    Set<Integer> sources = targetId2Sources.get(i2);
    if (sources == null) {
      targetId2Sources.put(i2, new HashSet<>(Arrays.asList(i1)));
    } else {
      sources.add(i1);
    }

    return m_mapping.add(c);
  }

  public boolean addAll(Set<MappingCell> m) {
    boolean b = true;
    for (MappingCell mc : m) {
      b = (b && this.add(mc));
    }
    return b;
  }

  public boolean addAll(Mapping m) {
    return this.addAll(m.m_mapping);
  }

  public boolean removeAll(Mapping m) {
    return m_mapping.removeAll(m.m_mapping);
  }

  public boolean removeAll(Set<MappingCell> m) {
    return m_mapping.removeAll(m);
  }

  public boolean add(String entity1, String entity2) {
    return add(new MappingCell(entity1, entity2));
  }

  public boolean add(Resource resource1, Resource resource2) {
    return add(new MappingCell(resource1, resource2));
  }

  public boolean add(String entity1, String entity2, String relation, String confidence) {
    return add(new MappingCell(entity1, entity2, relation, confidence));
  }

  public int size() {
    return m_mapping.size();
  }

  public void setOntSourceTarget(OntModel source, OntModel target) {
    m_source = source;
    m_target = target;
  }

  public OntModel getSource() {
    return m_source;
  }

  public OntModel getTarget() {
    return m_target;
  }

  public final String getContent(int indent) {
    MappingCell.setIndent(indent);
    if (m_mapping == null) return "";

    StringBuilder content = new StringBuilder();
    for (MappingCell m : m_mapping) {
      content.append(m.getMappingCellElement());
    }
    return content.toString();
  }

  public final void clear() {
    m_mapping.clear();
  }

  public boolean isEmpty() {
    return m_mapping == null || m_mapping.isEmpty();
  }

  /**
   * Compute the closure of the mappings
   *
   * @return the closure of the mappings
   */
  public Mapping toClosure() {
    Mapping mClosure = new Mapping();

    Map<Integer, Set<Integer>> source2TargetsClosure = new HashMap<>();
    int n = sourceEntity2Id.size();

    for (int i = 0; i < n; ++i) {
      Set<Integer> sTargets = sourceId2Targets.get(i);
      if (sTargets == null) continue;

      Set<Integer> targets = new HashSet<>();

      Set<Integer> sources = new HashSet<>();
      for (int  t : sTargets) {
        Set<Integer> ss = targetId2Sources.get(t);
        if (ss == null) continue;
        sources.addAll(ss);
      }

      for (int s : sources) {
        Set<Integer> ts = sourceId2Targets.get(s);
        if (ts == null) continue;
        targets.addAll(ts);
      }

      source2TargetsClosure.put(i, targets);
    }

    for (Map.Entry<Integer, Set<Integer>> e : source2TargetsClosure.entrySet()) {
      int sid = e.getKey();
      for (int tid : e.getValue()) {
        String e1 = Id2SourceEntity.get(sid);
        String e2 = Id2TargetEntity.get(tid);
        if (e1 == null || e2 == null) continue;
        mClosure.add(e1, e2);
      }
    }

    return mClosure;
  }

  public String listMappingCellSPO(MappingCell mc) {
    if (mc == null || m_source == null || m_target == null) return "null";

    StringBuilder sb = new StringBuilder();
    Resource r1 = m_source.getResource(mc.getEntity1());
    sb.append(String.format("%n<<<<<<<"));
    sb.append(String.format("%n* %s%n", r1.getURI()));
    for (StmtIterator stmt = r1.listProperties(); stmt.hasNext(); ) {
      sb.append(String.format("** %s%n", stmt.nextStatement()));
    }

    sb.append(String.format("======="));

    Resource r2 = m_target.getResource(mc.getEntity2());
    sb.append(String.format("%n* %s%n", r2.getURI()));
    for (StmtIterator stmt = r2.listProperties(); stmt.hasNext(); ) {
      sb.append(String.format("** %s%n", stmt.nextStatement()));
    }
    sb.append(String.format(">>>>>>>%n"));

    return sb.toString();
  }

  @Override
  public Iterator<MappingCell> iterator() {
    return m_mapping.iterator();
  }

  @Override
  public String toString() {
    return getContent(0);
  }

  @Override
  public int hashCode() {
    return Objects.hash(m_mapping);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Mapping that = (Mapping) o;
    return Objects.equals(m_mapping, that.m_mapping);
  }
}