/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache;

import org.jboss.cache.config.Configuration;
import org.jboss.cache.factories.CommandsFactory;
import org.jboss.cache.factories.ComponentFactory;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.factories.annotations.Start;
import org.jboss.cache.interceptors.InterceptorChain;
import org.jboss.cache.invocation.InvocationContextContainer;
import org.jboss.cache.invocation.NodeInvocationDelegate;
import org.jboss.cache.lock.LockStrategyFactory;
import org.jboss.cache.optimistic.TransactionWorkspace;
import org.jboss.cache.optimistic.WorkspaceNode;
import org.jboss.cache.optimistic.WorkspaceNodeImpl;

import java.util.Map;

/**
 * Generates new nodes based on the {@link CacheSPI} configuration.
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
public class NodeFactory<K, V> extends ComponentFactory
{
   private CacheSPI<K, V> cache;
   private boolean optimistic;
   private Configuration configuration;
   private InvocationContextContainer invocationContextContainer;
   private InterceptorChain interceptorChain;
   private CommandsFactory commandsFactory;
   private LockStrategyFactory lockStrategyFactory;

   @Override
   protected <T> T construct(Class<T> componentType)
   {
      throw new UnsupportedOperationException("Should never be called!");
   }

   public static enum NodeType
   {
      UNVERSIONED_NODE, VERSIONED_NODE, WORKSPACE_NODE
   }

   /**
    * Constructs an instance of the factory
    */
   public NodeFactory(CacheSPI<K, V> cache)
   {
      this.cache = cache;
      init();
   }

   public NodeFactory()
   {
   }

   @Inject
   private void injectDependencies(CacheSPI<K, V> cache, Configuration configuration,
                                   InvocationContextContainer invocationContextContainer,
                                   InterceptorChain interceptorChain, CommandsFactory commandsFactory, LockStrategyFactory lockStrategyFactory)
   {
      this.cache = cache;
      this.configuration = configuration;
      this.invocationContextContainer = invocationContextContainer;
      this.interceptorChain = interceptorChain;
      this.commandsFactory = commandsFactory;
      this.lockStrategyFactory = lockStrategyFactory;
   }

   /**
    * Initialises the node factory with the configuration from the cache.
    */
   @Start
   public void init()
   {
      optimistic = configuration.isNodeLockingOptimistic();
   }


   /**
    * Creates a new {@link Node} instance.
    *
    * @param childName the new node's name
    * @param fqn       the new node's Fqn
    * @param parent    the new node's parent
    * @param data      the new node's attribute map
    * @param mapSafe   <code>true</code> if param <code>data</code> can safely
    *                  be directly assigned to the new node's data field;
    *                  <code>false</code> if param <code>data</code>'s contents
    *                  should be copied into the new node's data field.
    * @return the new node
    */
   public NodeSPI<K, V> createDataNode(Object childName, Fqn fqn, NodeSPI<K, V> parent, Map<K, V> data, boolean mapSafe)
   {
      UnversionedNode un = optimistic ? new VersionedNode<K, V>(fqn, parent, data, cache) : new UnversionedNode<K, V>(childName, fqn, data, mapSafe, cache);
      // always assume that new nodes do not have data loaded
      un.setDataLoaded(false);
      NodeInvocationDelegate<K, V> nid = new NodeInvocationDelegate(un);

      // Too slow to have these autowired for now.  Look at manually wiring them.
      nid.initialize(configuration, invocationContextContainer, componentRegistry, interceptorChain);
      nid.injectDependencies(cache);
      un.injectDependencies(cache, commandsFactory, lockStrategyFactory);

//      componentRegistry.wireDependencies(nid);
//      componentRegistry.wireDependencies(un);
      // back ref
      un.setDelegate(nid);
      return nid;
   }

   public Node<K, V> createNode(Object childName, Node<K, V> parent, Map<K, V> data)
   {
      return createNodeOfType(parent, childName, parent, data);
   }

   public Node<K, V> createNodeOfType(Node<K, V> template, Object childName, Node<K, V> parent, Map<K, V> data)
   {
      // not a workspace node.
      return createDataNode(childName, Fqn.fromRelativeElements(parent.getFqn(), childName), (NodeSPI<K, V>) parent, data, false);
   }

   public WorkspaceNode<K, V> createWorkspaceNode(NodeSPI<K, V> dataNode, TransactionWorkspace workspace)
   {
      return new WorkspaceNodeImpl<K, V>(dataNode, workspace);
   }

   public NodeSPI<K, V> createRootDataNode()
   {
      return createDataNode(null, Fqn.ROOT, null, null, false);
   }

}
