/*
 * Copyright 2014 Andrew Gaul <andrew@gaul.org>
 *
 * 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 org.gaul.modernizer_maven_plugin;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.InstructionAdapter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

final class Modernizer {
    private final long javaVersion;
    private final Map<String, Violation> violations;
    private final Collection<String> exclusions;
    private final Collection<String> ignorePackages;

    Modernizer(String javaVersion, Map<String, Violation> violations,
            Collection<String> exclusions, Collection<String> ignorePackages) {
        if (!javaVersion.startsWith("1.")) {
            throw new IllegalArgumentException(
                    "Invalid version, must have the form 1.6");
        }
        long version = Long.parseLong(javaVersion.substring(2));
        Utils.checkArgument(version >= 0);
        this.javaVersion = version;
        this.violations = Utils.createImmutableMap(violations);
        this.exclusions = Utils.createImmutableSet(exclusions);
        this.ignorePackages = Utils.createImmutableSet(ignorePackages);
    }

    Collection<ViolationOccurrence> check(ClassReader classReader)
            throws IOException {
        ModernizerClassVisitor classVisitor = new ModernizerClassVisitor(
                javaVersion, violations, exclusions, ignorePackages);
        classReader.accept(classVisitor, 0);
        return classVisitor.getOccurrences();
    }

    Collection<ViolationOccurrence> check(InputStream is) throws IOException {
        return check(new ClassReader(is));
    }

    static Map<String, Violation> parseFromXml(InputStream is)
            throws IOException, ParserConfigurationException, SAXException {
        Map<String, Violation> map =
                new HashMap<String, Violation>();
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(is);
        doc.getDocumentElement().normalize();

        NodeList nList = doc.getElementsByTagName("violation");
        for (int temp = 0; temp < nList.getLength(); ++temp) {
            Node nNode = nList.item(temp);
            if (nNode.getNodeType() != Node.ELEMENT_NODE) {
                continue;
            }
            Element element = (Element) nNode;
            String version = element.getElementsByTagName("version").item(0)
                    .getTextContent();
            if (!version.startsWith("1.")) {
                throw new IllegalArgumentException(
                        "Invalid version, must have the form 1.6");
            }
            Violation violation = new Violation(
                    element.getElementsByTagName("name").item(0)
                            .getTextContent(),
                    Integer.parseInt(version.substring(2)),
                    element.getElementsByTagName("comment").item(0)
                            .getTextContent());
            map.put(violation.getName(), violation);
        }

        return map;
    }
}

final class ModernizerClassVisitor extends ClassVisitor {
    private final long javaVersion;
    private final Map<String, Violation> violations;
    private final Collection<String> exclusions;
    private final Collection<String> ignorePackages;
    private final Collection<ViolationOccurrence> occurrences =
            new ArrayList<ViolationOccurrence>();
    private String packageName;

    ModernizerClassVisitor(long javaVersion,
            Map<String, Violation> violations, Collection<String> exclusions,
            Collection<String> ignorePackages) {
        super(Opcodes.ASM5);
        Utils.checkArgument(javaVersion >= 0);
        this.javaVersion = javaVersion;
        this.violations = Utils.checkNotNull(violations);
        this.exclusions = Utils.checkNotNull(exclusions);
        this.ignorePackages = Utils.checkNotNull(ignorePackages);
    }

    @Override
    public void visit(int version, int access, String name, String signature,
            String superName, String[] interfaces) {
        packageName = name.substring(0, name.lastIndexOf('/')).replace('/',
                '.');
        for (String itr : interfaces) {
            Violation violation = violations.get(itr);
            checkToken(itr, violation, name, /*lineNumber=*/ -1);
        }
    }

    @Override
    public MethodVisitor visitMethod(int access, final String methodName,
            final String methodDescriptor, final String methodSignature,
            String[] exceptions) {
        MethodVisitor base = super.visitMethod(access, methodName,
                methodDescriptor, methodSignature, exceptions);
        MethodVisitor origVisitor = new MethodVisitor(Opcodes.ASM5, base) {
        };
        InstructionAdapter adapter = new InstructionAdapter(Opcodes.ASM5,
                origVisitor) {
            private int lineNumber = -1;

            @Override
            public void visitFieldInsn(int opcode, String owner, String name,
                    String desc) {
                visitFieldOrMethod(owner, name, desc);
            }

            @Override
            public void visitMethodInsn(int opcode, String owner, String name,
                    String desc, boolean isInterface) {
                if (name.equals("<init>")) {
                    name = "\"<init>\"";
                }
                visitFieldOrMethod(owner, name, desc);
            }

            private void visitFieldOrMethod(String owner, String name,
                    String desc) {
                String token = owner + "." + name + ":" + desc;
                Violation violation = violations.get(token);
                checkToken(token, violation, name, lineNumber);
            }

            @Override
            public void visitLineNumber(int lineNumber, Label start) {
                this.lineNumber = lineNumber;
            }
        };
        return adapter;
    }

    private void checkToken(String token, Violation violation, String name,
            int lineNumber) {
        if (violation != null && !exclusions.contains(token) &&
                javaVersion >= violation.getVersion() &&
                !ignorePackages.contains(packageName)) {
            for (String prefix : ignorePackages) {
                if (packageName.startsWith(prefix + ".")) {
                    return;
                }
            }
            occurrences.add(new ViolationOccurrence(name, lineNumber,
                    violation));
        }
    }

    Collection<ViolationOccurrence> getOccurrences() {
        return occurrences;
    }
}

@@ -5209,32 +5209,70 @@
 %5D interfaces) %7B%0A
+        if (name.contains(%22/%22)) %7B%0A    
         packageN
@@ -5317,16 +5317,37 @@
 Of('/'))
+%0A                    
 .replace
@@ -5351,16 +5351,22 @@
 ace('/',
+ '.');
 %0A       
@@ -5370,21 +5370,56 @@
     
-        '.');
+%7D else %7B%0A            packageName = %22%22;%0A        %7D
 %0A   

Handle default package classes