/*
 * Copyright (c) 2019 the Eclipse Milo Authors
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.milo.opcua.stack.core;

import java.util.Map;
import java.util.Optional;

import com.google.common.collect.ImmutableMap;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
import org.jetbrains.annotations.Nullable;

public enum BuiltinReferenceType implements ReferenceType {

    References(
        Identifiers.References,
        "References",
        null,
        true, true, null),

    HierarchicalReferences(
        Identifiers.HierarchicalReferences,
        "HierarchicalReferences",
        null,
        false, true, References),

    NonHierarchicalReferences(
        Identifiers.NonHierarchicalReferences,
        "NonHierarchicalReferences",
        null,
        true, true, References),

    HasChild(
        Identifiers.HasChild,
        "HasChild",
        null,
        false, true, HierarchicalReferences),

    Aggregates(
        Identifiers.Aggregates,
        "Aggregates",
        null,
        false, true, HasChild),

    Organizes(
        Identifiers.Organizes,
        "Organizes",
        "OrganizedBy",
        false, false, HierarchicalReferences),

    HasComponent(
        Identifiers.HasComponent,
        "HasComponent",
        "ComponentOf",
        false, false, Aggregates),

    HasOrderedComponent(
        Identifiers.HasOrderedComponent,
        "HasOrderedComponent",
        "OrderedComponentOf",
        false, false, HasComponent),

    HasProperty(
        Identifiers.HasProperty,
        "HasProperty",
        "PropertyOf",
        false, false, Aggregates),

    HasSubtype(
        Identifiers.HasSubtype,
        "HasSubtype",
        "SubtypeOf", false, false, HasChild),

    HasModellingRule(
        Identifiers.HasModellingRule,
        "HasModellingRule",
        "ModellingRuleOf",
        false, false, NonHierarchicalReferences),

    HasTypeDefinition(
        Identifiers.HasTypeDefinition,
        "HasTypeDefinition",
        "TypeDefinitionOf",
        false, false, NonHierarchicalReferences),

    HasEncoding(
        Identifiers.HasEncoding,
        "HasEncoding",
        "EncodingOf",
        false, false, NonHierarchicalReferences),

    HasDescription(
        Identifiers.HasDescription,
        "HasDescription",
        "DescriptionOf",
        false, false, NonHierarchicalReferences),

    HasEventSource(
        Identifiers.HasEventSource,
        "HasEventSource",
        "EventSourceOf",
        false, false, HierarchicalReferences),

    HasNotifier(
        Identifiers.HasNotifier,
        "HasNotifier",
        "NotifierOf",
        false, false, HasEventSource),

    GeneratesEvent(
        Identifiers.GeneratesEvent,
        "GeneratesEvent",
        "GeneratedBy",
        false, false, NonHierarchicalReferences),

    AlwaysGeneratesEvent(
        Identifiers.AlwaysGeneratesEvent,
        "AlwaysGeneratesEvent",
        "AlwaysGeneratedBy",
        false, false, GeneratesEvent),

    FromState(
        Identifiers.FromState,
        "FromState",
        "ToTransition",
        false, false, NonHierarchicalReferences),

    ToState(
        Identifiers.ToState,
        "ToState",
        "FromTransition",
        false, false, NonHierarchicalReferences),

    HasCause(
        Identifiers.HasCause,
        "HasCause",
        "MayBeCausedBy",
        false, false, NonHierarchicalReferences),

    HasEffect(
        Identifiers.HasEffect,
        "HasEffect",
        "MayBeEffectedBy",
        false, false, NonHierarchicalReferences),

    HasSubStateMachine(
        Identifiers.HasSubStateMachine,
        "HasSubStateMachine",
        "SubStateMachineOf",
        false, false, NonHierarchicalReferences),

    HasTrueSubState(
        Identifiers.HasTrueSubState,
        "HasTrueSubState",
        "IsTrueSubStateOf",
        false, false, NonHierarchicalReferences),

    HasFalseSubState(
        Identifiers.HasFalseSubState,
        "HasFalseSubState",
        "IsFalseSubStateOf",
        false, false, NonHierarchicalReferences);

    private final NodeId nodeId;
    private final QualifiedName browseName;
    private final String inverseName;
    private final boolean symmetric;
    private final boolean isAbstract;
    private final NodeId superTypeId;

    BuiltinReferenceType(
        NodeId nodeId,
        String browseName,
        @Nullable String inverseName,
        boolean symmetric,
        boolean isAbstract,
        @Nullable BuiltinReferenceType superType) {

        this.nodeId = nodeId;
        this.browseName = new QualifiedName(0, browseName);
        this.inverseName = inverseName;
        this.symmetric = symmetric;
        this.isAbstract = isAbstract;

        if (superType != null) {
            this.superTypeId = superType.getNodeId();
        } else {
            this.superTypeId = null;
        }
    }

    @Override
    public NodeId getNodeId() {
        return nodeId;
    }

    @Override
    public QualifiedName getBrowseName() {
        return browseName;
    }

    @Override
    public Optional<String> getInverseName() {
        return Optional.ofNullable(inverseName);
    }

    @Override
    public boolean isSymmetric() {
        return symmetric;
    }

    @Override
    public boolean isAbstract() {
        return isAbstract;
    }

    @Override
    public Optional<NodeId> getSuperTypeId() {
        return Optional.ofNullable(superTypeId);
    }

    private static final ImmutableMap<NodeId, ReferenceType> BUILTIN_REFERENCE_MAP;

    static {
        ImmutableMap.Builder<NodeId, ReferenceType> builder = ImmutableMap.builder();
        for (ReferenceType referenceType : BuiltinReferenceType.values()) {
            builder.put(referenceType.getNodeId(), referenceType);
        }
        BUILTIN_REFERENCE_MAP = builder.build();
    }

    public static Map<NodeId, ReferenceType> getReferenceMap() {
        return BUILTIN_REFERENCE_MAP;
    }

}
