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

package org.netbeans.modules.j2ee.persistence.action;

import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.modules.j2ee.persistence.spi.entitymanagergenerator.EntityManagerGenerationStrategyResolver;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import java.io.IOException;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.j2ee.persistence.spi.entitymanagergenerator.ApplicationManagedResourceTransactionInJ2SE;
import org.netbeans.modules.j2ee.persistence.spi.entitymanagergenerator.EntityManagerGenerationStrategy;
import org.netbeans.modules.j2ee.persistence.api.PersistenceScope;
import org.netbeans.modules.j2ee.persistence.dd.PersistenceMetadata;
import org.netbeans.modules.j2ee.persistence.dd.common.Persistence;
import org.netbeans.modules.j2ee.persistence.dd.common.PersistenceUnit;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.Parameters;

/**
 * Generates appropriate code for retrieving and invoking <code>javax.persistence.EntityManager</code>.
 * The generated code depends on the target class' enviroment.
 *
 * TODO: move this class to different package if anybody else wants to use it
 * @author Martin Adamek, Erno Mononen
 */

public final class EntityManagerGenerator {
    
    /**
     * The fully qualified name of the target class.
     */
    private final String fqn;
    /**
     *  The target java source file.
     */
    private final JavaSource targetSource;
    /**
     * The file object of the target source file.
     */
    private final FileObject targetFo;
    
    /**
     * The project to which the target file belongs.
     */
    private final Project project;
    
    /**
     * Creates a new EntityManagerGenerator.
     * @param targetFo the file object of the target java source file.
     * @param fqn the fully qualified name of the target java class.
     */
    public EntityManagerGenerator(FileObject targetFo, String fqn) {
        this.fqn = fqn;
        this.targetFo = targetFo;
        this.targetSource = JavaSource.forFileObject(targetFo);
        this.project = FileOwnerQuery.getOwner(targetFo);
    }

    /**
     * Generates the code needed for retrieving and invoking
     * an instance of <code>javax.persistence.EntityManager</code>. The generated 
     * code depends on the environment of the target class (e.g. whether
     * it supports injection or not).
     * 
     * @param options the options for the generation. Must not be null.
     * @return the modified file object of the target java class.
     */
    public FileObject generate(final GenerationOptions options) throws IOException{
        
        final Class<? extends EntityManagerGenerationStrategy> strategyClass = getStrategy();
    
        if (strategyClass == null){
            NotifyDescriptor d = new NotifyDescriptor.Message(
                    NbBundle.getMessage(EntityManagerGenerator.class, "ERR_NotSupported"), NotifyDescriptor.INFORMATION_MESSAGE);
            DialogDisplayer.getDefault().notify(d);
            
            return targetFo;
        }
        
        return generate(options, strategyClass);
        
    }
        
    /**
     * Generates the code needed for retrieving and invoking
     * an instance of <code>javax.persistence.EntityManager</code>. The generated 
     * code depends on the given <code>strategyClass</code>. 
     * 
     * @param options the options for the generation. Must not be null.
     * @param strategyClass the generation strategy that should be used. Must not be null.
     * @return the modified file object of the target java class.
     */
    public FileObject generate(final GenerationOptions options, 
            final Class<? extends EntityManagerGenerationStrategy> strategyClass) throws IOException{
    
        Parameters.notNull("options", options); //NOI18N
        Parameters.notNull("strategyClass", strategyClass); //NOI18N
        
        Task task = new Task<WorkingCopy>() {
            
            public void run(WorkingCopy workingCopy) throws Exception {
                
                workingCopy.toPhase(Phase.RESOLVED);
                CompilationUnitTree cut = workingCopy.getCompilationUnit();
                TreeMaker make = workingCopy.getTreeMaker();
                
                for (Tree typeDeclaration : cut.getTypeDecls()){
                    if (TreeUtilities.CLASS_TREE_KINDS.contains(typeDeclaration.getKind())){
                        ClassTree clazz = (ClassTree) typeDeclaration;
                        EntityManagerGenerationStrategy strategy = instantiateStrategy(strategyClass, workingCopy, make, clazz, options);
                        workingCopy.rewrite(clazz, strategy.generate());
                    }
                }
            }
        };
        
        targetSource.runModificationTask(task).commit();
        
        return targetFo;
    }
    
    private Class<? extends EntityManagerGenerationStrategy> getStrategy(){

        EntityManagerGenerationStrategyResolver resolver = project.getLookup().lookup(EntityManagerGenerationStrategyResolver.class);
        if (resolver != null){
            return resolver.resolveStrategy(targetFo);
        }
        
        // must be a java se project (we don't want it to implement the EntityManagerGenerationStrategyResolver SPI)
        return ApplicationManagedResourceTransactionInJ2SE.class;
    }
    
    private EntityManagerGenerationStrategy instantiateStrategy(Class<? extends EntityManagerGenerationStrategy> strategy, WorkingCopy workingCopy,
            TreeMaker make, ClassTree clazz, GenerationOptions options){
        
        EntityManagerGenerationStrategy result = null;
        
        try{
            result = strategy.newInstance();
            result.setClassTree(clazz);
            result.setWorkingCopy(workingCopy);
            result.setGenerationOptions(options);
            result.setTreeMaker(make);
            result.setPersistenceUnit(getPersistenceUnit());
        } catch (IllegalAccessException iae){
            throw new RuntimeException(iae); //TODO
        } catch (InstantiationException ie){
            throw new RuntimeException(ie); //TODO
        }
        
        return result;
    }
    
    private  PersistenceUnit getPersistenceUnit() {
        PersistenceScope persistenceScope = PersistenceScope.getPersistenceScope(targetFo);
        
        if (persistenceScope == null){
            return null;
        }
        
        try {
            Persistence persistence = PersistenceMetadata.getDefault().getRoot(persistenceScope.getPersistenceXml());
            if(persistence != null){
                PersistenceUnit[] pus=persistence.getPersistenceUnit();
                PersistenceUnit ret=pus.length>0 ? pus[0] : null;//if there is only one pu, return in any case (even if do not contain fqn)
                if(pus.length>1) {//searchh for best match
                    PersistenceUnit forAll=null;
                    PersistenceUnit forOne=null;
                    for(int i=0;i<pus.length && forOne==null;i++) {
                        PersistenceUnit tmp=pus[i];
                        if(forAll ==null && !tmp.isExcludeUnlistedClasses()) forAll=tmp;//first match sutable for all entities in the project
                        if(tmp.isExcludeUnlistedClasses()) {
                            String []classes = tmp.getClass2();
                            for(String clas:classes){
                                if(fqn.equals(clas)) {
                                    forOne = tmp;
                                    break;
                                }
                            }
                        }
                    }
                    ret = forOne != null ? forOne : (forAll != null ? forAll : ret);
                }
                return ret;
            }
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
        return null;
    }
    
}
