/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.beans.metadata.api.model;

import java.io.Serializable;
import java.io.ObjectStreamException;
import java.util.Collections;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;

import org.jboss.beans.info.spi.BeanInfo;
import org.jboss.beans.info.spi.helpers.UnmodifiableBeanInfo;
import org.jboss.dependency.spi.ControllerContext;
import org.jboss.dependency.spi.ControllerState;
import org.jboss.dependency.spi.helpers.UnmodifiableControllerContext;
import org.jboss.kernel.spi.dependency.KernelControllerContext;
import org.jboss.kernel.spi.dependency.helpers.UnmodifiableKernelControllerContext;
import org.jboss.metadata.spi.MetaData;
import org.jboss.metadata.spi.scope.ScopeKey;
import org.jboss.util.JBossObject;
import org.jboss.util.JBossStringBuilder;

/**
 * Inject from controller context:
 *  * name - controller context name
 *  * aliases - aliases
 *  * metadata - inject MetaData
 *  * beaninfo - BeanInfo
 *  * scope - ScopeKey
 *  * id - identifier
 *  * dynamic - method specific
 *  * ...
 *
 * @author <a href="mailto:ales.justin@jboss.com">Ales Justin</a>
 */
abstract class FromContextDelegate extends JBossObject implements Serializable
{
   private static final long serialVersionUID = 1L;

   /** name */
   static final FromContextDelegate NOOP = new NoopFromContext("noop");

   /** name */
   static final FromContextDelegate NAME = new NameFromContext(MicrocontainerConstants.NAME);

   /** alias */
   static final FromContextDelegate ALIASES = new AliasesFromContext(MicrocontainerConstants.ALIASES);

   /** metadata */
   static final FromContextDelegate METADATA = new MetaDataFromContext(MicrocontainerConstants.METADATA);

   /** beaninfo */
   static final FromContextDelegate BEANINFO = new BeanInfoFromContext(MicrocontainerConstants.BEANINFO);

   /** scope */
   static final FromContextDelegate SCOPE = new ScopeFromContext(MicrocontainerConstants.SCOPE);

   /** state */
   static final FromContextDelegate STATE = new StateFromContext(MicrocontainerConstants.STATE);

   /** id */
   static final FromContextDelegate ID = new IdFromContext(MicrocontainerConstants.ID);

   /** context */
   static final FromContextDelegate CONTEXT = new ThisContext(MicrocontainerConstants.CONTEXT);

   /** The type string */
   protected final String fromString;

   /** The when valid state */
   protected ControllerState whenValid;

   /** The values */
   private static Map<String, FromContextDelegate> values = new HashMap<String, FromContextDelegate>();

   static
   {
      values.put(NOOP.getFromString(), NOOP);
      values.put(NAME.getFromString(), NAME);
      values.put(ALIASES.getFromString(), ALIASES);
      values.put(METADATA.getFromString(), METADATA);
      values.put(BEANINFO.getFromString(), BEANINFO);
      values.put(SCOPE.getFromString(), SCOPE);
      values.put(STATE.getFromString(), STATE);
      values.put(ID.getFromString(), ID);
      values.put(CONTEXT.getFromString(), CONTEXT);
   }

   /**
    * Create a new state
    *
    * @param fromString the string representation
    */
   protected FromContextDelegate(String fromString)
   {
      this(fromString, null);
   }

   /**
    * Create new state
    *
    * @param fromString the string representation
    * @param whenValid the when valid state
    */
   protected FromContextDelegate(String fromString, ControllerState whenValid)
   {
      if (fromString == null)
         throw new IllegalArgumentException("Null from string");
      this.fromString = fromString;
      if (whenValid == null)
         whenValid = ControllerState.PRE_INSTALL;
      this.whenValid = whenValid;
   }

   /**
    * Get when valid state.
    *
    * @return the when required state
    */
   public ControllerState getWhenValid()
   {
      return whenValid;
   }

   /**
    * Validate context before execution.
    * After validation we must be able to cast context to T instance.
    *
    * @param context the context
    */
   protected void validate(ControllerContext context)
   {
   }

   /**
    * Execute injection on context.
    *
    * @param context the target context
    * @return lookup value
    * @throws Throwable for any error
    */
   public Object executeLookup(ControllerContext context) throws Throwable
   {
      validate(context);
      return internalExecute(context);
   }

   /**
    * Execute internal lookup on context.
    *
    * @param context the target context
    * @return lookup value
    * @throws Throwable for any error
    */
   public abstract Object internalExecute(ControllerContext context) throws Throwable;

   /**
    * Get the from string
    *
    * @return the state string
    */
   public String getFromString()
   {
      return fromString;
   }

   public boolean equals(Object object)
   {
      if (object == null || object instanceof FromContextDelegate == false)
         return false;
      FromContextDelegate other = (FromContextDelegate) object;
      return fromString.equals(other.getFromString());
   }

   public void toString(JBossStringBuilder buffer)
   {
      buffer.append(fromString);
   }

   protected int getHashCode()
   {
      return fromString.hashCode();
   }

   protected Object readResolve() throws ObjectStreamException
   {
      return values.get(fromString);
   }

   private static abstract class KernelFromContextDelegate extends FromContextDelegate
   {
      private static final long serialVersionUID = 1L;

      protected KernelFromContextDelegate(String fromString)
      {
         super(fromString);
      }

      protected KernelFromContextDelegate(String fromString, ControllerState whenRequired)
      {
         super(fromString, whenRequired);
      }

      protected void validate(ControllerContext context)
      {
         if (context instanceof KernelControllerContext == false)
            throw new UnsupportedOperationException("Cannot execute " + getFromString() + " on underlying context: " + context);
      }
   }

   private static class NoopFromContext extends FromContextDelegate
   {
      private static final long serialVersionUID = 1L;

      public NoopFromContext(String fromString)
      {
         super(fromString);
      }

      public Object internalExecute(ControllerContext context)
      {
         throw new UnsupportedOperationException("Noop from context.");
      }
   }

   private static class NameFromContext extends FromContextDelegate
   {
      private static final long serialVersionUID = 1L;

      public NameFromContext(String fromString)
      {
         super(fromString);
      }

      public Object internalExecute(ControllerContext context)
      {
         return context.getName();
      }
   }

   private static class AliasesFromContext extends FromContextDelegate
   {
      private static final long serialVersionUID = 1L;

      public AliasesFromContext(String fromString)
      {
         super(fromString);
      }

      public Set<Object> internalExecute(ControllerContext context)
      {
         Set<Object> aliases = context.getAliases();
         return aliases != null ? Collections.unmodifiableSet(aliases) : null;
      }
   }

   private static class MetaDataFromContext extends FromContextDelegate
   {
      private static final long serialVersionUID = 1L;

      public MetaDataFromContext(String fromString)
      {
         super(fromString);
      }

      public MetaData internalExecute(ControllerContext context)
      {
         return context.getScopeInfo().getMetaData();
      }
   }

   private static class BeanInfoFromContext extends KernelFromContextDelegate
   {
      private static final long serialVersionUID = 1L;

      public BeanInfoFromContext(String fromString)
      {
         super(fromString, ControllerState.INSTANTIATED);
      }

      public BeanInfo internalExecute(ControllerContext context)
      {
         BeanInfo info = ((KernelControllerContext)context).getBeanInfo();
         return info != null ? new UnmodifiableBeanInfo(info) : null;
      }
   }

   private static class ScopeFromContext extends FromContextDelegate
   {
      private static final long serialVersionUID = 1L;

      public ScopeFromContext(String fromString)
      {
         super(fromString);
      }

      public ScopeKey internalExecute(ControllerContext context)
      {
         return context.getScopeInfo().getScope();
      }
   }

   private static class StateFromContext extends FromContextDelegate
   {
      private static final long serialVersionUID = 1L;

      public StateFromContext(String fromString)
      {
         super(fromString, ControllerState.NOT_INSTALLED);
      }

      public ControllerState internalExecute(ControllerContext context)
      {
         return context.getState();
      }
   }

   private static class IdFromContext extends FromContextDelegate
   {
      private static final long serialVersionUID = 1L;

      public IdFromContext(String fromString)
      {
         super(fromString);
      }

      public Object internalExecute(ControllerContext context)
      {
         // todo - change to actual id when impl
         return context.getName();
      }
   }

   private static class ThisContext extends FromContextDelegate
   {
      private static final long serialVersionUID = 1L;

      public ThisContext(String fromString)
      {
         super(fromString);
      }

      public ControllerContext internalExecute(ControllerContext context)
      {
         if (context instanceof KernelControllerContext)
            return new UnmodifiableKernelControllerContext((KernelControllerContext)context);
         return new UnmodifiableControllerContext(context);
      }
   }
}
