/*
 * 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.
 */

import bsh.*;
import java.util.regex.Pattern;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import org.codehaus.plexus.util.StringUtils;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;

// only on start of line
this.tag = "\\r?\\n\\s*\\*?\\s*@(\\w+)";
// zero width lookahead to next tag or end of comment
this.tagOrEndComment = "(?=" + tag + "|\\*/)";
this.commentTextPattern = Pattern.compile( "(?s)/\\*\\*(.*?)" + tagOrEndComment );
this.tagsPattern = Pattern.compile( "(?s)" + tag + "\\s*(.*?)" + tagOrEndComment );
this.descriptionPattern = Pattern.compile( "(?s)\\r?\\n\\s*\\*" );
this.typePattern = Pattern.compile( "type\\s*=\\s*\"(.*?)\"" );
this.expressionPattern = Pattern.compile( "expression\\s*=\\s*\"(.*?)\"" );
this.defaultValuePattern = Pattern.compile( "default-value\\s*=\\s*\"(.*?)\"" );
this.phasePattern = Pattern.compile( "phase\\s*=\\s*\"(.*?)\"" );
this.goalPattern = Pattern.compile( "goal\\s*=\\s*\"(.*?)\"" );
this.lifecyclePattern = Pattern.compile( "lifecycle\\s*=\\s*\"(.*?)\"" );
this.rolePattern = Pattern.compile( "role\\s*=\\s*\"(.*?)\"" );
this.roleHintPattern = Pattern.compile( "roleHint\\s*=\\s*\"(.*?)\"" );

setAccessibility( true );

createParameter( text, method )
{
    if ( method.startsWith( "set" ) )
    {
        this.name = StringUtils.uncapitalise( method.substring( 3 ) );
        this.desc = getDescription( text );
        this.tags = getTags( text );

        this.parameter = new Parameter();
        parameter.setName( name );
        parameter.setDescription( desc );
        parameter.setRequired( tags.containsKey( "required" ) );
        parameter.setEditable( !tags.containsKey( "readonly" ) );
        this.deprecation = tags.get( "deprecated" );
        if ( deprecation != null )
        {
            parameter.setDeprecated( deprecation );
        }
        this.alias = tags.get( "alias" );
        if ( alias != null )
        {
            parameter.setAlias( alias );
        }
        this.value = tags.get( "parameter" );
        if ( value != null )
        {
            m = typePattern.matcher( value );
            if ( m.find() )
            {
                parameter.setType( m.group( 1 ) );
            }
            else
            {
                parameter.setType( "java.lang.Object" );
            }
            m = expressionPattern.matcher( value );
            if ( m.find() )
            {
                parameter.setExpression( m.group( 1 ) );
            }
            m = defaultValuePattern.matcher( value );
            if ( m.find() )
            {
                parameter.setDefaultValue( m.group( 1 ) );
            }
        }
        value = tags.get( "component" );
        if ( value != null )
        {
            m = rolePattern.matcher( value );
            if ( m.find() )
            {
                role = m.group( 1 );
            }
            m = roleHintPattern.matcher( value );
            if ( m.find() )
            {
                roleHint = m.group( 1 );
            }
            else
            {
                roleHint = null;
            }
            parameter.setRequirement( new Requirement( role, roleHint ) );
        }
        return parameter;
    }
    return null;
}

getTags( text )
{
    this.matcher = tagsPattern.matcher( text );
    this.tags = new HashMap();
    while ( matcher.find() )
    {
        this.tagname = matcher.group( 1 );
        this.tagvalue = matcher.group( 2 );
        tags.put( tagname, tagvalue.trim() );
    }
    return tags;
}

getDescription( text )
{
    this.matcher = commentTextPattern.matcher( text );
    if ( matcher.find() )
    {
        this.input = matcher.group( 1 );
        return descriptionPattern.matcher( input ).replaceAll( "" ).trim();
    }
    else
    {
        return "";
    }
}

extract( file, encoding, mojoDescriptor )
{
    this.parser = new Parser( new InputStreamReader( new FileInputStream( file ), encoding ) );
    parser.setRetainComments( true );

    this.lastNode = null;
    this.firstComment = null;
    while ( !parser.Line() )
    {
        this.node = parser.popNode();

        if ( node instanceof BSHFormalComment && firstComment == null )
        {
            firstComment = node;
        }

        if ( node instanceof BSHMethodDeclaration )
        {
            if ( lastNode instanceof BSHFormalComment )
            {
                this.text = lastNode.text;

                this.method = node.name;

                this.parameter = createParameter( text, method );
                if ( parameter != null )
                {
                    if ( "${reports}".equals( parameter.getExpression() ) )
                    {
                        mojoDescriptor.setRequiresReports( true );
                    }
                    mojoDescriptor.addParameter( parameter );
                }

                if ( firstComment == lastNode )
                {
                    firstComment = null;
                }
            }
        }
        lastNode = node;
    }
    if ( firstComment != null )
    {
        this.text = firstComment.text;

        mojoDescriptor.setDescription( getDescription( text ) );
        this.tags = getTags( text );
        mojoDescriptor.setGoal( tags.get( "goal" ) );
        mojoDescriptor.setPhase( tags.get( "phase" ) );
        this.value = tags.get( "requiresDependencyResolution" );
        // TODO: share with java extractor
        if ( value == null || value.length() == 0 )
        {
            value = "runtime";
        }
        mojoDescriptor.setDependencyResolutionRequired( value );

        mojoDescriptor.setProjectRequired( tags.containsKey( "requiresProject" ) );
        mojoDescriptor.setOnlineRequired( tags.containsKey( "requiresOnline" ) );

        this.value = tags.get( "execute" );
        if ( value != null )
        {
            m = phasePattern.matcher( value );
            if ( m.find() )
            {
                mojoDescriptor.setExecutePhase( m.group( 1 ) );
            }

            m = goalPattern.matcher( value );
            if ( m.find() )
            {
                mojoDescriptor.setExecuteGoal( m.group( 1 ) );
            }

            if ( mojoDescriptor.getExecutePhase() == null || mojoDescriptor.getExecuteGoal() == null )
            {
                throw new InvalidPluginDescriptorException( "@execute must have a phase or goal" );
            }

            if ( mojoDescriptor.getExecutePhase() != null && mojoDescriptor.getExecuteGoal() != null )
            {
                throw new InvalidPluginDescriptorException( "@execute must have only one of a phase or goal" );
            }

            m = lifecyclePattern.matcher( value );
            if ( m.find() )
            {
                mojoDescriptor.setExecuteLifecycle( m.group( 1 ) );
                if ( mojoDescriptor.getExecuteGoal() != null )
                {
                    throw new InvalidPluginDescriptorException( "@execute lifecycle requires a phase instead of a goal" );
                }
            }
        }
    }
}

extract( file, encoding, mojoDescriptor );
