/*
 * Copyright 2000-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.debugger;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.concurrency.PromiseManager;
import org.jetbrains.concurrency.Promises;
import org.jetbrains.debugger.values.ValueManager;

import java.util.List;

public abstract class VariablesHost<VALUE_MANAGER extends ValueManager> {
  @SuppressWarnings("unchecked")
  private static final PromiseManager<VariablesHost, List<Variable>> VARIABLES_LOADER =
    new PromiseManager<VariablesHost, List<Variable>>(VariablesHost.class) {
      @Override
      public boolean isUpToDate(@NotNull VariablesHost host, @NotNull List<Variable> data) {
        return host.valueManager.getCacheStamp() == host.cacheStamp;
      }

      @NotNull
      @Override
      public Promise load(@NotNull VariablesHost host) {
        return host.valueManager.isObsolete() ? Promises.cancelledPromise() : host.load();
      }
    };

  @SuppressWarnings("UnusedDeclaration")
  private volatile Promise<List<Variable>> result;

  private volatile int cacheStamp = -1;

  public final VALUE_MANAGER valueManager;

  public VariablesHost(@NotNull VALUE_MANAGER manager) {
    valueManager = manager;
  }

  /**
   * You must call {@link #updateCacheStamp()} when data loaded
   */
  @NotNull
  public final Promise<List<Variable>> get() {
    return VARIABLES_LOADER.get(this);
  }

  @Nullable
  public final Promise.State getState() {
    return VARIABLES_LOADER.getState(this);
  }

  public final void set(@NotNull List<Variable> result) {
    updateCacheStamp();
    VARIABLES_LOADER.set(this, result);
  }

  @NotNull
  protected abstract Promise<List<Variable>> load();

  public final void updateCacheStamp() {
    cacheStamp = valueManager.getCacheStamp();
  }

  /**
   * Some backends requires to reload the whole call stack on scope variable modification, but not all API is asynchronous (compromise, to not increase complexity),
   * for example, {@link CallFrame#getVariableScopes()} is not asynchronous method. So, you must use returned callback to postpone your code working with updated data.
   */
  public Promise<?> clearCaches() {
    cacheStamp = -1;
    VARIABLES_LOADER.reset(this);
    return Promises.resolvedPromise();
  }
}
