package org.jboss.seam.navigation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;

import org.jboss.seam.core.Events;
import org.jboss.seam.core.Interpolator;
import org.jboss.seam.core.ResourceLoader;
import org.jboss.seam.security.Identity;

/**
 * Metadata about page actions, page parameters, action navigation,
 * resource bundle, etc, for a particular JSF view id.
 */
public final class Page
{
   private final String viewId;
   private String description;
   private Integer timeout;
   private String noConversationViewId;
   private String resourceBundleName;
   private boolean switchEnabled = true;
   private List<Param> parameters = new ArrayList<Param>();
   private List<Input> inputs = new ArrayList<Input>();
   private List<Action> actions = new ArrayList<Action>();
   private Map<String, Navigation> navigations = new HashMap<String, Navigation>();
   private Integer concurrentRequestTimeout;
   private Navigation defaultNavigation;
   private boolean conversationRequired;
   private boolean loginRequired;
   private ConversationControl conversationControl = new ConversationControl();
   private TaskControl taskControl = new TaskControl();
   private ProcessControl processControl = new ProcessControl();
   private ConversationIdParameter conversationIdParameter;
   private String eventType;
   
   /**
    * The scheme (http/https) required by this page.
    */
   private String scheme;
   
   /**
    * Indicates whether this view id has a security restriction.  
    */
   private boolean restricted;
   
   /**
    * A security restriction expression to evaluate when requesting this view id.
    * If the view is restricted but no restriction expression is set, the implied
    * permission restriction will be name="[viewid]", action="[get or post]" 
    */
   private String restriction;
   
   public Page(String viewId)
   {
      this.viewId = viewId;
      if (viewId!=null)
      {
         int loc = viewId.lastIndexOf('.');
         if ( loc>0 && viewId.startsWith("/") )
         {
            this.setResourceBundleName( viewId.substring(1, loc) );
         }
      }
      
      conversationIdParameter = new SyntheticConversationIdParameter();
   }
   
   public java.util.ResourceBundle getResourceBundle()
   {
      String resourceBundleName = getResourceBundleName();
      if (resourceBundleName==null)
      {
         return null;
      }
      else
      {
          return ResourceLoader.instance().loadBundle(resourceBundleName);
      }
   }
   
   @Override
   public String toString()
   {
      return "Page(" + getViewId() + ")";
   }
   
   public String getViewId()
   {
      return viewId;
   }
   
   public String renderDescription()
   {
      return Interpolator.instance().interpolate( getDescription() );
   }
   
   public void setDescription(String description)
   {
      this.description = description;
   }
   
   public String getDescription()
   {
      return description;
   }
   
   public void setTimeout(Integer timeout)
   {
      this.timeout = timeout;
   }
   
   public Integer getTimeout()
   {
      return timeout;
   }
   
   public void setConcurrentRequestTimeout(Integer concurrentRequestTimeout)
   {
      this.concurrentRequestTimeout = concurrentRequestTimeout;
   }
   
   public Integer getConcurrentRequestTimeout()
   {
      return concurrentRequestTimeout;
   }
   
   public void setNoConversationViewId(String noConversationViewId)
   {
      this.noConversationViewId = noConversationViewId;
   }
   
   public String getNoConversationViewId()
   {
      return noConversationViewId;
   }
   
   public void setResourceBundleName(String resourceBundleName)
   {
      this.resourceBundleName = resourceBundleName;
   }
   
   public String getResourceBundleName()
   {
      return resourceBundleName;
   }
   
   public void setSwitchEnabled(boolean switchEnabled)
   {
      this.switchEnabled = switchEnabled;
   }
   
   public boolean isSwitchEnabled()
   {
      return switchEnabled;
   }
   
   public List<Param> getParameters()
   {
      return parameters;
   }
   
   public Map<String, Navigation> getNavigations()
   {
      return navigations;
   }
   
   public boolean hasDescription()
   {
      return description!=null;
   }
   
   public boolean isConversationRequired()
   {
      return conversationRequired;
   }
   
   public void setConversationRequired(boolean conversationRequired)
   {
      this.conversationRequired = conversationRequired;
   }
   
   public Navigation getDefaultNavigation()
   {
      return defaultNavigation;
   }
   
   public void setDefaultNavigation(Navigation defaultActionOutcomeMapping)
   {
      this.defaultNavigation = defaultActionOutcomeMapping;
   }
   
   public ConversationControl getConversationControl()
   {
      return conversationControl;
   }
   
   public TaskControl getTaskControl()
   {
      return taskControl;
   }
   
   public ProcessControl getProcessControl()
   {
      return processControl;
   }
   
   public List<Action> getActions()
   {
      return actions;
   }
   
   private void checkPermission(FacesContext facesContext, String name)
   {
      if ( isRestricted() && Identity.isSecurityEnabled() )
      {
         // If no expression is configured, create a default one
         if (restriction == null)
         {
            Identity.instance().checkPermission( Pages.getViewId(facesContext), name );
         }
         else
         {
            Identity.instance().checkRestriction(restriction);
         }
      }
   }
   
   /**
    * Check the restore permission.
    */
   public void postRestore(FacesContext facesContext)
   {
      checkPermission(facesContext, "restore");
   }

   /**
    * Call page actions, in order they appear in XML, and
    * handle conversation begin/end. Also check the 
    * render permission.
    */
   public boolean preRender(FacesContext facesContext)
   {
      checkPermission(facesContext, "render");     
      
      boolean result = false;
      
      getConversationControl().beginOrEndConversation();
      getTaskControl().beginOrEndTask();
      getProcessControl().createOrResumeProcess();
      
      for ( Input in: getInputs() ) in.in();
      
      if (eventType!=null)
      {
         Events.instance().raiseEvent(eventType);
      }
   
      for ( Action action: getActions() )
      {
         if ( action.isExecutable() )
         {
            String outcome = action.getOutcome();
            String fromAction = outcome;
            
            if (outcome==null)
            {
               fromAction = action.getMethodExpression().getExpressionString();
               result = true;
               outcome = Pages.toString( action.getMethodExpression().invoke() );
               UIViewRoot oldViewRoot = facesContext.getViewRoot();
               Pages.handleOutcome(facesContext, outcome, fromAction);
               if (facesContext.getResponseComplete() || oldViewRoot != facesContext.getViewRoot()) {
                  break;
               }
            }
            else
            {
               UIViewRoot oldViewRoot = facesContext.getViewRoot();
               Pages.handleOutcome(facesContext, outcome, fromAction);
               if (facesContext.getResponseComplete() || oldViewRoot != facesContext.getViewRoot()) {
                  break;
               }
            }
         }
      }
      
      return result;
   }
   
   public List<Input> getInputs()
   {
      return inputs;
   }
   
   public boolean isRestricted()
   {
      return restricted;
   }
   
   public void setRestricted(boolean restricted)
   {
      this.restricted = restricted;
   }
   public String getRestriction()
   {
      return restriction;
   }
   
   public void setRestriction(String restriction)
   {
      this.restriction = restriction;
   }

   public boolean isLoginRequired()
   {
      return loginRequired;
   }

   public void setLoginRequired(boolean loginRequired)
   {
      this.loginRequired = loginRequired;
   }
   
   public String getScheme()
   {
      return scheme;
   }
   
   public void setScheme(String scheme)
   {
      this.scheme = scheme;
   }
   
   public ConversationIdParameter getConversationIdParameter()
   {
      return conversationIdParameter;
   }
   
   public void setConversationIdParameter(ConversationIdParameter param)
   {
      this.conversationIdParameter = param;
   }

   public String getEventType()
   {
      return eventType;
   }

   public void setEventType(String eventType)
   {
      this.eventType = eventType;
   }
      
}
