/*
 *  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.vysper.compliance.reporting;

import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableCollection;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.vysper.compliance.SpecCompliance;
import org.apache.vysper.compliance.SpecCompliant;

import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.AnnotationProcessorFactory;
import com.sun.mirror.apt.Filer;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.Declaration;
import com.sun.mirror.declaration.FieldDeclaration;
import com.sun.mirror.declaration.MemberDeclaration;
import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;

/**
 * this generates from all SpecCompliant annotations a HTML doc to show what parts of the spec are covered.
 * The table is sorted by implemented spec section and links out to the spec and the apidocs.
 *
 * apt can be invoked like this (tools.jar must be on the classpath)
 * apt -cp build/ant/classes;lib/* -s src/main/java  -factory org.apache.vysper.compliance.reporting.DocumentSpecCompliantAnnotationFactory
 * it generates a file spec_compliance.html in src/main/java
 * 
 * @author The Apache MINA Project (dev@mina.apache.org)
 */
public class DocumentSpecCompliantAnnotationFactory implements AnnotationProcessorFactory {
    // Process any set of annotations
    private static final Collection<String> supportedAnnotations = unmodifiableCollection(Arrays
            .asList("org.apache.vysper.compliance.*"));

    // No supported options
    private static final Collection<String> supportedOptions = emptySet();

    public Collection<String> supportedAnnotationTypes() {
        return supportedAnnotations;
    }

    public Collection<String> supportedOptions() {
        return supportedOptions;
    }

    public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env) {
        return new SpecCompliantClassAP(env);
    }

    private static class SpecCompliantClassAP implements AnnotationProcessor {

        protected final AnnotationProcessorEnvironment env;

        protected AnnotationTypeDeclaration specCompliantAnnotation;

        protected AnnotationTypeDeclaration specCompliantCollectionAnnotation;

        protected Map<String, SpecDoc> map = new TreeMap<String, SpecDoc>();

        protected PrintWriter fileWriter;

        SpecCompliantClassAP(AnnotationProcessorEnvironment env) {
            this.env = env;
            specCompliantAnnotation = (AnnotationTypeDeclaration) env
                    .getTypeDeclaration("org.apache.vysper.compliance.SpecCompliant");
            specCompliantCollectionAnnotation = (AnnotationTypeDeclaration) env
                    .getTypeDeclaration("org.apache.vysper.compliance.SpecCompliance");

            try {
                fileWriter = env.getFiler().createTextFile(Filer.Location.SOURCE_TREE, "",
                        new File("spec_compliance.html"), null);
            } catch (IOException e) {
                throw new RuntimeException("could not write to output file", e);
            }
        }

        public void process() {

            // Retrieve all declarations with SpecCompliant annotations
            Collection<Declaration> declarations = env.getDeclarationsAnnotatedWith(specCompliantAnnotation);
            System.out.println("number of solitairy @SpecCompliant: " + declarations.size());
            for (Declaration declaration : declarations) {
                final SpecDoc doc = new SpecDoc(declaration, declaration.getAnnotation(SpecCompliant.class));
                map.put(doc.getKey(), doc);
            }

            // Retrieve all SpecCompliance-typed declarations and extract SpecCompliant annotations 
            Collection<Declaration> moreDeclarations = env
                    .getDeclarationsAnnotatedWith(specCompliantCollectionAnnotation);
            System.out.println("number of @SpecCompliance: " + moreDeclarations.size());
            for (Declaration declaration : moreDeclarations) {
                final SpecCompliance compliance = declaration.getAnnotation(SpecCompliance.class);
                final SpecCompliant[] specCompliants = compliance.compliant();
                for (SpecCompliant specCompliant : specCompliants) {
                    if (specCompliant == null)
                        continue;

                    final SpecDoc doc = new SpecDoc(declaration, specCompliant);
                    map.put(doc.getKey(), doc);
                }
            }

            // visit every annotation fromt the now sorted map

            // write the HTML file
            fileWriter
                    .println("<html><head>"
                            + "<link rel='stylesheet' type='text/css' href='http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css'>\n"
                            + "<link rel='stylesheet' type='text/css' href='http://yui.yahooapis.com/2.7.0/build/base/base-min.css'>\n"
                            + "</head><body><table><thead><th>spec</th><th>section</th><th>package</th><th>class</th><th>field/method/etc.</th><th>coverage</th><th>implementation</th><th>comment</th></thead>");
            for (String key : map.keySet()) {
                fileWriter.print("<tr>");
                System.out.println(key);
                final SpecDoc doc = map.get(key);

                // spec
                fileWriter.print("<td>");
                final String specURL = doc.getSpecDocURL();
                if (specURL != null)
                    fileWriter.print("<a href='" + specURL + "'>");
                final String specDoc = doc.getSpecDoc();
                if (specDoc != null)
                    fileWriter.print(specDoc);
                if (specURL != null)
                    fileWriter.print("</a>");
                fileWriter.print("</td>");

                // spec section
                fileWriter.print("<td>");
                String specSection = doc.getSpecSection();
                if (specSection != null && specSection.length() > 0) {
                    if (specSection.endsWith("."))
                        specSection = specSection.substring(0, specSection.length() - 1);
                    String anchorLink = Character.isDigit(specSection.charAt(0)) ? "#section-" : "#appendix-";
                    if (specURL != null)
                        fileWriter.print("<a href='" + specURL + anchorLink + specSection + "'>");
                    fileWriter.print(specSection);
                    if (specURL != null)
                        fileWriter.print("</a>");
                }
                fileWriter.print("</td>");

                // package
                fileWriter.print("<td>");
                final String packageName = doc.getPackage();
                if (packageName != null) {
                    fileWriter.print(o_a_v_shortened(packageName));
                }
                fileWriter.print("</td>");

                // class
                fileWriter.print("<td>");
                final String className = doc.getClassName();
                if (className != null) {
                    fileWriter.print("<a href='" + doc.getFQClassName().replace(".", "/") + ".html'>");
                    fileWriter.print(o_a_v_cut(packageName, className));
                    fileWriter.print("</a>");
                }
                fileWriter.print("</td>");

                // class element
                fileWriter.print("<td>");
                final String member = doc.getMember();
                if (member != null) {
                    if (className != null) {
                        fileWriter.print("<a href='" + className.replace(".", "/") + ".html#"
                                + doc.getMemberAnchor().replace(",", ",%20") + "'>");
                    }
                    fileWriter.print(o_a_v_cut(packageName, member));
                    if (className != null) {
                        fileWriter.print("</a>");
                    }
                }
                fileWriter.print("</td>");

                // coverage
                fileWriter.print("<td>");
                final SpecCompliant.ComplianceCoverage coverage = doc.getCoverageLevel();
                if (coverage != null)
                    fileWriter.print(coverage.toString().toLowerCase());
                fileWriter.print("</td>");

                // status
                fileWriter.print("<td>");
                final SpecCompliant.ComplianceStatus complianceStatus = doc.getComplianceStatus();
                if (complianceStatus != null)
                    fileWriter.print(complianceStatus.toString().toLowerCase());
                fileWriter.print("</td>");

                // comment
                fileWriter.print("<td>");
                final String comment = doc.getComment();
                if (comment != null)
                    fileWriter.print(comment);
                fileWriter.print("</td>");

                fileWriter.println("</tr>");

            }
            fileWriter.println("</table></body>");
        }

        private String o_a_v_shortened(String packageString) {
            if (packageString != null && packageString.contains("org.apache.vysper."))
                return packageString.replace("org.apache.vysper.", "o.a.v.");
            return packageString;
        }

        private String o_a_v_cut(String packageString, String memberString) {
            if (memberString != null && memberString.contains(packageString + "."))
                return memberString.replace(packageString + ".", "");
            return memberString;
        }

    }

    static class SpecDoc {

        Declaration declaration;

        SpecCompliant specCompliant;

        SpecDoc(Declaration declaration, SpecCompliant specCompliant) {
            this.declaration = declaration;
            this.specCompliant = specCompliant;
        }

        public String getClassName() {
            if (declaration instanceof FieldDeclaration || declaration instanceof MethodDeclaration) {
                MemberDeclaration memberDeclaration = (MemberDeclaration) declaration;
                return memberDeclaration.getDeclaringType().getQualifiedName();
            } else {
                return declaration.getSimpleName();
            }
        }

        public String getFQClassName() {
            if (declaration instanceof FieldDeclaration || declaration instanceof MethodDeclaration) {
                MemberDeclaration memberDeclaration = (MemberDeclaration) declaration;
                return memberDeclaration.getDeclaringType().getQualifiedName();
            } else if (declaration instanceof ClassDeclaration) {
                ClassDeclaration classDeclaration = (ClassDeclaration) declaration;
                return classDeclaration.getPackage() + "." + classDeclaration.getSimpleName();
            } else {
                return declaration.getSimpleName();
            }
        }

        public String getPackage() {
            if (declaration instanceof TypeDeclaration) {
                TypeDeclaration typeDeclaration = (TypeDeclaration) declaration;
                return typeDeclaration.getPackage().getQualifiedName();
            } else if (declaration instanceof FieldDeclaration || declaration instanceof MethodDeclaration) {
                MemberDeclaration memberDeclaration = (MemberDeclaration) declaration;
                return memberDeclaration.getDeclaringType().getPackage().getQualifiedName();
            } else {
                return null;
            }
        }

        public String getMember() {
            if (declaration instanceof FieldDeclaration || declaration instanceof MethodDeclaration) {
                MemberDeclaration memberDeclaration = (MemberDeclaration) declaration;
                return declaration.getSimpleName();
            } else {
                return null;
            }
        }

        public String getMemberAnchor() {
            if (declaration instanceof FieldDeclaration || declaration instanceof MethodDeclaration) {
                MemberDeclaration memberDeclaration = (MemberDeclaration) declaration;
                return declaration.toString();
            } else {
                return null;
            }
        }

        public String getKey() {
            return getSpecDoc() + " " + getSpecSection() + " " + getClassName() + " " + getMember() + " "
                    + getCoverageLevel() + " " + getComplianceStatus();
        }

        public String getSpecSection() {
            return specCompliant.section();
        }

        public String getSpecDoc() {
            final String specRaw = specCompliant.spec();
            if (specRaw == null)
                return null;
            return specRaw.toLowerCase();
        }

        public String getSpecDocURL() {
            final String spec = getSpecDoc();
            if (spec == null)
                return null;

            if (spec.startsWith("xep"))
                return "http://xmpp.org/extensions/" + spec + ".html";
            if (spec.startsWith("rfc")) {
                if (!spec.contains("bis")) {
                    return "http://tools.ietf.org/html/" + spec;
                } else {
                    return "http://tools.ietf.org/html/draft-saintandre-" + spec;
                }
            }
            return null;
        }

        public SpecCompliant.ComplianceCoverage getCoverageLevel() {
            return specCompliant.coverage();
        }

        public SpecCompliant.ComplianceStatus getComplianceStatus() {
            return specCompliant.status();
        }

        public String getComment() {
            return specCompliant.comment();
        }
    }
}
