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

import org.apache.beehive.netui.compiler.genmodel.GenStrutsApp;
import org.apache.beehive.netui.compiler.grammar.ControllerGrammar;
import org.apache.beehive.netui.compiler.grammar.WebappPathOrActionType;
import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationInstance;
import org.apache.beehive.netui.compiler.typesystem.declaration.ClassDeclaration;
import org.apache.beehive.netui.compiler.typesystem.declaration.FieldDeclaration;
import org.apache.beehive.netui.compiler.typesystem.declaration.Modifier;
import org.apache.beehive.netui.compiler.typesystem.declaration.PackageDeclaration;
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.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;


public class PageFlowChecker
        extends FlowControllerChecker
        implements JpfLanguageConstants
{
    public PageFlowChecker( CoreAnnotationProcessorEnv env, Diagnostics diagnostics, FlowControllerInfo fcInfo )    
    {
        super( env, fcInfo, diagnostics );
    }

    protected void checkField( FieldDeclaration field, TypeDeclaration jclass )
    {
        //
        // Check to make sure that if this is a Shared Flow field, its type matches up with the type declared
        // for the shared flow of that name.
        //
        AnnotationInstance sfFieldAnn = CompilerUtils.getAnnotation( field, SHARED_FLOW_FIELD_TAG_NAME );
        
        if ( sfFieldAnn != null )
        {
            String sharedFlowName = CompilerUtils.getString( sfFieldAnn, NAME_ATTR, true );

            // sharedFlow name is a required attribute, if not defined return and let apt error.
            if (sharedFlowName == null) {
                return;
            }

            Collection sharedFlowRefs =
                    getFCSourceFileInfo().getMergedControllerAnnotation().getSharedFlowRefs();
            
            boolean foundOne = false;
            
            if ( sharedFlowRefs != null )
            {
                for ( Iterator ii = sharedFlowRefs.iterator(); ii.hasNext(); )  
                {
                    AnnotationInstance sharedFlowRef = ( AnnotationInstance ) ii.next();
                    if ( sharedFlowName.equals( CompilerUtils.getString( sharedFlowRef, NAME_ATTR, true ) ) )
                    {
                        foundOne = true;
                        
                        TypeInstance sfType = CompilerUtils.getTypeInstance( sharedFlowRef, TYPE_ATTR, true );
                        TypeInstance ft = field.getType();
                        
                        if ( ! ( sfType instanceof DeclaredType )
                             || ! CompilerUtils.isAssignableFrom( ft, ( ( DeclaredType ) sfType ).getDeclaration() ) )
                        {
                            getDiagnostics().addError(
                                    field, "error.field-not-assignable",
                                    CompilerUtils.getDeclaration( ( DeclaredType ) sfType ).getQualifiedName() );
                        }
                    }
                }
            }
            
            if ( ! foundOne )
            {
                getDiagnostics().addError( sfFieldAnn, "error.no-matching-shared-flow-declared",
                                           SHARED_FLOW_REF_TAG_NAME, sharedFlowName );
            }
        }
        else if ( CompilerUtils.isAssignableFrom( SHARED_FLOW_BASE_CLASS, field.getType(), getEnv() )
                  && ! CompilerUtils.isAssignableFrom( GLOBALAPP_BASE_CLASS, field.getType(), getEnv() ) )
        {
            // Output a warning if the field type extends SharedFlowController but there's no @Jpf.SharedFlowField
            // annotation (in which case the field won't get auto-initialized at runtime.
            getDiagnostics().addWarning( field, "warning.shared-flow-field-no-annotation",
                                         field.getSimpleName(), SHARED_FLOW_BASE_CLASS,
                                         ANNOTATION_INTERFACE_PREFIX + SHARED_FLOW_FIELD_TAG_NAME );
        }
        
        super.checkField( field, jclass );
    }

    protected void doAdditionalClassChecks( ClassDeclaration jpfClass )
    {
        // Make sure there are no other page flows in this package/directory.
        checkForOverlappingClasses( jpfClass, JPF_BASE_CLASS, JPF_FILE_EXTENSION_DOT, "error.overlapping-pageflows" );
        
        PackageDeclaration pkg = jpfClass.getPackage();
        File jpfFile = CompilerUtils.getSourceFile( jpfClass, true );
        File parentDir = jpfFile.getParentFile();
        
        //
        // Check the package name.
        //
        String jpfPackageName = pkg.getQualifiedName();
        
        if ( jpfPackageName != null && jpfPackageName.length() > 0 )
        {
            String expectedPackage = parentDir.getAbsolutePath().replace( '\\', '/' ).replace( '/', '.' );
            
            if ( ! expectedPackage.endsWith( jpfPackageName ) )
            {
                getDiagnostics().addError( jpfClass, "error.wrong-package-for-directory", parentDir.getPath() );
            }
        }

        //
        // Issue a warning if the class name is the same as the parent package name.
        // This causes ambiguity when resolving inner classes.
        //
        if ( jpfClass.getSimpleName().equals( pkg.getQualifiedName() ) )
        {
            getDiagnostics().addWarning( jpfClass, "warning.classname-same-as-package" );
        }
        
        //
        // Make sure every .jpf has a begin action if the class isn't abstract.
        //
        boolean isAbstract = jpfClass.hasModifier( Modifier.ABSTRACT );
        FlowControllerInfo fcInfo = getFCSourceFileInfo();
        
        if ( ! WebappPathOrActionType.actionExists( BEGIN_ACTION_NAME, jpfClass, null, getEnv(), fcInfo, true )
             && ! isAbstract )
        {
            getDiagnostics().addError( jpfClass, "error.no-begin-action" );
        }

        //
        // Make sure every nested pageflow has a returnAction.  Return actions are added by ForwardGrammar, but
        // here we also need to add them for inherited Forwards and SimpleActions.
        //
        if ( fcInfo.isNested() )
        {
            MergedControllerAnnotation mca = fcInfo.getMergedControllerAnnotation();
            addReturnActions( mca.getSimpleActions(), fcInfo, jpfClass, CONDITIONAL_FORWARDS_ATTR );
            addReturnActions( mca.getForwards(), fcInfo, jpfClass, null );
            
            if ( ! isAbstract && fcInfo.countReturnActions() == 0 )
            {
                getDiagnostics().addError( jpfClass, "error.no-return-action",
                                           ANNOTATION_INTERFACE_PREFIX + FORWARD_TAG_NAME,
                                           RETURN_ACTION_ATTR );
            }                
        }        
    }
    
    private void addReturnActions( Collection forwardAnnotations, FlowControllerInfo fcInfo,
                                   TypeDeclaration outerType, String childArrayAttr )
    {
        for ( Iterator ii = forwardAnnotations.iterator(); ii.hasNext(); )  
        {
            AnnotationInstance ann = ( AnnotationInstance ) ii.next();
            String returnAction = CompilerUtils.getString( ann, RETURN_ACTION_ATTR, true );
            if ( returnAction != null ) fcInfo.addReturnAction( returnAction, ann, outerType );
            
            if ( childArrayAttr != null )
            {
                Collection children = CompilerUtils.getAnnotationArray( ann, childArrayAttr, true );
                if ( children != null ) addReturnActions( children, fcInfo, outerType, null );
            }
        }
    }
    
    protected String getDesiredBaseClass( ClassDeclaration jclass )
    {
        return JPF_BASE_CLASS;
    }

    protected GenStrutsApp createStrutsApp( ClassDeclaration jclass )
        throws IOException, FatalCompileTimeException
    {
        File sourceFile = CompilerUtils.getSourceFile( jclass, true );
        return new GenStrutsApp( sourceFile, jclass, getEnv(), getFCSourceFileInfo(), true, getDiagnostics() );
    }

    protected AnnotationGrammar getControllerGrammar()
    {
        return new JpfControllerGrammar();
    }
    
    private class JpfControllerGrammar
        extends ControllerGrammar
    {
        public JpfControllerGrammar()
        {
            super( PageFlowChecker.this.getEnv(), PageFlowChecker.this.getDiagnostics(),
                   PageFlowChecker.this.getRuntimeVersionChecker(), PageFlowChecker.this.getFCSourceFileInfo() );
            addMemberType( NESTED_ATTR, new AnnotationMemberType( null, this ) );
            addMemberType( LONGLIVED_ATTR, new AnnotationMemberType( null, this ) );
        }
    }
}
