/*
 * @(#)RowsetIterateTag.java
 *
 * Copyright 2001-2002 by Oracle Corporation,
 * 500 Oracle Parkway, Redwood Shores, California, 94065, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Oracle Corporation.
 */

package oracle.jbo.html.jsp.datatags;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.Tag;
import oracle.jbo.Row;
import oracle.jbo.RowSet;
import oracle.jbo.html.DataSource;

public class RowsetIterateTag extends BodyTagSupport implements DataBinder
{
   protected String      sDataSource;
   protected boolean     changeCurrentRow;
   protected boolean     useRange;
   protected DataSource  ds;
   protected RowSet      rs;
   protected RowIteratorForTag iter;

   public RowsetIterateTag()
   {
      reset();
   }

   public void setDatasource(String sDataSource)
   {
      this.sDataSource = sDataSource;
   }

   public void setChangecurrentrow(String sValue)
   {
      changeCurrentRow = !Utils.isFalse(sValue);
   }

   public void setUserange(String sValue)
   {
      useRange = Utils.isTrue(sValue);
   }
   
   public String getDataSourceName()
   {
      return sDataSource;
   }

   public DataSource getDataSource()
   {
      return ds;
   }
   
   public Row getRow()
   {
      return iter.getRow();
   }

   protected void initialize()
   {
      ds = Utils.getDataSourceFromContext(pageContext, sDataSource);
      rs = ds.getRowSet();
   
      // Instantiate the appropriate iterator class based on the parameters.   
      if (changeCurrentRow)
      {
         if (useRange)
         {
            iter = new CurrencyWithRangeIterator();
         }
         else
         {
            iter = new CurrencyNoRangeIterator();
         }
      }
      else
      {
         if (useRange)
         {
            iter = new NoCurrencyWithRangeIterator();
         }
         else
         {
            iter = new NoCurrencyNoRangeIterator();
         }
      }
   }
   
   /**
      * Process the start tag for this instance.
      *
      * The doStartTag() method assumes that all setter methods have been
      * invoked before.
      *
      * When this method is invoked, the body has not yet been invoked.
      *
      * @returns EVAL_BODY_INCLUDE if the tag wants to process body, SKIP_BODY if it
      * does ont want to process it.
      */
   public int doStartTag() throws JspException
   {
      initialize();
      
      if (iter.hasNext())
      {
         iter.next();
         return BodyTag.EVAL_BODY_AGAIN;
      }
      else
      {
         return Tag.SKIP_BODY;
      }
   }

   /**
     * Actions after some body has been evaluated.
     *
     * Not invoked in empty tags or in tags returning SKIP_BODY in doStartTag()
     * This method is invoked after every body evaluation.
     * The pair "BODY -- doAfterBody()" is invoked initially if doStartTag()
     * returned EVAL_BODY_TAG, and it is repeated as long
     * as the doAfterBody() evaluation returns EVAL_BODY_TAG
     * <p>
     * The method re-invocations may be lead to different actions because
     * there might have been some changes to shared state, or because
     * of external computation.
     *
     * @returns whether additional evaluations of the body are desired
     * @seealso #doInitBody
     */
   public int doAfterBody() throws JspException
   {
      if (iter.hasNext())
      {
         // move on to the next row
         iter.next();
      }
      else
      {
         iter.restoreState();
         return Tag.SKIP_BODY;
      }
      
      return BodyTag.EVAL_BODY_AGAIN;
   }

   /**
     * Process the end tag. This method will be called on all Tag objects.
     *
     * All instance state associated with this instance must be reset.
     * The release() method should be called after this invocation.
     */
   public int doEndTag() throws JspException
   {
      if (this.bodyContent != null)
      {
         try
         {
            bodyContent.writeOut(bodyContent.getEnclosingWriter());
         }
         catch (java.io.IOException ex)
         {
            pageContext.getServletContext().log(Res.getString(Res.IO_ERROR), ex);
            throw new JspTagException(ex.getMessage());
         }
      }
      
      return EVAL_PAGE;
   }

   
   // Use by the constructor and the release method to reset the member variable values
   private void reset()
   {
      sDataSource = null;
      changeCurrentRow = true;
      useRange = false;
      ds = null;
      rs = null;
      iter = null;
   }
   
   /**
     * reset the state of the Tag
     */
   public void release()
   {
      reset();
      super.release();
   }

   final class CurrencyNoRangeIterator implements RowIteratorForTag
   {
      private final Row origRow;
      
      CurrencyNoRangeIterator()
      {
         origRow = rs.getCurrentRow();
         rs.reset();
      }
      
      public boolean hasNext()
      {
         return rs.hasNext();
      }

      public void next()
      {
         rs.next();
      }
      
      public Row getRow()
      {
         return rs.getCurrentRow();
      }

      public void restoreState()
      {
         // Restore original currency of the rowset
         if (origRow != null)
            rs.setCurrentRow(origRow);
      }
   }

   final class CurrencyWithRangeIterator implements RowIteratorForTag
   {
      private int rowIndex;
      private int rowCountInRange;
      private final Row origRow;
      
      CurrencyWithRangeIterator()
      {
         origRow = rs.getCurrentRow();
         rowIndex = -1;
         rowCountInRange = rs.getRowCountInRange();
      }
      
      public boolean hasNext()
      {
         return (rowIndex+1 < rowCountInRange);
      }

      public void next()
      {
         rowIndex++;
         rs.setCurrentRowAtRangeIndex(rowIndex);
      }
      
      public Row getRow()
      {
         return rs.getCurrentRow();
      }

      public void restoreState()
      {
         // Restore original currency of the rowset
         if (origRow != null)
            rs.setCurrentRow(origRow);
      }
   }


   final class NoCurrencyWithRangeIterator implements RowIteratorForTag
   {
      private int rowIndex;
      private int rowCountInRange;
      
      NoCurrencyWithRangeIterator()
      {
         rowIndex = -1;
         rowCountInRange = rs.getRowCountInRange();
      }
      
      public boolean hasNext()
      {
         return (rowIndex+1 < rowCountInRange);
      }

      public void next()
      {
         rowIndex++;
      }
      
      public Row getRow()
      {
         return rs.getRowAtRangeIndex(rowIndex);
      }

      public void restoreState()
      {
         // no-op
      }
   }

   final class NoCurrencyNoRangeIterator implements RowIteratorForTag
   {
      private int rowIndex;
      private int rowCountInRange;
      private final int rangeStart;
      
      NoCurrencyNoRangeIterator()
      {
         rowIndex = -1;
         rangeStart = rs.getRangeStart();
         rs.setRangeStart(0);
         rowCountInRange = rs.getRowCountInRange();
      }
      
      public boolean hasNext()
      {
         if (rowIndex+1 >= rowCountInRange)
         {
            int scrolled = rs.scrollRange(rowCountInRange);
            if (scrolled == 0)
            {
               return false;
            }
            rowIndex = rowCountInRange - scrolled - 1;
         }

         return true;
      }

      public void next()
      {
         rowIndex++;
      }
      
      public Row getRow()
      {
         return rs.getRowAtRangeIndex(rowIndex);
      }

      public void restoreState()
      {
         // Restore range state
         rs.setRangeStart(rangeStart);
      }
   }

}

