/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.
 */
package org.apache.atlas.model.glossary;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonValue;
import org.apache.atlas.model.annotation.AtlasJSON;
import org.apache.atlas.model.glossary.relations.AtlasGlossaryHeader;
import org.apache.atlas.model.glossary.relations.AtlasRelatedTermHeader;
import org.apache.atlas.model.glossary.relations.AtlasTermCategorizationHeader;
import org.apache.atlas.model.instance.AtlasRelatedObjectId;
import org.apache.atlas.type.AtlasType;
import org.apache.commons.collections.CollectionUtils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static java.util.Objects.requireNonNull;

@AtlasJSON
public class AtlasGlossaryTerm extends AtlasGlossaryBaseObject {
    // Core attributes
    private List<String> examples;
    private String       abbreviation;
    private String       usage;

    // Attributes derived from relationships
    private AtlasGlossaryHeader                anchor;
    private Set<AtlasRelatedObjectId>          assignedEntities;
    private Set<AtlasTermCategorizationHeader> categories;

    // Related Terms
    private Set<AtlasRelatedTermHeader> seeAlso;

    // Term Synonyms
    private Set<AtlasRelatedTermHeader> synonyms;

    // Term antonyms
    private Set<AtlasRelatedTermHeader> antonyms;

    // Term preference
    private Set<AtlasRelatedTermHeader> preferredTerms;
    private Set<AtlasRelatedTermHeader> preferredToTerms;

    // Term replacements
    private Set<AtlasRelatedTermHeader> replacementTerms;
    private Set<AtlasRelatedTermHeader> replacedBy;

    // Term translations
    private Set<AtlasRelatedTermHeader> translationTerms;
    private Set<AtlasRelatedTermHeader> translatedTerms;

    // Term classification
    private Set<AtlasRelatedTermHeader> isA;
    private Set<AtlasRelatedTermHeader> classifies;

    // Values for terms
    private Set<AtlasRelatedTermHeader> validValues;
    private Set<AtlasRelatedTermHeader> validValuesFor;

    private boolean hasTerms;

    public AtlasGlossaryTerm() {
    }

    public AtlasGlossaryTerm(final AtlasGlossaryTerm other) {
        super(other);
        this.examples         = other.examples;
        this.abbreviation     = other.abbreviation;
        this.usage            = other.usage;
        this.anchor           = other.anchor;
        this.assignedEntities = other.assignedEntities;
        this.categories       = other.categories;
        this.seeAlso          = other.seeAlso;
        this.synonyms         = other.synonyms;
        this.antonyms         = other.antonyms;
        this.preferredTerms   = other.preferredTerms;
        this.preferredToTerms = other.preferredToTerms;
        this.replacementTerms = other.replacementTerms;
        this.replacedBy       = other.replacedBy;
        this.translationTerms = other.translationTerms;
        this.translatedTerms  = other.translatedTerms;
        this.isA              = other.isA;
        this.classifies       = other.classifies;
        this.validValues      = other.validValues;
        this.validValuesFor   = other.validValuesFor;
        this.hasTerms         = other.hasTerms;
    }

    public List<String> getExamples() {
        return examples;
    }

    public void setExamples(final List<String> examples) {
        this.examples = examples;
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public void setAbbreviation(final String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getUsage() {
        return usage;
    }

    public void setUsage(final String usage) {
        this.usage = usage;
    }

    public AtlasGlossaryHeader getAnchor() {
        return anchor;
    }

    public void setAnchor(final AtlasGlossaryHeader anchor) {
        this.anchor = anchor;
    }

    public Set<AtlasTermCategorizationHeader> getCategories() {
        return categories;
    }

    public void setCategories(final Set<AtlasTermCategorizationHeader> categories) {
        this.categories = categories;
    }

    public void addCategory(final AtlasTermCategorizationHeader category) {
        Set<AtlasTermCategorizationHeader> categories = this.categories;

        if (categories == null) {
            categories = new HashSet<>();
        }

        categories.add(category);

        setCategories(categories);
    }

    public Set<AtlasRelatedObjectId> getAssignedEntities() {
        return assignedEntities;
    }

    public void setAssignedEntities(final Set<AtlasRelatedObjectId> assignedEntities) {
        this.assignedEntities = assignedEntities;
    }

    public void addAssignedEntity(final AtlasRelatedObjectId atlasObjectId) {
        Set<AtlasRelatedObjectId> assignedEntities = this.assignedEntities;

        if (assignedEntities == null) {
            assignedEntities = new HashSet<>();
        }

        assignedEntities.add(atlasObjectId);

        setAssignedEntities(assignedEntities);
    }

    public Set<AtlasRelatedTermHeader> getSeeAlso() {
        return seeAlso;
    }

    public void setSeeAlso(final Set<AtlasRelatedTermHeader> seeAlso) {
        this.seeAlso = seeAlso;

        if (CollectionUtils.isNotEmpty(seeAlso)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getSynonyms() {
        return synonyms;
    }

    public void setSynonyms(final Set<AtlasRelatedTermHeader> synonyms) {
        this.synonyms = synonyms;

        if (CollectionUtils.isNotEmpty(synonyms)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getAntonyms() {
        return antonyms;
    }

    public void setAntonyms(final Set<AtlasRelatedTermHeader> antonyms) {
        this.antonyms = antonyms;

        if (CollectionUtils.isNotEmpty(antonyms)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getPreferredTerms() {
        return preferredTerms;
    }

    public void setPreferredTerms(final Set<AtlasRelatedTermHeader> preferredTerms) {
        this.preferredTerms = preferredTerms;

        if (CollectionUtils.isNotEmpty(preferredTerms)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getPreferredToTerms() {
        return preferredToTerms;
    }

    public void setPreferredToTerms(final Set<AtlasRelatedTermHeader> preferredToTerms) {
        this.preferredToTerms = preferredToTerms;

        if (CollectionUtils.isNotEmpty(preferredToTerms)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getReplacementTerms() {
        return replacementTerms;
    }

    public void setReplacementTerms(final Set<AtlasRelatedTermHeader> replacementTerms) {
        this.replacementTerms = replacementTerms;

        if (CollectionUtils.isNotEmpty(replacementTerms)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getReplacedBy() {
        return replacedBy;
    }

    public void setReplacedBy(final Set<AtlasRelatedTermHeader> replacedBy) {
        this.replacedBy = replacedBy;

        if (CollectionUtils.isNotEmpty(replacedBy)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getTranslationTerms() {
        return translationTerms;
    }

    public void setTranslationTerms(final Set<AtlasRelatedTermHeader> translationTerms) {
        this.translationTerms = translationTerms;

        if (CollectionUtils.isNotEmpty(translationTerms)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getTranslatedTerms() {
        return translatedTerms;
    }

    public void setTranslatedTerms(final Set<AtlasRelatedTermHeader> translatedTerms) {
        this.translatedTerms = translatedTerms;

        if (CollectionUtils.isNotEmpty(translatedTerms)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getIsA() {
        return isA;
    }

    public void setIsA(final Set<AtlasRelatedTermHeader> isA) {
        this.isA = isA;

        if (CollectionUtils.isNotEmpty(isA)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getClassifies() {
        return classifies;
    }

    public void setClassifies(final Set<AtlasRelatedTermHeader> classifies) {
        this.classifies = classifies;

        if (CollectionUtils.isNotEmpty(classifies)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getValidValues() {
        return validValues;
    }

    public void setValidValues(final Set<AtlasRelatedTermHeader> validValues) {
        this.validValues = validValues;

        if (CollectionUtils.isNotEmpty(validValues)) {
            hasTerms = true;
        }
    }

    public Set<AtlasRelatedTermHeader> getValidValuesFor() {
        return validValuesFor;
    }

    public void setValidValuesFor(final Set<AtlasRelatedTermHeader> validValuesFor) {
        this.validValuesFor = validValuesFor;

        if (CollectionUtils.isNotEmpty(validValuesFor)) {
            hasTerms = true;
        }
    }

    public AtlasGlossaryTermHeader getGlossaryTermHeader() {
        return new AtlasGlossaryTermHeader(this.getGuid(), this.getQualifiedName());
    }

    @JsonIgnore
    public String toAuditString() {
        AtlasGlossaryTerm t = new AtlasGlossaryTerm();

        t.setGuid(this.getGuid());
        t.setName(this.getName());
        t.setQualifiedName(this.getQualifiedName());

        return AtlasType.toJson(t);
    }

    @JsonIgnore
    public boolean hasTerms() {
        return hasTerms;
    }

    @JsonIgnore
    @Override
    public void setAttribute(String attrName, String attrVal) {
        requireNonNull(attrName, "AtlasGlossaryTerm attribute name");

        switch (attrName) {
            case "name":
                setName(attrVal);
                break;
            case "shortDescription":
                setShortDescription(attrVal);
                break;
            case "longDescription":
                setLongDescription(attrVal);
                break;
            case "abbreviation":
                setAbbreviation(attrVal);
                break;
            case "usage":
                setUsage(attrVal);
                break;
            default:
                throw new IllegalArgumentException("Invalid attribute '" + attrName + "' for object AtlasGlossaryTerm");
        }
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        } else if (!(o instanceof AtlasGlossaryTerm)) {
            return false;
        } else if (!super.equals(o)) {
            return false;
        }

        AtlasGlossaryTerm that = (AtlasGlossaryTerm) o;

        return Objects.equals(examples, that.examples) &&
                Objects.equals(abbreviation, that.abbreviation) &&
                Objects.equals(usage, that.usage) &&
                Objects.equals(anchor, that.anchor) &&
                Objects.equals(assignedEntities, that.assignedEntities) &&
                Objects.equals(categories, that.categories) &&
                Objects.equals(seeAlso, that.seeAlso) &&
                Objects.equals(synonyms, that.synonyms) &&
                Objects.equals(antonyms, that.antonyms) &&
                Objects.equals(preferredTerms, that.preferredTerms) &&
                Objects.equals(preferredToTerms, that.preferredToTerms) &&
                Objects.equals(replacementTerms, that.replacementTerms) &&
                Objects.equals(replacedBy, that.replacedBy) &&
                Objects.equals(translationTerms, that.translationTerms) &&
                Objects.equals(translatedTerms, that.translatedTerms) &&
                Objects.equals(isA, that.isA) &&
                Objects.equals(classifies, that.classifies) &&
                Objects.equals(validValues, that.validValues) &&
                Objects.equals(validValuesFor, that.validValuesFor);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), examples, abbreviation, usage, anchor, assignedEntities, categories,
                seeAlso, synonyms, antonyms, preferredTerms, preferredToTerms, replacementTerms, replacedBy,
                translationTerms, translatedTerms, isA, classifies, validValues, validValuesFor);
    }

    @Override
    protected StringBuilder toString(final StringBuilder sb) {
        sb.append("examples=").append(examples);
        sb.append(", abbreviation='").append(abbreviation).append('\'');
        sb.append(", usage='").append(usage).append('\'');
        sb.append(", anchor=").append(anchor);
        sb.append(", assignedEntities=").append(assignedEntities);
        sb.append(", categories=").append(categories);
        sb.append(", seeAlso=").append(seeAlso);
        sb.append(", synonyms=").append(synonyms);
        sb.append(", antonyms=").append(antonyms);
        sb.append(", preferredTerms=").append(preferredTerms);
        sb.append(", preferredToTerms=").append(preferredToTerms);
        sb.append(", replacementTerms=").append(replacementTerms);
        sb.append(", replacedBy=").append(replacedBy);
        sb.append(", translationTerms=").append(translationTerms);
        sb.append(", translatedTerms=").append(translatedTerms);
        sb.append(", isA=").append(isA);
        sb.append(", classifies=").append(classifies);
        sb.append(", validValues=").append(validValues);
        sb.append(", validValuesFor=").append(validValuesFor);

        return sb;
    }

    @JsonIgnore
    public Map<Relation, Set<AtlasRelatedTermHeader>> getRelatedTerms() {
        Map<Relation, Set<AtlasRelatedTermHeader>> ret = new HashMap<>();

        if (CollectionUtils.isNotEmpty(seeAlso)) {
            ret.put(Relation.SEE_ALSO, seeAlso);
        }

        if (CollectionUtils.isNotEmpty(synonyms)) {
            ret.put(Relation.SYNONYMS, synonyms);
        }

        if (CollectionUtils.isNotEmpty(antonyms)) {
            ret.put(Relation.ANTONYMS, antonyms);
        }

        if (CollectionUtils.isNotEmpty(preferredTerms)) {
            ret.put(Relation.PREFERRED_TERMS, preferredTerms);
        }

        if (CollectionUtils.isNotEmpty(preferredToTerms)) {
            ret.put(Relation.PREFERRED_TO_TERMS, preferredToTerms);
        }

        if (CollectionUtils.isNotEmpty(replacementTerms)) {
            ret.put(Relation.REPLACEMENT_TERMS, replacementTerms);
        }

        if (CollectionUtils.isNotEmpty(replacedBy)) {
            ret.put(Relation.REPLACED_BY, replacedBy);
        }

        if (CollectionUtils.isNotEmpty(translationTerms)) {
            ret.put(Relation.TRANSLATION_TERMS, translationTerms);
        }

        if (CollectionUtils.isNotEmpty(translatedTerms)) {
            ret.put(Relation.TRANSLATED_TERMS, translatedTerms);
        }

        if (CollectionUtils.isNotEmpty(isA)) {
            ret.put(Relation.ISA, isA);
        }

        if (CollectionUtils.isNotEmpty(classifies)) {
            ret.put(Relation.CLASSIFIES, classifies);
        }

        if (CollectionUtils.isNotEmpty(validValues)) {
            ret.put(Relation.VALID_VALUES, validValues);
        }

        if (CollectionUtils.isNotEmpty(validValuesFor)) {
            ret.put(Relation.VALID_VALUES_FOR, validValuesFor);
        }

        return ret;
    }

    public enum Relation {
        SEE_ALSO("AtlasGlossaryRelatedTerm", "seeAlso"),
        SYNONYMS("AtlasGlossarySynonym", "synonyms"),
        ANTONYMS("AtlasGlossaryAntonym", "antonyms"),
        PREFERRED_TO_TERMS("AtlasGlossaryPreferredTerm", "preferredToTerms", true),
        PREFERRED_TERMS("AtlasGlossaryPreferredTerm", "preferredTerms"),
        REPLACEMENT_TERMS("AtlasGlossaryReplacementTerm", "replacementTerms", true),
        REPLACED_BY("AtlasGlossaryReplacementTerm", "replacedBy"),
        TRANSLATION_TERMS("AtlasGlossaryTranslation", "translationTerms", true),
        TRANSLATED_TERMS("AtlasGlossaryTranslation", "translatedTerms"),
        ISA("AtlasGlossaryIsARelationship", "isA", true),
        CLASSIFIES("AtlasGlossaryIsARelationship", "classifies"),
        VALID_VALUES("AtlasGlossaryValidValue", "validValues", true),
        VALID_VALUES_FOR("AtlasGlossaryValidValue", "validValuesFor");

        private final String  name;
        private final String  attrName;
        private final boolean isEnd2Attr;

        Relation(final String name, final String attrName) {
            this(name, attrName, false);
        }

        Relation(final String name, final String attrName, final boolean isEnd2Attr) {
            this.name       = name;
            this.attrName   = attrName;
            this.isEnd2Attr = isEnd2Attr;
        }

        public String getName() {
            return name;
        }

        @JsonValue
        public String getAttrName() {
            return attrName;
        }

        public boolean isEnd2Attr() {
            return isEnd2Attr;
        }
    }
}
