/*
 * Copyright 2003-2016 JetBrains s.r.o.
 *
 * Licensed 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.
 */
package jetbrains.mps.smodel.runtime.illegal;

import jetbrains.mps.smodel.adapter.ids.MetaIdFactory;
import jetbrains.mps.smodel.adapter.ids.SConceptId;
import jetbrains.mps.smodel.adapter.ids.SContainmentLinkId;
import jetbrains.mps.smodel.adapter.ids.SPropertyId;
import jetbrains.mps.smodel.adapter.ids.SReferenceLinkId;
import jetbrains.mps.smodel.runtime.ConceptDescriptor;
import jetbrains.mps.smodel.runtime.ConceptKind;
import jetbrains.mps.smodel.runtime.LinkDescriptor;
import jetbrains.mps.smodel.runtime.PropertyDescriptor;
import jetbrains.mps.smodel.runtime.ReferenceDescriptor;
import jetbrains.mps.smodel.runtime.StaticScope;
import jetbrains.mps.util.NameUtil;
import jetbrains.mps.util.containers.ConcurrentHashSet;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SNodeReference;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

public class IllegalConceptDescriptor implements ConceptDescriptor {
  private static final Logger LOG = LogManager.getLogger(IllegalConceptDescriptor.class);
  private static final Set<String> ourReportedConcepts = new ConcurrentHashSet<>();
  private static final Set<SConceptId> ourReportedConceptIds = new ConcurrentHashSet<>();
  private boolean myReported = false;

  @NotNull
  private final SConceptId myConceptId;
  @NotNull
  private final String myConceptName;
  @Nullable
  private final String myLanguageName; // null indicates we don't know concept name nor language name

  /**
   * Covers the case when attempt to find a concept by its known id failed.
   * Generally shall not face {@link MetaIdFactory#INVALID_CONCEPT_ID}, although
   * tolerates the value ({@link #getConceptFqName} gives different name in this case).
   *
   * @param concept
   */
  public IllegalConceptDescriptor(@NotNull SConceptId concept) {
    myConceptId = concept;
    myConceptName = concept == MetaIdFactory.INVALID_CONCEPT_ID ? MetaIdFactory.INVALID_CONCEPT_NAME : "<unknown>";
    myLanguageName = null;
  }

  /**
   * General case when we know name of the concept being looked up
   */
  public IllegalConceptDescriptor(@NotNull SConceptId concept, @NotNull String conceptQualifiedName) {
    myConceptId = concept;
    myConceptName = conceptQualifiedName;
    // language name won't be null even if conceptQualifiedName doesn't match expected pattern.
    myLanguageName = NameUtil.namespaceFromConceptFQName(conceptQualifiedName);
  }

  private void reportWarn() {
    if (myReported) {
      return;
    }
    myReported = true;
    if (myLanguageName != null) {
      if (ourReportedConcepts.add(myConceptName)) {
        String msg = "IllegalConceptDescriptor created for concept %s. Please check the language %s is built and compiled.";
        LOG.warn(String.format(msg, myConceptName, myLanguageName));
      }
    } else {
      if (ourReportedConceptIds.add(myConceptId)) {
        String msg = "IllegalConceptDescriptor created for concept %s.";
        LOG.warn(String.format(msg, myConceptId));
      }
    }
  }

  @NotNull
  @Override
  public SConceptId getId() {
    reportWarn();
    return myConceptId;
  }

  @Override
  public String getConceptFqName() {
    reportWarn();
    return myConceptName;
  }

  @Override
  public String getSuperConcept() {
    reportWarn();
    return null;
  }

  @Override
  public boolean isInterfaceConcept() {
    reportWarn();
    return false;
  }

  @Override
  public Set<SReferenceLinkId> getReferenceIds() {
    reportWarn();
    return Collections.emptySet();
  }

  @Override
  public Collection<ReferenceDescriptor> getReferenceDescriptors() {
    reportWarn();
    return Collections.emptySet();
  }

  @Override
  public ReferenceDescriptor getRefDescriptor(SReferenceLinkId id) {
    reportWarn();
    return null;
  }

  @Override
  public PropertyDescriptor getPropertyDescriptor(String name) {
    reportWarn();
    return null;
  }

  @Override
  public ReferenceDescriptor getRefDescriptor(String name) {
    reportWarn();
    return null;
  }

  @Override
  public LinkDescriptor getLinkDescriptor(String name) {
    reportWarn();
    return null;
  }

  @Override
  public Set<SContainmentLinkId> getLinkIds() {
    reportWarn();
    return Collections.emptySet();
  }

  @Override
  public Collection<LinkDescriptor> getLinkDescriptors() {
    reportWarn();
    return Collections.emptySet();
  }

  @Override
  public LinkDescriptor getLinkDescriptor(SContainmentLinkId id) {
    reportWarn();
    return null;
  }

  @Override
  public StaticScope getStaticScope() {
    reportWarn();
    return StaticScope.GLOBAL;
  }

  @Override
  public Set<SPropertyId> getPropertyIds() {
    reportWarn();
    return Collections.emptySet();
  }

  @Override
  public Collection<PropertyDescriptor> getPropertyDescriptors() {
    reportWarn();
    return Collections.emptySet();
  }

  @Override
  public PropertyDescriptor getPropertyDescriptor(SPropertyId id) {
    reportWarn();
    return null;
  }

  @Override
  public ConceptKind getConceptKind() {
    reportWarn();
    return ConceptKind.NORMAL;
  }

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

  @Override
  public boolean isFinal() {
    reportWarn();
    return false;
  }

  @Override
  public boolean isRootable() {
    return false;
  }

  @NotNull
  @Override
  public String getConceptAlias() {
    reportWarn();
    return "";
  }

  @Override
  public String getConceptShortDescription() {
    reportWarn();
    return "";
  }

  @Override
  public String getHelpUrl() {
    reportWarn();
    return "";
  }

  @Nullable
  @Override
  public SNodeReference getSourceNode() {
    return null;
  }

  @Nullable
  @Override
  public SConceptId getSuperConceptId() {
    reportWarn();
    return MetaIdFactory.INVALID_CONCEPT_ID;
  }

  @Override
  public List<SConceptId> getParentsIds() {
    reportWarn();
    return Collections.emptyList();
  }

  @Override
  public boolean isAssignableTo(SConceptId conceptId) {
    reportWarn();
    return false;
  }

  @Override
  public Set<SConceptId> getAncestorsIds() {
    reportWarn();
    return Collections.emptySet();
  }
}
