/*
 * Copyright 2009 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.user.linker.rpc;

import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.AbstractLinker;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.EmittedArtifact;
import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
import com.google.gwt.core.ext.linker.LinkerOrder;
import com.google.gwt.core.ext.linker.LinkerOrder.Order;
import com.google.gwt.core.ext.linker.Shardable;
import com.google.gwt.core.ext.linker.SyntheticArtifact;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.user.rebind.rpc.ProxyCreator;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * Emit a file containing a map of RPC proxy classes to the partial path of the
 * RPC policy file.
 */
@LinkerOrder(Order.PRE)
@Shardable
public class RpcPolicyManifestLinker extends AbstractLinker {
  private static final String MANIFEST_TXT = "manifest.txt";

  /**
   * The main body of the manifest. It is built up as per-permutation manifests
   * are looped over.
   */
  private StringBuilder manifestBody = new StringBuilder();

  @Override
  public String getDescription() {
    return "RPC policy file manifest";
  }

  @Override
  public ArtifactSet link(TreeLogger logger, LinkerContext context,
      ArtifactSet artifacts, boolean onePermutation)
      throws UnableToCompleteException {
    if (onePermutation) {
      return artifacts;
    } else {
      for (EmittedArtifact art : artifacts.find(EmittedArtifact.class)) {
        if (art.getPartialPath().startsWith(ProxyCreator.MANIFEST_ARTIFACT_DIR)) {
          readOneManifest(logger, art.getContents(logger));
        }
      }

      ArtifactSet toReturn = new ArtifactSet(artifacts);
      SyntheticArtifact manifestArt = emitString(logger,
          generateManifest(context), MANIFEST_TXT);
      manifestArt.setVisibility(Visibility.LegacyDeploy);
      toReturn.add(manifestArt);
      return toReturn;
    }
  }

  /**
   * Compute a manifest for all RPC policy files seen so far.
   */
  private String generateManifest(LinkerContext context) {
    StringBuilder sb = new StringBuilder();
    sb.append("# Module " + context.getModuleName() + "\n");
    sb.append("# RPC service class, partial path of RPC policy file\n");
    sb.append(manifestBody.toString());
    return sb.toString();
  }

  /**
   * Read one manifest and close the input stream.
   */
  private void readOneManifest(TreeLogger logger, InputStream manifestStream)
      throws UnableToCompleteException {
    Map<String, String> entries = new HashMap<String, String>();
    try {
      BufferedReader reader = new BufferedReader(new InputStreamReader(
          manifestStream, StandardCharsets.UTF_8));
      String line;
      while ((line = reader.readLine()) != null) {
        int idx = line.indexOf(':');
        if (idx < 0) {
          throw new InternalCompilerException(
              "invalid selection information line: " + line);
        }
        String propName = line.substring(0, idx).trim();
        String propValue = line.substring(idx + 1).trim();
        entries.put(propName, propValue);
      }
      reader.close();
    } catch (IOException e) {
      logger.log(TreeLogger.ERROR, "Unexpected IOException", e);
      throw new UnableToCompleteException();
    }

    String serviceClass = entries.get("serviceClass");
    if (serviceClass == null) {
      logger.log(TreeLogger.ERROR,
          "Internal error: manifest file does not include a serviceClass");
      throw new UnableToCompleteException();
    }
    String path = entries.get("path");
    if (path == null) {
      logger.log(TreeLogger.ERROR,
          "Internal error: manifest file does not include a path");
      throw new UnableToCompleteException();
    }

    manifestBody.append(serviceClass);
    manifestBody.append(", ");
    manifestBody.append(path);
    manifestBody.append("\n");
  }
}
