/**
 *  Copyright 2005-2015 Red Hat, Inc.
 *
 *  Red Hat 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 io.fabric8.kubernetes.generator.processor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.fabric8.kubernetes.api.KubernetesHelper;
import io.fabric8.kubernetes.api.extensions.Templates;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesList;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.kubernetes.api.model.KubernetesResource;
import io.fabric8.kubernetes.generator.annotation.KubernetesProvider;
import io.fabric8.utils.Strings;

import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.Callable;

import static io.fabric8.kubernetes.api.KubernetesHelper.getName;

@SupportedAnnotationTypes("io.fabric8.kubernetes.generator.annotation.KubernetesProvider")
public class KubernetesProviderProcessor extends AbstractKubernetesAnnotationProcessor {

    private static final ObjectMapper MAPPER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<HasMetadata> provided = new LinkedHashSet<>();

        CompilationTaskFactory compilationTaskFactory = new CompilationTaskFactory(processingEnv);
        Set<TypeElement> providers = new HashSet<>();

        //1st pass collect classes to compile.
        for (Element element : roundEnv.getElementsAnnotatedWith(KubernetesProvider.class)) {
            providers.add(getClassElement(element));
        }

        if (providers.isEmpty()) {
            return true;
        }
        StringWriter writer = new StringWriter();
        try {
            Callable<Boolean> compileTask = compilationTaskFactory.create(providers, writer);
            if (!compileTask.call()) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to compile provider classes. See output below.");
                return false;
            }
        } catch (Exception e) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Error to compile provider classes, due to: " + e.getMessage() + ". See output below.");
            return false;
        } finally {
            String output = writer.toString();
            if (Strings.isNullOrBlank(output)) {
                output = "success";
            }
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Fabric8 model generator compiler output:" + output);
        }

        //2nd pass generate json.
        for (Element element : roundEnv.getElementsAnnotatedWith(KubernetesProvider.class)) {
            try {
                if (element instanceof ExecutableElement) {
                    KubernetesProvider provider = element.getAnnotation(KubernetesProvider.class);

                    ExecutableElement methodElement = (ExecutableElement) element;
                    String methodName = methodElement.getSimpleName().toString();
                    TypeElement classElement = getClassElement(element);
                    Class<?> cls = Class.forName(classElement.getQualifiedName().toString());
                    Object instance = cls.newInstance();

                    Method providerMethod = instance.getClass().getDeclaredMethod(methodName);
                    if (providerMethod != null) {
                        Object obj = providerMethod.invoke(instance);
                        if (obj instanceof HasMetadata) {
                            provided.add((HasMetadata) obj);
                        }
                    }
                }
            } catch (Exception ex) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Error creating Kubernetes configuration.");
            }
        }

        KubernetesResource answer = null;
        try {
            answer = (KubernetesResource)KubernetesHelper.combineJson(provided);

        } catch (Exception e) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to combine provider items");
            return false;
        }

        generateJson(answer);
        return true;
    }

    private KubernetesList createList(Iterable<HasMetadata> objects) {
        StringBuilder sb = new StringBuilder();
        List<HasMetadata> allItems = new ArrayList<>();
        boolean first = true;
        for (HasMetadata obj : objects) {
            if (first) {
                first = false;
            } else {
                sb.append("-");
            }
            
            sb.append(getName(obj));
            allItems.add(obj);
        }
        return new KubernetesListBuilder().
                // TODO KubernetesList no longer has an id/name
                //withName(sb.toString()).
                withItems(allItems).build();
    }
}

@@ -695,384 +695,48 @@
 ort 
-com.fasterxml.jackson.databind.ObjectMapper;%0Aimport com.fasterxml.jackson.databind.SerializationFeature;%0Aimport io.fabric8.kubernetes.api.KubernetesHelper;%0Aimport io.fabric8.kubernetes.api.extensions.Templates;%0Aimport io.fabric8.kubernetes.api.model.HasMetadata;%0Aimport io.fabric8.kubernetes.api.model.KubernetesList;%0Aimport io.fabric8.kubernetes.api.model.KubernetesListBuild
+io.fabric8.kubernetes.api.KubernetesHelp
 er;%0A
@@ -1259,17 +1259,23 @@
 va.util.
-*
+HashSet
 ;%0Aimport
@@ -1289,93 +1289,80 @@
 til.
-concurrent.Callable;%0A%0Aimport static io.fabric8.kubernetes.api.KubernetesHelper.getNam
+LinkedHashSet;%0Aimport java.util.Set;%0Aimport java.util.concurrent.Callabl
 e;%0A%0A
@@ -1546,119 +1546,8 @@
  %7B%0A%0A
-    private static final ObjectMapper MAPPER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);%0A%0A
     
@@ -1663,29 +1663,16 @@
      Set
-%3CHasMetadata%3E
  provide
@@ -3246,108 +3246,8 @@
 ) %7B%0A
-                    KubernetesProvider provider = element.getAnnotation(KubernetesProvider.class);%0A%0A
     
@@ -3804,21 +3804,8 @@
     
- Object obj =
  pro
@@ -3820,170 +3820,99 @@
 hod.
-invoke(instance);%0A                        if (obj instanceof HasMetadata) %7B%0A                            provided.add((HasMetadata) obj);%0A                        %7D
+setAccessible(true);%0A                        provided.add(providerMethod.invoke(instance));
 %0A   
@@ -4269,20 +4269,29 @@
 provided
+.toArray()
 );%0A
-%0A
         
@@ -4524,671 +4524,7 @@
    %7D
-%0A%0A    private KubernetesList createList(Iterable%3CHasMetadata%3E objects) %7B%0A        StringBuilder sb = new StringBuilder();%0A        List%3CHasMetadata%3E allItems = new ArrayList%3C%3E();%0A        boolean first = true;%0A        for (HasMetadata obj : objects) %7B%0A            if (first) %7B%0A                first = false;%0A            %7D else %7B%0A                sb.append(%22-%22);%0A            %7D%0A            %0A            sb.append(getName(obj));%0A            allItems.add(obj);%0A        %7D%0A        return new KubernetesListBuilder().%0A                // TODO KubernetesList no longer has an id/name%0A                //withName(sb.toString()).%0A                withItems(allItems).build();%0A    %7D
 %0A%7D%0A

Fix casting/instaceof issues with HasMetadata in KubernetesProviderProcessor.