package org.infinispan.tools.doclet;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

import com.sun.javadoc.Doc;
import com.sun.javadoc.DocErrorReporter;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.ProgramElementDoc;
import com.sun.javadoc.RootDoc;
import com.sun.tools.doclets.standard.Standard;
import com.sun.tools.javadoc.Main;

public class PublicAPIDoclet {
   private static final String PUBLIC_TAG = "@public";
   private static final String PRIVATE_TAG = "@private";

   public static void main(String[] args) {
      String name = PublicAPIDoclet.class.getName();
      Main.execute(name, name, args);
   }

   public static boolean validOptions(String[][] options, DocErrorReporter reporter) throws java.io.IOException {
      return Standard.validOptions(options, reporter);
   }

   public static LanguageVersion languageVersion() {
      return LanguageVersion.JAVA_1_5;
   }

   public static int optionLength(String option) {
      return Standard.optionLength(option);
   }

   public static boolean start(RootDoc root) throws java.io.IOException {
      return Standard.start((RootDoc) filter(root, RootDoc.class));
   }

   private static boolean isPublicAPI(Doc doc) {
      if (doc.tags(PRIVATE_TAG).length > 0)
         return false;
      if (doc instanceof ProgramElementDoc) {
         ProgramElementDoc peDoc = (ProgramElementDoc) doc;
         if (peDoc.containingClass() != null && peDoc.containingClass().tags(PUBLIC_TAG).length > 0)
            return true;
         if (peDoc.containingPackage().tags(PUBLIC_TAG).length > 0)
            return true;
      }
      return doc.tags(PUBLIC_TAG).length > 0;
   }

   private static Object filter(Object obj, Class<?> expect) {
      if (obj == null)
         return null;
      Class<?> cls = obj.getClass();
      if (cls.getName().startsWith("com.sun.")) {
         return Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), new FilterHandler(obj));
      } else if (obj instanceof Object[]) {
         Class<?> componentType = expect.getComponentType();
         if (componentType == null) {
            return obj;
         }
         String componentName = componentType.getName();
         if (!componentName.startsWith("com.sun.javadoc"))
            return obj;

         Object[] array = (Object[]) obj;
         List<Object> list = new ArrayList<>(array.length);
         for (int i = 0; i < array.length; i++) {
            Object entry = array[i];
            if ((entry instanceof Doc) && !isPublicAPI((Doc) entry))
               continue;
            list.add(entry);
         }
         return list.toArray((Object[]) Array.newInstance(componentType, list.size()));
      } else {
         return obj;
      }

   }

   private static class FilterHandler implements InvocationHandler {
      private Object target;

      public FilterHandler(Object target) {
         this.target = target;
      }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         if (args != null) {
            String methodName = method.getName();
            if (methodName.equals("compareTo") || methodName.equals("equals") || methodName.equals("overrides")
                  || methodName.equals("subclassOf")) {
               args[0] = unwrap(args[0]);
            }
         }
         try {
            return filter(method.invoke(target, args), method.getReturnType());
         } catch (InvocationTargetException e) {
            throw e.getTargetException();
         }
      }

      private Object unwrap(Object proxy) {
         if (proxy instanceof Proxy)
            return ((FilterHandler) Proxy.getInvocationHandler(proxy)).target;
         return proxy;
      }
   }

}
