// Copyright (C) 2012 The Android Open Source Project
//
// 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.gerrit.httpd.plugins;

import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Version;
import com.google.gerrit.server.plugins.Plugin;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;

class PluginServletContext {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  static ServletContext create(Plugin plugin, String contextPath) {
    return (ServletContext)
        Proxy.newProxyInstance(
            PluginServletContext.class.getClassLoader(),
            new Class<?>[] {ServletContext.class, API.class},
            new Handler(plugin, contextPath));
  }

  private PluginServletContext() {}

  private static class Handler implements InvocationHandler, API {
    private final Plugin plugin;
    private final String contextPath;
    private final ConcurrentMap<String, Object> attributes;

    Handler(Plugin plugin, String contextPath) {
      this.plugin = plugin;
      this.contextPath = contextPath;
      this.attributes = Maps.newConcurrentMap();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Method handler;
      try {
        handler = API.class.getDeclaredMethod(method.getName(), method.getParameterTypes());
      } catch (NoSuchMethodException e) {
        String msg =
            String.format(
                "%s does not implement %s", PluginServletContext.class, method.toGenericString());
        logger.atSevere().withCause(e).log(msg);
        throw new NoSuchMethodError(msg);
      }
      return handler.invoke(this, args);
    }

    @Override
    public String getContextPath() {
      return contextPath;
    }

    @Override
    public String getInitParameter(String name) {
      return null;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Enumeration getInitParameterNames() {
      return Collections.enumeration(Collections.emptyList());
    }

    @Override
    public ServletContext getContext(String name) {
      return null;
    }

    @Override
    public RequestDispatcher getNamedDispatcher(String name) {
      return null;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String name) {
      return null;
    }

    @Override
    public URL getResource(String name) {
      return null;
    }

    @Override
    public InputStream getResourceAsStream(String name) {
      return null;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Set getResourcePaths(String name) {
      return null;
    }

    @Override
    public Servlet getServlet(String name) {
      return null;
    }

    @Override
    public String getRealPath(String name) {
      return null;
    }

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

    @SuppressWarnings("rawtypes")
    @Override
    public Enumeration getServletNames() {
      return Collections.enumeration(Collections.emptyList());
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Enumeration getServlets() {
      return Collections.enumeration(Collections.emptyList());
    }

    @Override
    public void log(Exception reason, String msg) {
      log(msg, reason);
    }

    @Override
    public void log(String msg) {
      log(msg, null);
    }

    @Override
    public void log(String msg, Throwable reason) {
      logger.atWarning().withCause(reason).log("[plugin %s] %s", plugin.getName(), msg);
    }

    @Override
    public Object getAttribute(String name) {
      return attributes.get(name);
    }

    @Override
    public Enumeration<String> getAttributeNames() {
      return Collections.enumeration(attributes.keySet());
    }

    @Override
    public void setAttribute(String name, Object value) {
      attributes.put(name, value);
    }

    @Override
    public void removeAttribute(String name) {
      attributes.remove(name);
    }

    @Override
    public String getMimeType(String file) {
      return null;
    }

    @Override
    public int getMajorVersion() {
      return 2;
    }

    @Override
    public int getMinorVersion() {
      return 5;
    }

    @Override
    public String getServerInfo() {
      String v = Version.getVersion();
      return "Gerrit Code Review/" + (v != null ? v : "dev");
    }
  }

  interface API {
    String getContextPath();

    String getInitParameter(String name);

    @SuppressWarnings("rawtypes")
    Enumeration getInitParameterNames();

    ServletContext getContext(String name);

    RequestDispatcher getNamedDispatcher(String name);

    RequestDispatcher getRequestDispatcher(String name);

    URL getResource(String name);

    InputStream getResourceAsStream(String name);

    @SuppressWarnings("rawtypes")
    Set getResourcePaths(String name);

    Servlet getServlet(String name);

    String getRealPath(String name);

    String getServletContextName();

    @SuppressWarnings("rawtypes")
    Enumeration getServletNames();

    @SuppressWarnings("rawtypes")
    Enumeration getServlets();

    void log(Exception reason, String msg);

    void log(String msg);

    void log(String msg, Throwable reason);

    Object getAttribute(String name);

    Enumeration<String> getAttributeNames();

    void setAttribute(String name, Object value);

    void removeAttribute(String name);

    String getMimeType(String file);

    int getMajorVersion();

    int getMinorVersion();

    String getServerInfo();
  }
}
