package org.drools.rule.builder.dialect.java;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.drools.RuntimeDroolsException;
import org.drools.base.TypeResolver;
import org.drools.compiler.FunctionError;
import org.drools.core.util.StringUtils;
import org.drools.lang.descr.FunctionDescr;
import org.drools.rule.LineMappings;
import org.drools.rule.Package;
import org.drools.rule.builder.FunctionBuilder;
import org.mvel2.integration.impl.MapVariableResolverFactory;
import org.mvel2.templates.TemplateRuntime;


public class JavaFunctionBuilder
    implements
    FunctionBuilder {

    //    private static final StringTemplateGroup functionGroup = new StringTemplateGroup( new InputStreamReader( JavaFunctionBuilder.class.getResourceAsStream( "javaFunction.stg" ) ),
    //                                                                                      AngleBracketTemplateLexer.class );

    private static final String template = StringUtils.readFileAsString( new InputStreamReader( JavaFunctionBuilder.class.getResourceAsStream( "javaFunction.mvel" ) ) );

    public JavaFunctionBuilder() {

    }

    /* (non-Javadoc)
     * @see org.drools.rule.builder.dialect.java.JavaFunctionBuilder#build(org.drools.rule.Package, org.drools.lang.descr.FunctionDescr, org.codehaus.jfdi.interpreter.TypeResolver, java.util.Map)
     */
    public String build(final Package pkg,
                        final FunctionDescr functionDescr,
                        final TypeResolver typeResolver,
                        final Map lineMappings,
                        final List errors) {

        final Map vars = new HashMap();

        vars.put( "package",
                  pkg.getName() );

        vars.put( "imports",
                  pkg.getImports().keySet() );

        final List staticImports = new LinkedList();
        for( Iterator it = pkg.getStaticImports().iterator(); it.hasNext(); ) {
            final String staticImport = (String) it.next();
            if( ! staticImport.endsWith( functionDescr.getName() ) ) {
                staticImports.add( staticImport );
            }
        }
        vars.put( "staticImports",
                  staticImports );

        vars.put( "className",
                  StringUtils.ucFirst( functionDescr.getName() ) );

        vars.put( "methodName",
                  functionDescr.getName() );

        vars.put( "returnType",
                  functionDescr.getReturnType() );

        vars.put( "parameterTypes",
                  functionDescr.getParameterTypes() );

        vars.put( "parameterNames",
                  functionDescr.getParameterNames() );
        
        vars.put("hashCode",
                new Integer(functionDescr.getText().hashCode() ) );

        // Check that all the parameters are resolvable
        final Map params = new HashMap();
        final List names = functionDescr.getParameterNames();
        final List types = functionDescr.getParameterTypes();
        try {
            for ( int i = 0, size = names.size(); i < size; i++ ) {
                params.put( names.get( i ),
                            typeResolver.resolveType( (String) types.get( i ) ) );
            }
        } catch ( final ClassNotFoundException e ) {
            errors.add( new FunctionError( functionDescr,
                                           e,
                                           "unable to resolve type while building function" ) );
        }

        vars.put( "text",
                  functionDescr.getText() );

        final String text = String.valueOf(TemplateRuntime.eval( template, null, new MapVariableResolverFactory(vars)));

        final BufferedReader reader = new BufferedReader( new StringReader( text ) );
        String line = null;
        final String lineStartsWith = "    public static " + functionDescr.getReturnType() + " " + functionDescr.getName();
        int offset = 0;
        try {
            while ( (line = reader.readLine()) != null ) {
                offset++;
                if ( line.startsWith( lineStartsWith ) ) {
                    break;
                }
            }
            functionDescr.setOffset( offset );
        } catch ( final IOException e ) {
            // won't ever happen, it's just reading over a string.
            throw new RuntimeDroolsException( "Error determining start offset with function" );
        }

        final String name = pkg.getName() + "." + StringUtils.ucFirst( functionDescr.getName() );
        final LineMappings mapping = new LineMappings( name );
        mapping.setStartLine( functionDescr.getLine() );
        mapping.setOffset( functionDescr.getOffset() );
        lineMappings.put( name,
                          mapping );

        return text;

    }

}
