/*
 * Copyright 2008 The Closure Compiler Authors.
 *
 * 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.javascript.jscomp.deps;

import static com.google.javascript.jscomp.deps.DefaultDependencyResolver.CLOSURE_BASE;
import static com.google.javascript.jscomp.deps.DefaultDependencyResolver.CLOSURE_BASE_PROVIDE;

import com.google.javascript.jscomp.ErrorManager;
import com.google.javascript.jscomp.LoggerErrorManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

/**
 * SourceFile containing dependency information.  Delegates file handling to
 * another SourceFile such that a VirtualFile, LocalFile or RemoteFile can be
 * used.
 */
public final class DependencyFile implements SourceFile {

  /** Map of name spaces to their dependency info. */
  private final Map<String, DependencyInfo> dependencies = new HashMap<>();

  /** A source file to delegate functionality too. */
  private final SourceFile delegate;

  /** Logger for DependencyResolver. */
  private static final Logger logger = Logger.getLogger(DependencyFile.class.getName());

  /** Creates a new dependency file. */
  public DependencyFile(SourceFile delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getName() {
    return delegate.getName();
  }

  @Override
  public String getContent() {
    return delegate.getContent();
  }

  @Override
  public boolean wasModified() {
    return delegate.wasModified();
  }

  /**
   * Ensures that the dependency graph is up to date and reloads the graph if
   * necessary.
   */
  public void ensureUpToDate() throws ServiceException {
    if (dependencies.isEmpty() || wasModified()) {
      loadGraph();
    }
  }

  /**
   * Gets the dependency info for the provided symbol, if contained in this
   * dependency file.
   */
  public DependencyInfo getDependencyInfo(String symbol) {
    return dependencies.get(symbol);
  }

  /** Loads the dependency graph. */
  private void loadGraph() throws ServiceException {
    dependencies.clear();

    logger.info("Loading dependency graph");

    // Parse the deps.js file.
    ErrorManager errorManager = new LoggerErrorManager(logger);
    DepsFileRegexParser parser = new DepsFileRegexParser(errorManager);
    List<DependencyInfo> depInfos =
        parser.parseFile(getName(), getContent());

    // Ensure the parse succeeded.
    if (!parser.didParseSucceed()) {
      throw new ServiceException("Problem parsing " + getName()
          + ". See logs for details.");
    }
    // Incorporate the dependencies into our maps.
    for (DependencyInfo depInfo : depInfos) {
      for (String provide : depInfo.getProvides()) {
        DependencyInfo existing = dependencies.get(provide);
        if (existing != null && !existing.equals(depInfo)) {
          throw new ServiceException("Duplicate provide of " + provide
              + ". Was provided by " + existing.getPathRelativeToClosureBase()
              + " and " + depInfo.getPathRelativeToClosureBase());
        }
        dependencies.put(provide, depInfo);
      }
    }

    List<String> provides = new ArrayList<>();
    provides.add(CLOSURE_BASE_PROVIDE);

    // Add implicit base.js entry.
    dependencies.put(
        CLOSURE_BASE_PROVIDE,
        SimpleDependencyInfo.builder(CLOSURE_BASE, CLOSURE_BASE).setProvides(provides).build());
    errorManager.generateReport();

    logger.info("Dependencies loaded");
  }

}
