/* $Id: FormDataAccumulator.java 1416199 2012-12-02 16:57:56Z kwright $ */

/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.manifoldcf.crawler.connectors.webcrawler;

import org.apache.manifoldcf.core.interfaces.*;
import java.util.*;
import java.util.regex.*;

/** This class accumulates form data and allows overrides */
public class FormDataAccumulator implements FormData
{
  // Note well: We don't handle multipart posts at this time!!

  // Element categorization
  protected final static int ELEMENTCATEGORY_FREEFORM = 0;
  protected final static int ELEMENTCATEGORY_FIXEDEXCLUSIVE = 1;
  protected final static int ELEMENTCATEGORY_FIXEDINCLUSIVE = 2;

  /** The form's action URI */
  protected String actionURI;
  /** The form's submit method */
  protected int submitMethod;

  /** The set of elements */
  protected ArrayList elementList = new ArrayList();

  public FormDataAccumulator(String actionURI, int submitMethod)
  {
    this.actionURI = actionURI;
    this.submitMethod = submitMethod;
  }

  public void addElement(Map attributes)
  {
    // Interpret the input tag, and make a list of the potential elements we'll want to submit
    String type = (String)attributes.get("type");
    if (type == null)
      type = "text";
    String name = (String)attributes.get("name");
    if (name != null)
    {
      String lowerType = type.toLowerCase();
      if (lowerType.equals("submit"))
      {
        String value = (String)attributes.get("value");
        if (value == null)
          value = "Submit Form";
        elementList.add(new FormItem(name,value,ELEMENTCATEGORY_FREEFORM,true));
      }
      else if (lowerType.equals("hidden") || lowerType.equals("text") || lowerType.equals("password"))
      {
        String value = (String)attributes.get("value");
        if (value == null)
          value = "";
        elementList.add(new FormItem(name,value,ELEMENTCATEGORY_FREEFORM,true));
      }
      else if (lowerType.equals("select"))
      {
        String value = (String)attributes.get("value");
        if (value == null)
          value = "";
        String selected = (String)attributes.get("selected");
        boolean isSelected = false;
        if (selected != null)
          isSelected = true;
        String multiple = (String)attributes.get("multiple");
        boolean isMultiple = false;
        if (multiple != null)
          isMultiple = true;
        elementList.add(new FormItem(name,value,isMultiple?ELEMENTCATEGORY_FIXEDINCLUSIVE:ELEMENTCATEGORY_FIXEDEXCLUSIVE,isSelected));
      }
      else if (lowerType.equals("radio"))
      {
        String value = (String)attributes.get("value");
        if (value == null)
          value = "";
        String selected = (String)attributes.get("checked");
        boolean isSelected = false;
        if (selected != null)
          isSelected = true;
        elementList.add(new FormItem(name,value,ELEMENTCATEGORY_FIXEDEXCLUSIVE,isSelected));
      }
      else if (lowerType.equals("checkbox"))
      {
        String value = (String)attributes.get("value");
        if (value == null)
          value = "";
        String selected = (String)attributes.get("checked");
        boolean isSelected = false;
        if (selected != null)
          isSelected = true;
        elementList.add(new FormItem(name,value,ELEMENTCATEGORY_FIXEDINCLUSIVE,isSelected));
      }
      else if (lowerType.equals("textarea"))
      {
        elementList.add(new FormItem(name,"",ELEMENTCATEGORY_FREEFORM,true));
      }
    }
  }

  public void overrideActionURI(String overrideURI)
  {
    this.actionURI = overrideURI;
  }
  
  public void applyOverrides(LoginParameters lp)
  {
    // This map contains the control names we have ALREADY wiped clean.
    Map overrideMap = new HashMap();

    // Override the specified elements with the specified values
    int i = 0;
    while (i < lp.getParameterCount())
    {
      Pattern namePattern = lp.getParameterNamePattern(i);
      String value = lp.getParameterValue(i);
      i++;

      // For each parameter specified, go through the element list and do the right thing.  This will require us to keep some state around about
      // what exactly we've done to the element list so far, so that each parameter rule in turn applies properly.
      //
      // Each rule regular expression will be deemed to apply to all matching controls.  If the rule matches the control name, then the precise behavior
      // will depend on the type of the control.
      //
      // Controls can be categorized in the following way:
      // - free-form value
      // - specified exclusive value (e.g. radio button)
      // - specified inclusive value (e.g. checkbox)
      //
      // For free-form values, the value given will simply override the value of the element.
      // For exclusive controls, all values in the family will be disabled, and the value matching the one specified will be enabled.
      // For inclusive controls, all values in the family will be cleared ONCE, and then subsequently the value matching the one specified will be enabled.
      //
      int j = 0;
      while (j < elementList.size())
      {
        FormItem fi = (FormItem)elementList.get(j++);
        Matcher m = namePattern.matcher(fi.getElementName());
        if (m.find())
        {
          // Hey, it seems to apply!
          switch (fi.getType())
          {
          case ELEMENTCATEGORY_FREEFORM:
            // Override immediately
            fi.setValue(value);
            break;
          case ELEMENTCATEGORY_FIXEDEXCLUSIVE:
            // If it doesn't match the value, disable.
            fi.setEnabled(fi.getElementValue().equals(value));
            break;
          case ELEMENTCATEGORY_FIXEDINCLUSIVE:
            // Make sure we clear the entire control ONCE (and only once).
            if (overrideMap.get(fi.getElementName()) == null)
            {
              // Zip through the entire list
              int k = 0;
              while (k < elementList.size())
              {
                FormItem fi2 = (FormItem)elementList.get(k++);
                if (fi2.getElementName().equals(fi.getElementName()))
                  fi.setEnabled(false);
              }
              overrideMap.put(fi.getElementName(),fi.getElementName());
            }
            if (fi.getElementValue().equals(value))
              fi.setEnabled(true);
          default:
            break;
          }
        }
      }
    }
  }

  /** Get the full action URI for this form. */
  public String getActionURI()
  {
    return actionURI;
  }

  /** Get the submit method for this form. */
  public int getSubmitMethod()
  {
    return submitMethod;
  }

  /** Iterate over the active form data elements.  The returned iterator returns FormDataElement objects. */
  public Iterator getElementIterator()
  {
    return new FormItemIterator(elementList);
  }

  /** Iterator over FormItems */
  protected static class FormItemIterator implements Iterator
  {
    protected ArrayList elementList;
    protected int currentIndex = 0;

    public FormItemIterator(ArrayList elementList)
    {
      this.elementList = elementList;
    }

    public boolean hasNext()
    {
      while (true)
      {
        if (currentIndex == elementList.size())
          return false;
        if (((FormItem)elementList.get(currentIndex)).getEnabled() == false)
          currentIndex++;
        else
          break;
      }
      return true;
    }

    public Object next()
    {
      while (true)
      {
        if (currentIndex == elementList.size())
          throw new NoSuchElementException("No such element");
        if (((FormItem)elementList.get(currentIndex)).getEnabled() == false)
          currentIndex++;
        else
          break;
      }
      return elementList.get(currentIndex++);
    }

    public void remove()
    {
      throw new UnsupportedOperationException("Unsupported operation");
    }
  }

}
