package org.jboss.resteasy.cdi.injection.reverse;

import java.util.HashMap;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ejb.Stateful;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;

import org.jboss.resteasy.cdi.injection.Book;
import org.jboss.resteasy.cdi.injection.BookResource;
import org.jboss.resteasy.cdi.util.Utilities;


/**
 *
 * @author <a href="ron.sigal@jboss.com">Ron Sigal</a>
 * @version $Revision: 1.1 $
 *
 * Copyright May 30, 2012
 */
@Stateful
@RequestScoped
public class EJBHolder implements EJBHolderRemote, EJBHolderLocal
{
   public static final String SLE  = "sle";
   public static final String SFDE = "sfde";  
   public static final String SFRE = "sfre";
   public static final String SFAE = "sfae";
   public static final String SLI  = "sli";
   public static final String SFDI = "sfdi";
   public static final String SFRI = "sfri";
   public static final String SFAI = "sfai";
   public static final String SLI_SECRET  = "sliSecret";
   public static final String SFDI_SECRET = "sfdiSecret";
   public static final String SFRI_SECRET = "sfriSecret";
   public static final String SFAI_SECRET = "sfaiSecret";
   
   private static HashMap<String,Object> store = new HashMap<String,Object>();
   
   @Inject private Logger log;
   @Inject private Utilities utilities;
   @Inject int secret;
   
   @EJB private StatelessEJBwithJaxRsComponentsInterface                    sle;
   @EJB private StatefulDependentScopedEJBwithJaxRsComponentsInterface      sfde;
   @EJB private StatefulRequestScopedEJBwithJaxRsComponentsInterface        sfre;
   @EJB private StatefulApplicationScopedEJBwithJaxRsComponentsInterface    sfae;
   
   @Inject private StatelessEJBwithJaxRsComponentsInterface                 sli;
   @Inject private StatefulDependentScopedEJBwithJaxRsComponentsInterface   sfdi;
   @Inject private StatefulRequestScopedEJBwithJaxRsComponentsInterface     sfri;
   @Inject private StatefulApplicationScopedEJBwithJaxRsComponentsInterface sfai;
   
   @Inject private BookResource resource;
   
   @PostConstruct
   public void postConstruct()
   {
      log.info(this + " secret: " + secret);
   }
   
   @Override
   public boolean testScopes()
   {
      log.info("");
      log.info("entering EJBHolder.testScopes()");
      log.info("resource scope:                                                 " + utilities.getScope(BookResource.class));
      log.info("EJBHolder scope:                                                " + utilities.getScope(EJBHolder.class));
      log.info("EJBHolderLocal scope:                                           " + utilities.getScope(EJBHolderLocal.class));
      log.info("EJBHolderRemote scope:                                          " + utilities.getScope(EJBHolderRemote.class));
      log.info("StatelessEJBwithJaxRsComponents scope:                          " + utilities.getScope(StatelessEJBwithJaxRsComponents.class));
      log.info("StatelessEJBwithJaxRsComponentsInterface scope:                 " + utilities.getScope(StatelessEJBwithJaxRsComponentsInterface.class));
      log.info("StatefulDependentScopedEJBwithJaxRsComponents scope:            " + utilities.getScope(StatefulDependentScopedEJBwithJaxRsComponents.class));
      log.info("StatefulDependentScopedEJBwithJaxRsComponentsInterface scope:   " + utilities.getScope(StatefulDependentScopedEJBwithJaxRsComponentsInterface.class));
      log.info("StatefulRequestScopedEJBwithJaxRsComponents scope:              " + utilities.getScope(StatefulRequestScopedEJBwithJaxRsComponents.class));
      log.info("StatefulRequestScopedEJBwithJaxRsComponentsInterface scope:     " + utilities.getScope(StatefulRequestScopedEJBwithJaxRsComponentsInterface.class));
      log.info("StatefulApplicationScopedEJBwithJaxRsComponents scope:          " + utilities.getScope(StatefulApplicationScopedEJBwithJaxRsComponents.class));
      log.info("StatefulApplicationScopedEJBwithJaxRsComponentsInterface scope: " + utilities.getScope(StatefulApplicationScopedEJBwithJaxRsComponentsInterface.class));

      return utilities.isDependentScoped(StatelessEJBwithJaxRsComponentsInterface.class) &&
             utilities.isDependentScoped(StatefulDependentScopedEJBwithJaxRsComponentsInterface.class) &&
             utilities.isRequestScoped(StatefulRequestScopedEJBwithJaxRsComponentsInterface.class) &&
             utilities.isApplicationScoped(StatefulApplicationScopedEJBwithJaxRsComponentsInterface.class);
   }
   
   @Override
   public void setup()
   {
      log.info("");
      log.info("entering EJBHolder.setup()");
      resource.getSet().add(new Book("Disappearing Book"));
      store.put("sle", sle);
      store.put("sfde", sfde);
      store.put("sfre", sfre);
      store.put("sfae", sfae);
      store.put("sli", sli);
      store.put("sfdi", sfdi);
      store.put("sfri", sfri);
      store.put("sfai", sfai);
      store.put("sli.secret", sli.theSecret());
      store.put("sfdi.secret", sfdi.theSecret());
      store.put("sfri.secret", sfri.theSecret());
      store.put("sfai.secret", sfai.theSecret());
      
      sleSetup();
      sfdeSetup();
      sfreSetup();
      sfaeSetup();
      
      sliSetup();
      sfdiSetup();
      sfriSetup();
      sfaiSetup();
   }
   
   @Override
   public boolean test()
   {
      log.info("");
      log.info("entering EJBHolder.test()");
      
      boolean result = true;
      result &= resource.getSet().isEmpty();
      
      // @TODO inject singleton
      // @TODO inject by setter method
      result &=  store.get("sle").equals(sle);    // EJB spec 3.4.7.1
      result &= !store.get("sfde").equals(sfde);  // EJB spec 3.4.7.2, 16.2.1
      result &= !store.get("sfre").equals(sfre);  // EJB spec 3.4.7.2, 16.2.1
      result &= !store.get("sfae").equals(sfae);  // EJB spec 3.4.7.2, 16.2.1
      //
      result &= !(store.get("sle") == sle);       // EJB spec 16.2.1
      result &= !(store.get("sfde") == sfde);     // EJB spec 16.2.1
      result &= !(store.get("sfre") == sfre);     // EJB spec 16.2.1
      result &= !(store.get("sfae") == sfae);     // EJB spec 16.2.1
      
      // Unlike the EJB spec, the CDI spec does not explicitly specify the semantics of the equality or inequality
      // of injected objects.  In fact, it explicitly forbids calling the java.lang.Object.equals() function on injected
      // objects [CDI spec, section 5.4.2].  It does specify that the first reference to an injectible object in a given
      // context should result in a new contextual reference or a new contextual instance [CDI spec, section 6.5.3],
      // but it doesn't seem to specify the precise semantics of "new".  In the weld reference implementation,
      // there seem to be three variations, at least with respect to injected EJBs:
      //
      // 1. It could be a new proxy for an existing object, or
      // 2. a new proxy for a new object, or
      // 3. a reused proxy for a new object.
      //
      // In this test, we find that
      //
      // 1. An SLSB (which necessarily has dependent pseudo-scope) is treated according to case 1.
      // 2. An SFSB with dependent scope is treated according to case 2.
      // 3. An SFSB with request scope is treated according to case 3.
      //
      // This behavior seems to be consistent with the semantics of EJBs:
      //
      // 1. All instances of a given SLSB class are considered equal, and SLSB target objects are reused (case 1).
      // 2. All instances of a given SFSB class are considered unequal, and SFSBs are always recreated (cases 2 and 3).
      //
      // For this test, we consider inequality, in cases where we expect a new object in a new scope, to mean
      //
      // 1. a new proxy for SLSBs, and
      // 2. a new target object for SFSBs.
      //
      // N.B.  If an SLSB is reused, and it is a contextual object (i.e., created by CDI), then, though some fields might
      //       remain the same, all fields annotated with @Inject should be processed accordingly.
      //
      log.info("sli:  == stored sli:  " + (sli == store.get("sli")));
      log.info("sfdi: == stored sfdi: " + (sfdi == store.get("sfdi")));
      log.info("sfri: == stored sfri: " + (sfri == store.get("sfri")));
      log.info("sfai: == stored sfai: " + (sfai == store.get("sfai")));
      
      log.info("sli.secret:  == stored sli.secret:  " + (sli.theSecret()  == Integer.class.cast(store.get("sli.secret"))));
      log.info("sfdi.secret: == stored sfdi.secret: " + (sfdi.theSecret() == Integer.class.cast(store.get("sfdi.secret"))));
      log.info("sfri.secret: == stored sfri.secret: " + (sfri.theSecret() == Integer.class.cast(store.get("sfri.secret"))));
      log.info("sfai.secret: == stored sfai.secret: " + (sfai.theSecret() == Integer.class.cast(store.get("sfai.secret"))));
      
      result &= (sli != store.get("sli"));
      result &= (sfdi.theSecret() != Integer.class.cast(store.get("sfdi.secret")));
      result &= (sfri.theSecret() != Integer.class.cast(store.get("sfri.secret")));
      
      // The CDI spec requires that a single application scoped object of a given class should exist for the
      // lifetime of the application.  It seems reasonable to expect 
      //
      // 1. that a proxy for that object obtained by @Inject should be reused throughout the lifetime of the application, and
      // 2. that the target of that proxy should remain the same throughout the lifetime of the application.
      result &= (sfai == store.get("sfai") && sfai.theSecret() == Integer.class.cast(store.get("sfai.secret")));
      
      result &= sleTest();
      result &= sfdeTest();
      result &= sfreTest();
      result &= sfaeTest();
      
      result &= sliTest();
      result &= sfdiTest();
      result &= sfriTest();
      result &= sfaiTest();
      
      return result;
   }
   
   @Override
   public void sleSetup()
   {
      sle.setUp(SLE);
   }
   
   @Override
   public boolean sleTest()
   {
      return sle.test(SLE);
   }
   
   @Override
   public void sfdeSetup()
   {
      sfde.setUp(SFDE);
   }
   
   @Override
   public boolean sfdeTest()
   {
      return sfde.test(SFDE);
   }
   
   @Override
   public void sfreSetup()
   {
      sfre.setUp(SFRE);
   }
   
   @Override
   public boolean sfreTest()
   {
      return sfre.test(SFRE);
   }
   
   @Override
   public void sfaeSetup()
   {
      sfae.setUp(SFAE);
   }
   
   @Override
   public boolean sfaeTest()
   {
      return sfae.test(SFAE);
   }   
   
   @Override
   public void sliSetup()
   {
      sli.setUp(SLI);
   }
   
   @Override
   public boolean sliTest()
   {
      return sli.test(SLI);
   }
   
   @Override
   public void sfdiSetup()
   {
      sfdi.setUp(SFDI);
   }
   
   @Override
   public boolean sfdiTest()
   {
      return sfdi.test(SFDI);
   }
   
   @Override
   public void sfriSetup()
   {
      sfri.setUp(SFRI);
   }
   
   @Override
   public boolean sfriTest()
   {
      return sfri.test(SFRI);
   }

   @Override
   public void sfaiSetup()
   {
      sfai.setUp(SFAI);
   }
   
   @Override
   public boolean sfaiTest()
   {
      return sfai.test(SFAI);
   }
   
   @Override
   public boolean theSame(EJBHolderLocal that)
   {
      log.info("this secret: " + secret);
      log.info("that secret: " + that.theSecret());
      return this.secret == that.theSecret();
   }
   
   @Override
   public int theSecret()
   {
      return secret;
   }
}
