/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.apache.hadoop.yarn.server.webproxy;

import static org.apache.hadoop.yarn.util.StringHelper.ujoin;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.yarn.api.records.ApplicationId;

public class ProxyUriUtils {
  @SuppressWarnings("unused")
  private static final Log LOG = LogFactory.getLog(ProxyUriUtils.class);
  
  /**Name of the servlet to use when registering the proxy servlet. */
  public static final String PROXY_SERVLET_NAME = "proxy";
  /**Base path where the proxy servlet will handle requests.*/
  public static final String PROXY_BASE = "/proxy/";
  /**Path Specification for the proxy servlet.*/
  public static final String PROXY_PATH_SPEC = PROXY_BASE+"*";
  /**Query Parameter indicating that the URI was approved.*/
  public static final String PROXY_APPROVAL_PARAM = "proxyapproved";
  
  private static String uriEncode(Object o) {
    try {
      assert (o != null) : "o canot be null";
      return URLEncoder.encode(o.toString(), "UTF-8");
    } catch (UnsupportedEncodingException e) {
      //This should never happen
      throw new RuntimeException("UTF-8 is not supported by this system?", e);
    }
  }
  
  /**
   * Get the proxied path for an application.
   * @param id the application id to use.
   * @return the base path to that application through the proxy.
   */
  public static String getPath(ApplicationId id) {
    if(id == null) {
      throw new IllegalArgumentException("Application id cannot be null ");
    }
    return ujoin(PROXY_BASE, uriEncode(id));
  }

  /**
   * Get the proxied path for an application.
   * @param id the application id to use.
   * @param path the rest of the path to the application.
   * @return the base path to that application through the proxy.
   */
  public static String getPath(ApplicationId id, String path) {
    if(path == null) {
      return getPath(id);
    } else {
      return ujoin(getPath(id), path);
    }
  }
  
  /**
   * Get the proxied path for an application
   * @param id the id of the application
   * @param path the path of the application.
   * @param query the query parameters
   * @param approved true if the user has approved accessing this app.
   * @return the proxied path for this app.
   */
  public static String getPathAndQuery(ApplicationId id, String path, 
      String query, boolean approved) {
    StringBuilder newp = new StringBuilder();
    newp.append(getPath(id, path));
    boolean first = appendQuery(newp, query, true);
    if(approved) {
      appendQuery(newp, PROXY_APPROVAL_PARAM+"=true", first);
    }
    return newp.toString();
  }
  
  private static boolean appendQuery(StringBuilder builder, String query, 
      boolean first) {
    if(query != null && !query.isEmpty()) {
      if(first && !query.startsWith("?")) {
        builder.append('?');
      }
      if(!first && !query.startsWith("&")) {
        builder.append('&');
      }
      builder.append(query);
      return false;
    }
    return first;
  }
  
  /**
   * Get a proxied URI for the original URI.
   * @param originalUri the original URI to go through the proxy, or null if
   * a default path "/" can be used. 
   * @param proxyUri the URI of the proxy itself, scheme, host and port are used.
   * @param id the id of the application
   * @return the proxied URI
   */
  public static URI getProxyUri(URI originalUri, URI proxyUri,
      ApplicationId id) {
    try {
      String path = getPath(id, originalUri == null ? "/" : originalUri.getPath());
      return new URI(proxyUri.getScheme(), proxyUri.getAuthority(), path,
          originalUri == null ? null : originalUri.getQuery(),
          originalUri == null ? null : originalUri.getFragment());
    } catch (URISyntaxException e) {
      throw new RuntimeException("Could not proxify "+originalUri,e);
    }
  }
  
  /**
   * Create a URI form a no scheme Url, such as is returned by the AM.
   * @param noSchemeUrl the URL formate returned by an AM
   * @return a URI with an http scheme
   * @throws URISyntaxException if the url is not formatted correctly.
   */
  public static URI getUriFromAMUrl(String noSchemeUrl) 
    throws URISyntaxException {
    return new URI("http://"+noSchemeUrl);
  }
}
