/*
 * 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.
 *
 * $Header:$
 */
package org.apache.beehive.netui.compiler.grammar;

import org.apache.beehive.netui.compiler.AnnotationMemberType;
import org.apache.beehive.netui.compiler.CompilerUtils;
import org.apache.beehive.netui.compiler.Diagnostics;
import org.apache.beehive.netui.compiler.FlowControllerInfo;
import org.apache.beehive.netui.compiler.RuntimeVersionChecker;
import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationInstance;
import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationTypeElementDeclaration;
import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationValue;
import org.apache.beehive.netui.compiler.typesystem.declaration.MemberDeclaration;
import org.apache.beehive.netui.compiler.typesystem.declaration.MethodDeclaration;
import org.apache.beehive.netui.compiler.typesystem.declaration.TypeDeclaration;
import org.apache.beehive.netui.compiler.typesystem.env.CoreAnnotationProcessorEnv;
import org.apache.beehive.netui.compiler.typesystem.type.DeclaredType;
import org.apache.beehive.netui.compiler.typesystem.type.TypeInstance;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;


public class ForwardGrammar
        extends BaseFlowControllerGrammar
{
    private static final String[][] NAVIGATE_TO_VALS = new String[][]
            {
                { NAVIGATE_TO_CURRENT_PAGE_STR, VERSION_8_SP2_STRING },
                { NAVIGATE_TO_PREVIOUS_PAGE_STR, VERSION_8_SP2_STRING },
                { NAVIGATE_TO_PAGE_LEGACY_STR, null },
                { NAVIGATE_TO_PREVIOUS_ACTION_STR, VERSION_8_SP2_STRING },
            };
    
    private static final String[][] DEPRECATED_NAVIGATE_TO_VALS = new String[][]
            {
                { NAVIGATE_TO_PAGE_LEGACY_STR, "warning.return-to-page-deprecated" },
            };

    private static String[][] MUTUALLY_EXCLUSIVE_ATTRS =
            {
                { PATH_ATTR, TILES_DEFINITION_ATTR, RETURN_ACTION_ATTR, NAVIGATE_TO_ATTR, ACTION_ATTR },
                { OUTPUT_FORM_BEAN_TYPE_ATTR, OUTPUT_FORM_BEAN_ATTR },
                { REDIRECT_ATTR, EXTERNAL_REDIRECT_ATTR }
            };

    private static String[][] REQUIRED_ATTRS =
            {
                { NAME_ATTR },
                { PATH_ATTR, TILES_DEFINITION_ATTR, RETURN_ACTION_ATTR, NAVIGATE_TO_ATTR, ACTION_ATTR }
            };

    private static String[][] ATTR_DEPENDENCIES =
            {
                { REDIRECT_ATTR, PATH_ATTR, NAVIGATE_TO_ATTR, ACTION_ATTR },
                { EXTERNAL_REDIRECT_ATTR, PATH_ATTR },
                { RESTORE_QUERY_STRING_ATTR, NAVIGATE_TO_ATTR }
            };
    
    
    public ForwardGrammar( CoreAnnotationProcessorEnv env, Diagnostics diags, String requiredRuntimeVersion,
                           RuntimeVersionChecker runtimeVersionChecker, FlowControllerInfo fcInfo )
    {
        super( env, diags, requiredRuntimeVersion, runtimeVersionChecker, fcInfo );

        addMemberType( NAME_ATTR, getNameType() );
        addMemberType( OUTPUT_FORM_BEAN_TYPE_ATTR, new TypeNameType( null, false, null, this ) );
        addMemberType( OUTPUT_FORM_BEAN_ATTR, new MemberFieldType( null , null, this ) );
        addMemberType( RETURN_ACTION_ATTR, new JavaIdentifierType( null, this, new char[]{ '.' } ) );
        addMemberType( PATH_ATTR, new ExternalPathOrActionType( false, null, this, fcInfo ) );
        addMemberType( ACTION_ATTR, new ValidActionType( null, this, fcInfo ) );
        addMemberType( TILES_DEFINITION_ATTR, new AnnotationMemberType( null, this ) );
        addMemberType( REDIRECT_ATTR, new AnnotationMemberType( null, this ) );
        addMemberType( EXTERNAL_REDIRECT_ATTR, new AbsolutePathType( null , this ) );
        addMemberType( NAVIGATE_TO_ATTR, new EnumType( NAVIGATE_TO_VALS, DEPRECATED_NAVIGATE_TO_VALS, null, this ) );
        addMemberType( RESTORE_QUERY_STRING_ATTR, new AnnotationMemberType( null, this ) );
        
        addMemberArrayGrammar( ACTION_OUTPUTS_ATTR, new ActionOutputGrammar( env, diags, runtimeVersionChecker ) );
    }

    protected AnnotationMemberType getNameType()
    {
        return new ForwardNameType();
    }
    
    public String[][] getMutuallyExclusiveAttrs()
    {
        return MUTUALLY_EXCLUSIVE_ATTRS;
    }

    public String[][] getRequiredAttrs()
    {
        return REQUIRED_ATTRS;
    }

    public String[][] getAttrDependencies()
    {
        return ATTR_DEPENDENCIES;
    }

    protected void onCheckMember( AnnotationTypeElementDeclaration memberDecl, AnnotationValue value,
                                  AnnotationInstance annotation, AnnotationInstance[] parentAnnotations,
                                  MemberDeclaration classMember )
    {
        String valueName = memberDecl.getSimpleName();
        boolean isReturnAction = valueName.equals( RETURN_ACTION_ATTR );
        
        if ( isReturnAction )
        {
            if ( ! getFlowControllerInfo().isNested() )
            {
                addError( value, "error.only-valid-in-nested", new Object[]{ valueName } );
            }
        }
        
        if ( valueName.equals( ACTION_OUTPUTS_ATTR ) && ( ( List ) value.getValue() ).size() > 0 )
        {
            if ( CompilerUtils.getBoolean( annotation, REDIRECT_ATTR, false ).booleanValue() )
            {
                addError( value, "error.action-outputs-with-redirect", REDIRECT_ATTR );
            }
            
            String path = CompilerUtils.getString( annotation, PATH_ATTR, true );
            if ( path != null && CompilerUtils.isAbsoluteURI( path ) )
            {
                addError( value, "error.action-outputs-with-absolute-uri", PATH_ATTR );
            }
        }
        
        //
        // If this is a return-action, store its info in the FlowControllerInfo (which is eventually provided to tools).
        //
        if ( isReturnAction )
        {
            TypeDeclaration outerType = CompilerUtils.getOuterClass( classMember );
            TypeInstance formBeanType =
                    getFlowControllerInfo().addReturnAction( ( String ) value.getValue(), annotation, outerType );
            
            if ( formBeanType != null && ! ( formBeanType instanceof DeclaredType ) )
            {
                addError( annotation, "error.action-invalid-form-bean-type", formBeanType.toString() );
            }
        }
    }
    
    protected class ForwardNameType
        extends UniqueValueType
    {
        public ForwardNameType()
        {
            this( FORWARDS_ATTR );
        }
        
        protected ForwardNameType( String memberGroupName )
        {
            super( memberGroupName, false, false, null, ForwardGrammar.this );
        }

        /**
         * @return a List of AnnotationInstance
         */
        protected List getAdditionalAnnotationsToCheck( MemberDeclaration classMember )
        {
            //
            // curEntity will be either the pageflow class or an action method, where we'll look
            // for @Jpf.Catch annotations that refer to exception-handler methods, which also have
            // forwards that get rolled onto this entity.
            //
            List additionalEntities = new ArrayList();
            
            TypeDeclaration outerType = CompilerUtils.getOuterClass( classMember );
            
            Collection classLevelCatches =
                    getFlowControllerInfo().getMergedControllerAnnotation().getCatches();
            addAdditionalAnnotationsToCheck( classLevelCatches, outerType, additionalEntities );
            
            if ( classMember instanceof MethodDeclaration )
            {
                Collection methodLevelCatches = 
                        CompilerUtils.getAnnotationArrayValue( classMember, ACTION_TAG_NAME, CATCHES_ATTR, true );
                addAdditionalAnnotationsToCheck( methodLevelCatches, outerType, additionalEntities );
            }
            
            return additionalEntities;
        }
        
        private void addAdditionalAnnotationsToCheck( Collection catches,
                                                      TypeDeclaration outerType, List additionalEntities )
        {
            //
            // For each of the given @Jpf.Catch annotations, find the matching @Jpf.ExceptionHandler method and
            // add all of its @Jpf.Forward annotations to the list.
            //
            if ( catches != null )
            {
                for ( Iterator ii = catches.iterator(); ii.hasNext(); )  
                {
                    AnnotationInstance catchAnnotation = ( AnnotationInstance ) ii.next();
                    String methodName = CompilerUtils.getString( catchAnnotation, METHOD_ATTR, false );
                    
                    if ( methodName.length() > 0 )
                    {
                        MethodDeclaration[] allMethods = CompilerUtils.getClassMethods( outerType, null );
                        
                        for ( int i = 0; i < allMethods.length; i++ )
                        {
                            MethodDeclaration method = allMethods[i];
                            AnnotationInstance exHandlerAnnotation =
                                CompilerUtils.getAnnotation( method, EXCEPTION_HANDLER_TAG_NAME );
                            
                            if ( exHandlerAnnotation != null && method.getSimpleName().equals( methodName ) )
                            {
                                Collection forwardAnnotations =
                                        CompilerUtils.getAnnotationArray( exHandlerAnnotation, FORWARDS_ATTR, false );
                                
                                for ( Iterator i3 = forwardAnnotations.iterator(); i3.hasNext(); )  
                                {
                                    AnnotationInstance forwardAnnotation = ( AnnotationInstance ) i3.next();
                                    additionalEntities.add( forwardAnnotation );
                                }
                            }
                        }
                    }
                    
                }
            }
        }

        protected String getErrorMessageExtraInfo()
        {
            return CATCH_TAG_NAME;
        }

        
        protected boolean allowExactDuplicates()
        {
            return true;
        }
    }
}
