/*
 * Copyright 2008 Google Inc.
 *
 * 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 com.google.gwt.dev.shell;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.dev.CompilerContext;
import com.google.gwt.dev.PrecompileTaskOptionsImpl;
import com.google.gwt.dev.RebindCache;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.Rule;
import com.google.gwt.dev.javac.CompilationState;
import com.google.gwt.dev.javac.StandardGeneratorContext;
import com.google.gwt.dev.util.log.perf.SimpleEvent;

import java.io.File;
import java.util.Deque;

/**
 * Provides an environment for a {@link com.google.gwt.dev.shell.ModuleSpace}
 * that works appropriately for the development shell.
 */
public class ShellModuleSpaceHost implements ModuleSpaceHost {

  // TODO(jat): hack to try and serialize rebinds
  private static final Object rebindLock = new Object[0];

  protected final CompilationState compilationState;

  protected final File genDir;

  private final ArtifactAcceptor artifactAcceptor;

  private CompilingClassLoader classLoader;

  private final TreeLogger logger;

  private final ModuleDef module;

  private StandardRebindOracle rebindOracle;

  private ModuleSpace space;

  private RebindCache rebindCache;

  /**
   * @param module the module associated with the hosted module space
   */
  public ShellModuleSpaceHost(TreeLogger logger,
      CompilationState compilationState, ModuleDef module, File genDir,
      ArtifactAcceptor artifactAcceptor, RebindCache rebindCache) {
    this.logger = logger;
    this.compilationState = compilationState;
    this.module = module;
    this.genDir = genDir;
    this.artifactAcceptor = artifactAcceptor;
    this.rebindCache = rebindCache;
  }

  @Override
  public CompilingClassLoader getClassLoader() {
    checkForModuleSpace();
    return classLoader;
  }

  @Override
  public String[] getEntryPointTypeNames() {
    checkForModuleSpace();
    return module.getEntryPointTypeNames();
  }

  @Override
  public TreeLogger getLogger() {
    return logger;
  }

  @Override
  public void onModuleReady(ModuleSpace readySpace)
      throws UnableToCompleteException {
    this.space = readySpace;

    try (SimpleEvent ignored = new SimpleEvent("ModuleSpaceHost ready")) {
      // Establish an environment for JavaScript property providers to run.
      //
      ModuleSpacePropertyOracle propOracle = new ModuleSpacePropertyOracle(
          module.getProperties(), module.getActiveLinkerNames(), readySpace);

      // Set up the rebind oracle for the module.
      // It has to wait until now because we need to inject javascript.
      //
      Deque<Rule> rules = module.getRules();
      PrecompileTaskOptionsImpl options = new PrecompileTaskOptionsImpl();
      options.setGenDir(genDir);
      CompilerContext compilerContext =
          new CompilerContext.Builder().module(module).options(options).build();
      StandardGeneratorContext genCtx =
          new StandardGeneratorContext(compilerContext, compilationState, new ArtifactSet(), false);

      // Only enable generator result caching if we have a valid rebindCache
      genCtx.setGeneratorResultCachingEnabled((rebindCache != null));

      rebindOracle = new StandardRebindOracle(propOracle, rules, genCtx);
      rebindOracle.setRebindCache(rebindCache);

      // Create a completely isolated class loader which owns all classes
      // associated with a particular module. This effectively builds a
      // separate 'domain' for each running module, so that they all behave as
      // though they are running separately. This allows the shell to run
      // multiple modules, both in succession and simultaneously, without getting
      // confused.
      //
      // Note that the compiling class loader has no parent. This keeps it from
      // accidentally 'escaping' its domain and loading classes from the system
      // class loader (the one that loaded the shell itself).
      //
      classLoader = new CompilingClassLoader(logger, compilationState, readySpace);
    }
  }

  @Override
  public String rebind(TreeLogger logger, String sourceTypeName)
      throws UnableToCompleteException {
    synchronized (rebindLock) {
      checkForModuleSpace();
      return rebindOracle.rebind(logger, sourceTypeName, new ArtifactAcceptor() {
        @Override
        public void accept(TreeLogger logger, ArtifactSet newlyGeneratedArtifacts)
        throws UnableToCompleteException {
          artifactAcceptor.accept(logger, newlyGeneratedArtifacts);
        }
      });
    }
  }

  private void checkForModuleSpace() {
    if (space == null) {
      throw new IllegalStateException("Module initialization error");
    }
  }
}
