package org.apache.rya.reasoning;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Resource;

/**
 * Contains all the schema information we might need about a property.
 *
 * Rules implemented dynamically by getter methods:
 *
 * scm-op   States that every object property is its own subproperty and
 *          equivalent property.
 * scm-dp   States that every datatype property is its own subproperty and
 *          equivalent property.
 * scm-eqp1 States that if two properties are equivalent, they are also
 *          subproperties. Equivalence is represented this way internally.
 * scm-eqp2 States that if two properties are each other's subproperties,
 *          they are also equivalent properties.
 *
 * Rules implemented explicitly by calling methods once all the necessary data
 * has been read in:
 *
 * scm-spo  States that subPropertyOf is transitive; computed by
 *          computeSuperProperties().
 * scm-dom1 States that a property with domain c also has as domain any
 *          of c's superclasses. Computed in inheritDomainRange().
 * scm-dom2 States that a property inherits its superproperties' domains.
 *          Computed in inheritDomainRange().
 * scm-rng1 States that a property with range c also has as domain any
 *          of c's superclasses. Computed in inheritDomainRange().
 * scm-rng2 States that a property inherits its superproperties' ranges.
 *          Computed in inheritDomainRange().
 * svm-svf1 States that property restriction c1 is a subclass of another c2 if
 *          they are someValuesFrom restrictions on the same property where
 *          c1's target class is a subclass of c2's target class.
 *          Computed in compareRestrictions(), depends on subclass info.
 * svm-avf1 States that property restriction c1 is a subclass of another c2 if
 *          they are allValuesFrom restrictions on the same property where
 *          c1's target class is a subclass of c2's target class.
 *          Computed in compareRestrictions(), depends on subclass info.
 */
public class OwlProperty implements Serializable {
    private static final long serialVersionUID = 1L;

    private IRI iri;

    // Boolean qualities the property might have
    private boolean transitive = false;
    private boolean symmetric = false;
    private boolean asymmetric = false;
    private boolean functional = false;
    private boolean inverseFunctional = false;
    private boolean irreflexive = false;

    // Relations to other properties
    private Set<OwlProperty> superProperties = new HashSet<OwlProperty>();
    private Set<OwlProperty> disjointProperties = new HashSet<OwlProperty>();
    private Set<OwlProperty> inverseProperties = new HashSet<OwlProperty>();

    // Relations to classes
    private Set<OwlClass> domain = new HashSet<OwlClass>();
    private Set<OwlClass> range = new HashSet<OwlClass>();

    // Restrictions on this property
    private Set<OwlClass> restrictions = new HashSet<OwlClass>();

    OwlProperty(IRI iri) {
        this.iri = iri;
    }

    boolean addSuperProperty(OwlProperty p) {
        return superProperties.add(p);
    }

    /**
     * Add an equivalient property
     * RL rule scm-eqp1: Store equivalence as mutual subPropertyOf relations
     */
    boolean addEquivalentProperty(OwlProperty p) {
        boolean change = this.superProperties.add(p);
        change = p.superProperties.add(this) || change;
        return change;
    }

    boolean addDisjoint(OwlProperty p) { return disjointProperties.add(p); }
    boolean addInverse(OwlProperty p) { return inverseProperties.add(p); }
    boolean addDomain(OwlClass c) {
        return domain.add(c);
    }
    boolean addRange(OwlClass c) {
        return range.add(c);
    }
    boolean addRestriction(OwlClass r) { return restrictions.add(r); }

    public void setURI(IRI iri) { this.iri = iri; }
    void setTransitive() { transitive = true; }
    void setSymmetric() { symmetric = true; }
    void setAsymmetric() { asymmetric = true; }
    void setFunctional() { functional = true; }
    void setInverseFunctional() { inverseFunctional = true; }
    void setIrreflexive() { irreflexive = true; }

    public IRI getURI() { return iri; }
    public boolean isTransitive() { return transitive; }
    public boolean isSymmetric() { return symmetric; }
    public boolean isAsymmetric() { return asymmetric; }
    public boolean isFunctional() { return functional; }
    public boolean isInverseFunctional() { return inverseFunctional; }
    public boolean isIrreflexive() { return irreflexive; }

    /**
     * Apply RL rule scm-spo: subPropertyOf transitivity.
     * Follows subPropertyOf chains to compute all the ancestor properties.
     * Assumes the hierarchy is small enough that a simple BFS is fine.
     * @return  Whether new information was discovered
     */
    boolean computeSuperProperties() {
        Set<OwlProperty> ancestors = new HashSet<OwlProperty>();
        Set<OwlProperty> frontier = new HashSet<OwlProperty>(superProperties);
        while (!frontier.isEmpty()) {
            Set<OwlProperty> next = new HashSet<OwlProperty>();
            for (OwlProperty ancestor : frontier) {
                // This node is an ancestor; it's parents should be explored next
                ancestors.add(ancestor);
                next.addAll(ancestor.superProperties);
            }
            // Don't revisit nodes
            next.removeAll(ancestors);
            frontier = next;
        }
        boolean newInfo = !ancestors.equals(superProperties);
        superProperties = ancestors;
        return newInfo;
    }

    /**
     * Infer domain and range from parents' domains and ranges. Only looks at
     * those superproperties that are explicitly known; computeSuperProperties
     * should generally be called first to get the full closure.
     */
    void inheritDomainRange() {
        //RL rule scm-dom2: (p2 domain c) && (p1 subPropertyOf p2) -> (p1 domain c)
        //RL rule scm-rng2: (p2 range c) && (p1 subPropertyOf p2) -> (p1 range c)
        for (OwlProperty ancestorProperty : superProperties) {
            for (OwlClass c : ancestorProperty.domain) {
                addDomain(c);
            }
            for (OwlClass c : ancestorProperty.range) {
                addRange(c);
            }
        }
        //RL rule scm-dom1: (p domain c1) && (c1 subClassOf c2) -> (p domain c2)
        for (OwlClass domainClass : new HashSet<OwlClass>(this.domain)) {
            domainClass.inheritDomains(this);
        }
        //RL rule scm-rng1: (p range c1) && (c1 subClassOf c2) -> (p range c2)
        for (OwlClass rangeClass : new HashSet<OwlClass>(this.range)) {
            rangeClass.inheritRanges(this);
        }
    }

    /**
     * Get all the superproperties of this subproperty.
     */
    public Set<IRI> getSuperProperties() {
        Set<IRI> ancestors = new HashSet<>();
        for (OwlProperty ancestor : superProperties) {
            ancestors.add(ancestor.iri);
        }
        // RL rules scm-op & scm-dp: Every property is a subproperty of itself
        ancestors.add(this.iri);
        return ancestors;
    }

    /**
     * Get all the equivalent properties for this property.
     * Apply RL rules scm-op and scm-dp: Every property is its own equivalent.
     */
    public Set<IRI> getEquivalentProperties() {
        Set<IRI> equivalents = new HashSet<>();
        for (OwlProperty other : superProperties) {
            if (other.superProperties.contains(this)) {
                equivalents.add(other.iri);
            }
        }
        // RL rules scm-op & scm-dp: Every property is equivalent to itself
        equivalents.add(this.iri);
        return equivalents;
    }

    /**
     * Get all properties declared disjoint with this one.
     */
    public Set<IRI> getDisjointProperties() {
        Set<IRI> disjoint = new HashSet<>();
        for (OwlProperty other : disjointProperties) {
            disjoint.add(other.iri);
        }
        return disjoint;
    }

    /**
     * Get all properties declared inverse of this one.
     */
    public Set<IRI> getInverseProperties() {
        Set<IRI> inverse = new HashSet<>();
        for (OwlProperty other : inverseProperties) {
            inverse.add(other.iri);
        }
        return inverse;
    }

    /**
     * Get the domain (set of class IRIs/Resources).
     */
    public Set<Resource> getDomain() {
        Set<Resource> domain = new HashSet<>();
        for (OwlClass d : this.domain) {
            domain.add(d.getURI());
        }
        return domain;
    }

    /**
     * Get the range (set of class URIs/Resources).
     */
    public Set<Resource> getRange() {
        Set<Resource> range = new HashSet<>();
        for (OwlClass r : this.range) {
            range.add(r.getURI());
        }
        return range;
    }

    /**
     * Get the property restrictions relevant to this property.
     */
    public Set<Resource> getRestrictions() {
        Set<Resource> restrictions = new HashSet<>();
        for (OwlClass pr : this.restrictions) {
            restrictions.add(pr.getURI());
        }
        return restrictions;
    }

    /**
     * Apply property restriction rules that involve the same property.
     * RL rule scm-svf1: [someValuesFrom sub] subClassOf [someValuesFrom super]
     * RL rule scm-avf1: [allValuesFrom sub] subClassOf [allValuesFrom super]
     * Doesn't check subclass transitivity.
     * @return  Whether any information was generated
     */
    public boolean compareRestrictions() {
        boolean newInfo = false;
        for (OwlClass c1 : restrictions) {
            // Get all superclasses of this restriction's avf target class(es)
            // and avf target classes.
            Set<Resource> avfSuperClasses = c1.getAvfSuperClasses();
            Set<Resource> svfSuperClasses = c1.getSvfSuperClasses();
            if (avfSuperClasses.isEmpty() && svfSuperClasses.isEmpty()) {
                continue;
            }
            // Compare each other restriction's avf and svf targets to the
            // appropriate set of superclasses.
            for (OwlClass c2 : restrictions) {
                //svm-svf1, svm-avf1
                if (c1 != c2) {
                    Set<Resource> avf2 = c2.allValuesFrom();
                    avf2.retainAll(avfSuperClasses);
                    if (avf2.isEmpty()) {
                        Set<Resource> svf2 = c2.someValuesFrom();
                        svf2.retainAll(svfSuperClasses);
                        if (svf2.isEmpty()) {
                            continue;
                        }
                    }
                    // If c2's target is one of the parent classes of c1's
                    // target, then c2 is a more general version of c1 and
                    // therefore a superclass of c1.
                    newInfo = c1.addSuperClass(c2) || newInfo;
                }
            }
        }
        return newInfo;
    }
}
