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

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.bpm.ProcessInstance;
import org.jboss.seam.bpm.TaskInstance;
import org.jboss.seam.core.Events;
import org.jboss.seam.core.Init;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.jbpm.context.exe.ContextInstance;

/**
 * Exposes a jbpm variable context instance for reading/writing.
 *
 * @author <a href="mailto:theute@jboss.org">Thomas Heute</a>
 * @author <a href="mailto:steve@hibernate.org">Steve Ebersole</a>
 * @author Gavin King
 */
public class BusinessProcessContext implements Context 
{

   private static final LogProvider log = Logging.getLogProvider(BusinessProcessContext.class);

   private final Map<String, Object> additions = new HashMap<String, Object>();
   private final Set<String> removals = new HashSet<String>();

   public ScopeType getType()
   {
      return ScopeType.BUSINESS_PROCESS;
   }

   public BusinessProcessContext() {}
   
   public Object get(String name) 
   {
      
      Object result = additions.get(name);
      if (result!=null) return result;
      if ( removals.contains(name) ) return null;
      
      org.jbpm.taskmgmt.exe.TaskInstance taskInstance = getTaskInstance();
      if (taskInstance==null)
      {
         ContextInstance context = getContextInstance();
         return context==null ? null : context.getVariable(name);
      }
      else
      {
         return taskInstance.getVariable(name);
      }
      
   }

   public void set(String name, Object value) 
   {
      if ( Events.exists() ) Events.instance().raiseEvent("org.jboss.seam.preSetVariable." + name);
      if (value==null)
      {
         //yes, we need this
         remove(name);
      }
      else
      {
         removals.remove(name);
         additions.put(name, value);
      }
      if ( Events.exists() ) Events.instance().raiseEvent("org.jboss.seam.postSetVariable." + name);
   }

   public boolean isSet(String name) 
   {
      return get(name)!=null;
   }
   
   public void remove(String name) 
   {
      if ( Events.exists() ) Events.instance().raiseEvent("org.jboss.seam.preRemoveVariable." + name);
      additions.remove(name);
      removals.add(name);
      if ( Events.exists() ) Events.instance().raiseEvent("org.jboss.seam.postRemoveVariable." + name);
   }

   public String[] getNames() 
   {
      Set<String> results = getNamesFromContext();
      results.removeAll(removals);
      results.addAll( additions.keySet() ); //after, to override
      return results.toArray(new String[]{});
   }

   private Set<String> getNamesFromContext() 
   {
       //TODO: note that this is called from Contexts.destroy(), 
       //      after the Seam-managed txn was committed, but 
       //      this implementation requires a hit to the database!
       HashSet<String> results = new HashSet<String>();
       org.jbpm.taskmgmt.exe.TaskInstance taskInstance = getTaskInstance();
       if (taskInstance==null) {
           ContextInstance context = getContextInstance();
           if (context!=null) {
               Map variables = context.getVariables();
               if (variables != null) {
                   results.addAll(variables.keySet());
               }
           }
       } else {
           results.addAll( taskInstance.getVariables().keySet() );
       }
       return results;
   }

   public Object get(Class clazz)
   {
      return get( Component.getComponentName(clazz) );
   }
   
   public void clear()
   {
      additions.clear();
      removals.addAll( getNamesFromContext() );
   }

   /**
    * Propagate all additions and removals to the jBPM database if
    * there is a current process instance, or do nothing if there
    * is no current process instance.
    */
   public void flush()
   {
      if ( !additions.isEmpty() || !removals.isEmpty() )
      {
         
         org.jbpm.taskmgmt.exe.TaskInstance taskInstance = getTaskInstance();
         if (taskInstance==null)
         {
            org.jbpm.graph.exe.ProcessInstance processInstance = getProcessInstance();
            if ( processInstance==null )
            {
               log.debug( "no process instance to persist business process state" );
               return; //don't clear the additions and removals
            }
            else 
            {
               flushToProcessInstance(processInstance);
            }
         }
         else
         {
            flushToTaskInstance(taskInstance);
         }
         
         additions.clear();
         removals.clear();
         
      }
      
   }

   private void flushToTaskInstance(org.jbpm.taskmgmt.exe.TaskInstance taskInstance)
   {
      log.debug( "flushing to task instance: " + taskInstance.getId() );
      
      for ( Map.Entry<String, Object> entry: additions.entrySet() )
      {
         taskInstance.setVariableLocally( entry.getKey(), entry.getValue() );
      }

      for ( String name: removals )
      {
         taskInstance.deleteVariableLocally(name);
      }
   }

   private void flushToProcessInstance(org.jbpm.graph.exe.ProcessInstance processInstance)
   {
      log.debug( "flushing to process instance: " + processInstance.getId() );
  
      ContextInstance contextInstance = processInstance.getContextInstance();
  
      for ( Map.Entry<String, Object> entry: additions.entrySet() )
      {
         contextInstance.setVariable( entry.getKey(), entry.getValue() );
      }
  
      for ( String name: removals )
      {
         contextInstance.deleteVariable(name);
      }
   }

   private ContextInstance getContextInstance()
   {
      org.jbpm.graph.exe.ProcessInstance processInstance = getProcessInstance();
      return processInstance==null ? null : processInstance.getContextInstance(); 
   }

   private org.jbpm.graph.exe.ProcessInstance getProcessInstance()
   {
      Init init = Init.instance(); //may be null in some tests
      if ( init==null || !init.isJbpmInstalled() ) 
      {
         return null;
      }
      else
      {
         return ProcessInstance.instance();
      }
   }
   
   private org.jbpm.taskmgmt.exe.TaskInstance getTaskInstance()
   {
      Init init = Init.instance(); //may be null in some tests
      if ( init==null || !init.isJbpmInstalled() ) 
      {
         return null;
      }
      else
      {
         return TaskInstance.instance();
      }
   }
   
}
