/*
 * 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 org.jetbrains.mps.openapi.model;

import org.jetbrains.mps.openapi.model.SModel.Problem;
import org.jetbrains.mps.openapi.module.SRepository;

/**
 * This interface can be implemented to track model state.
 *
 * Generally, we try to send most specific event only, e.g.
 * if a model content is replaced, we likely to send only {@link #modelReplaced(SModel)}, not a sequence of
 * {@link #modelUnloaded(SModel)}, {@link #modelLoaded(SModel, boolean)}, {@link #modelReplaced(SModel)}.
 * However, at the moment we do not ensure this, and chances are you get different sequence for different models.
 * FIXME we lack tests that state aforementioned contract for notifications.
 *
 * FIXME may be useful to provide modelChanged() notification to avoid attaching node change listeners just to figure out if model has been changed.
 * Event makes sense for EditableSModel only, perhaps, could be a separate listener, then?
 */
public interface SModelListener {

  void modelLoaded(SModel model, boolean partially);

  void modelReplaced(SModel model);

  void modelUnloaded(SModel model);

  void modelSaved(SModel model);

  /**
   * Fired when a model becomes visible in a repository.
   * <p/>
   * IMPORTANT: it's unspecified whether the model is part of a module the moment this event is fired or not.
   * Do not expect {@link SModel#getModule()} to give meaningful value. This contract may change (i.e. become stricter) in future.
   * <p/>
   * NOTE: This is not an event most clients could make use of, as it's technically tricky to attach a listener to a model not yet
   * visible inside a repository, and thus it's more of internal mechanism (i.e. code that instantiates a model may attach a listener
   * to not yet published model and thus get notified). Besides, detached models do not get their listeners automatically discarded,
   * and the listeners get a chance to react to model detach/re-attach sequence with this event.
   *
   * @param model affected model, never <code>null</code>
   * @param repository repository the model become available at, never <code>null</code>
   */
  void modelAttached(SModel model, SRepository repository);

  /**
   * Fired when a model is no longer part of a repository, e.g. due to removal from module.
   * This event is intended to clean-up listener caches associated with the model and to gracefully unregister other listeners.
   * <p/>
   * IMPORTANT: it's unspecified whether the model is part of a module the moment this event is fired or not.
   * Do not expect {@link SModel#getModule()} to give meaningful value. This contract may change (i.e. become stricter) in future.
   * <p/>
   * Primary drive force for this event is desire to add model listener only, without a need to listen to
   * {@linkplain org.jetbrains.mps.openapi.module.SModuleListener module events} to find out when the model is no longer available.
   * <p/>
   * Note, {@linkplain SModelListener listeners} not unregistered from the model would get notified with {@link #modelAttached(SModel, SRepository)}
   * in case detached model is brought back (perhaps, in completely different module and repository).
   *
   * @param model affected model, never <code>null</code>
   * @param repository repository the model become available at, never <code>null</code>
   */
  void modelDetached(SModel model, SRepository repository);

  /**
   * This event is fired when the storage-memory conflict is detected (== isChanged() && needsReloading()).
   * An IDE can listen to it and give user a way to resolve the conflict by invoking either updateTimestamp(), or reloadFromSource().
   */
  void conflictDetected(SModel model);

  /**
   * This method is called each time a new problem, or a set of problems is discovered.
   */
  void problemsDetected(SModel model, Iterable<Problem> problems);
}
