/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.lang.plsql;

import java.io.StringReader;
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.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.LanguageVersionHandler;
import net.sourceforge.pmd.lang.ast.Node;
// Root Production comprising PLSQL definitions, and SQL*PLus, DDL, GRANTS etc.
import net.sourceforge.pmd.lang.plsql.ast.ASTInput;
//Covers all executbale code units, such as package and object type bodies, standalone procedures and functions, and triggers 
import net.sourceforge.pmd.lang.plsql.ast.PLSQLParserVisitor;
import net.sourceforge.pmd.lang.plsql.dfa.DataFlowFacade;
import net.sourceforge.pmd.lang.plsql.symboltable.SymbolFacade;

public abstract class AbstractPLSQLParserTst {

    private class Collector<E> implements InvocationHandler {
        private Class<E> clazz = null;
        private Collection<E> collection;

        Collector(Class<E> clazz) {
            this(clazz, new HashSet<E>());
        }

        Collector(Class<E> clazz, Collection<E> coll) {
            this.clazz = clazz;
            this.collection = coll;
        }

        public Collection<E> getCollection() {
            return collection;
        }

        public Object invoke(Object proxy, Method method, Object[] params) throws NoSuchMethodException,
                SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            if (method.getName().equals("visit")) {
                if (clazz.isInstance(params[0])) {
                    collection.add((E) params[0]);
                }
            }

            Method childrenAccept = params[0].getClass().getMethod("childrenAccept",
                    new Class[] { PLSQLParserVisitor.class, Object.class });
            childrenAccept.invoke(params[0], new Object[] { proxy, null });
            return null;
        }
    }

    public <E> Set<E> getNodes(Class<E> clazz, String plsqlCode) {
        return getNodes(LanguageRegistry.getLanguage(PLSQLLanguageModule.NAME).getDefaultVersion(), clazz, plsqlCode);
    }

    public <E> Set<E> getNodes(LanguageVersion languageVersion, Class<E> clazz, String plsqlCode) {
        Collector<E> coll = new Collector<>(clazz);
        LanguageVersionHandler languageVersionHandler = languageVersion.getLanguageVersionHandler();
        ASTInput cu = (ASTInput) languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions())
                .parse(null, new StringReader(plsqlCode));
        PLSQLParserVisitor jpv = (PLSQLParserVisitor) Proxy.newProxyInstance(PLSQLParserVisitor.class.getClassLoader(),
                new Class[] { PLSQLParserVisitor.class }, coll);
        jpv.visit(cu, null);
        return (Set<E>) coll.getCollection();
    }

    public <E> List<E> getOrderedNodes(Class<E> clazz, String plsqlCode) {
        Collector<E> coll = new Collector<>(clazz, new ArrayList<E>());
        LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(PLSQLLanguageModule.NAME)
                .getDefaultVersion().getLanguageVersionHandler();
        ASTInput cu = (ASTInput) languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions())
                .parse(null, new StringReader(plsqlCode));
        PLSQLParserVisitor jpv = (PLSQLParserVisitor) Proxy.newProxyInstance(PLSQLParserVisitor.class.getClassLoader(),
                new Class[] { PLSQLParserVisitor.class }, coll);
        jpv.visit(cu, null);
        SymbolFacade sf = new SymbolFacade();
        sf.initializeWith(cu);
        DataFlowFacade dff = new DataFlowFacade();
        dff.initializeWith(languageVersionHandler.getDataFlowHandler(), cu);
        return (List<E>) coll.getCollection();
    }

    public <E> String dumpNodes(List<E> list) {
        StringBuilder sb = new StringBuilder();
        int index = 0;
        for (E item : list) {
            sb.append("\n node[").append(index).append(item.toString());
            index++;
        }
        return sb.toString();
    }

    public ASTInput buildDFA(String plsqlCode) {
        LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(PLSQLLanguageModule.NAME)
                .getDefaultVersion().getLanguageVersionHandler();
        ASTInput cu = (ASTInput) languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions())
                .parse(null, new StringReader(plsqlCode));
        PLSQLParserVisitor jpv = (PLSQLParserVisitor) Proxy.newProxyInstance(PLSQLParserVisitor.class.getClassLoader(),
                new Class[] { PLSQLParserVisitor.class }, new Collector<>(ASTInput.class));
        jpv.visit(cu, null);
        new SymbolFacade().initializeWith(cu);
        new DataFlowFacade().initializeWith(languageVersionHandler.getDataFlowHandler(), cu);
        return cu;
    }

    public ASTInput parsePLSQL(LanguageVersion languageVersion, String code) {
        LanguageVersionHandler languageVersionHandler = languageVersion.getLanguageVersionHandler();
        return (ASTInput) languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()).parse(null,
                new StringReader(code));
    }

    public ASTInput parsePLSQL(String code) {
        return parsePLSQL(LanguageRegistry.getLanguage(PLSQLLanguageModule.NAME).getDefaultVersion(), code);
    }

    public Node parseLanguage(LanguageVersion languageVersion, String code) {
        LanguageVersionHandler languageVersionHandler = languageVersion.getLanguageVersionHandler();
        return languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()).parse(null,
                new StringReader(code));
    }
}
