/*
 * 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.language;

import jetbrains.mps.components.CoreComponent;
import jetbrains.mps.core.aspects.behaviour.BehaviorRegistryImpl;
import jetbrains.mps.core.aspects.behaviour.api.BehaviorRegistry;
import jetbrains.mps.smodel.adapter.ids.MetaIdFactory;
import jetbrains.mps.smodel.adapter.ids.MetaIdHelper;
import jetbrains.mps.smodel.adapter.ids.SConceptId;
import jetbrains.mps.smodel.adapter.structure.concept.InvalidConcept;
import jetbrains.mps.smodel.runtime.ConceptDescriptor;
import jetbrains.mps.smodel.runtime.ConceptPresentation;
import jetbrains.mps.smodel.runtime.ConstraintsDescriptor;
import jetbrains.mps.smodel.runtime.illegal.IllegalConceptDescriptor;
import jetbrains.mps.util.annotation.ToRemove;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import org.jetbrains.mps.openapi.language.SLanguage;

import java.util.HashMap;
import java.util.Map;

// TODO avoid singleton by creating a new ComponentPlugin instance with smodel-related components (it is not CoreComponent in fact)
public class ConceptRegistry implements CoreComponent, LanguageRegistryListener {
  private static Map<String, SAbstractConcept> myConceptByNameCache;

  private final LanguageRegistry myLanguageRegistry;
  private final StructureRegistry myStructureRegistry;
  private final ConceptPropertiesRegistry myConcPropsRegistry;
  private final BehaviorRegistry myBehaviorRegistry;
  private final ConstraintsRegistry myConstraintsRegistry;

  // fixme wrong naming
  public ConceptRegistry(@NotNull LanguageRegistry languageRegistry) {
    myLanguageRegistry = languageRegistry;
    myStructureRegistry = new StructureRegistry(languageRegistry);
    myConcPropsRegistry = new ConceptPropertiesRegistry(languageRegistry);
    myBehaviorRegistry = new BehaviorRegistryImpl(languageRegistry);
    myConstraintsRegistry = new ConstraintsRegistry(languageRegistry);
  }

  private static ConceptRegistry INSTANCE;

  public static ConceptRegistry getInstance() {
    return INSTANCE;
  }

  /**
   * @deprecated use
   */
  public BehaviorRegistry getBehaviorRegistry() {
    return myBehaviorRegistry;
  }

  @Override
  public void init() {
    if (INSTANCE != null) {
      throw new IllegalStateException("double initialization");
    }
    INSTANCE = this;
    myLanguageRegistry.addRegistryListener(this);
  }

  @Override
  public void dispose() {
    myLanguageRegistry.removeRegistryListener(this);
    INSTANCE = null;
  }

  /**
   * @deprecated It's odd to go from SAbstractConcept back to ConceptDescriptor. It's MPS implementation of SConcept that
   *             deals with ConceptDescriptors to populate SAbstractConcept and client code shall not reverse this.
   *             *
   *             NOTE, THERE ARE NO USES left in MPS code, don't introduce a new one! We'll drop the method any time soon.
   *             *
   */
  @NotNull
  @Deprecated
  public ConceptDescriptor getConceptDescriptor(@NotNull SAbstractConcept concept) {
    SConceptId cid = MetaIdHelper.getConcept(concept);
    if (cid == MetaIdFactory.INVALID_CONCEPT_ID) {
      return new IllegalConceptDescriptor(cid, concept.getQualifiedName());
    }
    return getConceptDescriptor(cid);
  }

  /**
   * Looks up {@link ConceptDescriptor} for the given id. If none found,
   * {@link IllegalConceptDescriptor} instance is returned, with {@link IllegalConceptDescriptor#getId()}
   * equal to the one supplied.
   *
   * @param id identity of a concept (generally, shall not use MetaIdFactory.INVALID_CONCEPT_ID)
   * @return never {@code null}
   */
  @NotNull
  public ConceptDescriptor getConceptDescriptor(@NotNull SConceptId id) {
    // XXX shall I check for id == MetaIdFactory.INVALID_CONCEPT_ID?
    // If yes, handle gracefully or assert !=
    ConceptDescriptor cd = myStructureRegistry.getConceptDescriptor(id);
    return cd == null ? new IllegalConceptDescriptor(id) : cd;
  }

  public ConceptPresentation getConceptProperties(@NotNull SAbstractConcept concept){
    return myConcPropsRegistry.getConceptProperties(concept);
  }

  @NotNull
  public ConstraintsDescriptor getConstraintsDescriptor(@NotNull SAbstractConcept concept) {
    return myConstraintsRegistry.getConstraintsDescriptor(concept);
  }

  /**
   * Use {@link jetbrains.mps.smodel.language.ConceptRegistryUtil#getConstraintsDescriptor(org.jetbrains.mps.openapi.language.SAbstractConcept)}
   * if you got SConcept
   */
  @Deprecated
  @ToRemove(version = 3.4)
  @NotNull
  public ConstraintsDescriptor getConstraintsDescriptor(@NotNull SConceptId conceptId) {
    return myConstraintsRegistry.getConstraintsDescriptor(conceptId);
  }

  @Deprecated
  @ToRemove(version = 3.4)
  //this method is here for compatibility purposes.
  //remve as soon as there's no need in optimizing by-name stuff
  synchronized public SAbstractConcept getConceptByName(String conceptName) {
    if (myConceptByNameCache==null) {
      myConceptByNameCache = new HashMap<String, SAbstractConcept>();
      for (SLanguage l : LanguageRegistry.getInstance().getAllLanguages()) {
        for (SAbstractConcept c : l.getConcepts()) {
          myConceptByNameCache.put(c.getQualifiedName(),c);
        }
      }
    }

    SAbstractConcept cached = myConceptByNameCache.get(conceptName);
    if (cached!=null) return cached;
    return new InvalidConcept(conceptName);
  }

  synchronized private void clearConceptsCache() {
    myConceptByNameCache = null;
  }

  @Override
  public void beforeLanguagesUnloaded(Iterable<LanguageRuntime> languages) {
    // no-op, it's not the right time to drop caches (unless can do it selectively)
    // as other unload listeners might (although should not) access this registry
  }

  @Override
  public void afterLanguagesLoaded(Iterable<LanguageRuntime> languages) {
    // todo: incremental?
    myStructureRegistry.clear();
    myBehaviorRegistry.clear();
    myConstraintsRegistry.clear();
    clearConceptsCache();
  }
}
