#ifndef __DW_OOFAWAREWIDGET_HH__
#define __DW_OOFAWAREWIDGET_HH__

#include "core.hh"
#include "outofflowmgr.hh"

namespace dw {

namespace oof {

/**
 * \brief Base class for widgets which can act as container and
 *     generator for widgets out of flow.
 *
 * (Perhaps it should be diffenciated between the two roles, container
 * and generator, but this would make multiple inheritance necessary.)
 *
 * See \ref dw-out-of-flow for an overview.
 *
 * Requirements for sub classes (in most cases refer to dw::Textblock
 * as a good example):
 *
 * - A sub class should at least take care to call these methods at the
 *   respective points:
 *
 *   - dw::oof::OOFAwareWidget::correctRequisitionByOOF (from
 *     dw::core::Widget::getExtremesImpl)
 *   - dw::oof::OOFAwareWidget::correctExtremesByOOF (from
 *     dw::core::Widget::sizeRequestImpl)
 *   - dw::oof::OOFAwareWidget::sizeAllocateStart
 *   - dw::oof::OOFAwareWidget::sizeAllocateEnd (latter two from
 *     dw::core::Widget::sizeAllocateImpl)
 *   - dw::oof::OOFAwareWidget::containerSizeChangedForChildrenOOF
 *     (from dw::core::Widget::containerSizeChangedForChildren)
 *   - dw::oof::OOFAwareWidget::drawOOF (from dw::core::Widget::draw)
 *   - dw::oof::OOFAwareWidget::getWidgetOOFAtPoint (from
 *     dw::core::Widget::getWidgetAtPoint)
 *
 * - Implementations of dw::core::Widget::getAvailWidthOfChild and
 *   dw::core::Widget::getAvailHeightOfChild have to distinguish
 *   between widgets in flow and out of flow; general pattern:
 *
 *    \code
 * if (isWidgetOOF (child) && getWidgetOutOfFlowMgr(child) &&
 *     getWidgetOutOfFlowMgr(child)->dealingWithSizeOfChild (child))
 *    width =
 *      getWidgetOutOfFlowMgr(child)->getAvailWidthOfChild (child,forceValue);
 * else {
 *    // ... specific implementation ...
 *    \endcode
 *
 *   See also implementations of dw::Textblock and dw::Table. (Open
 *   issue: What about dw::core::Widget::correctRequisitionOfChild and
 *   dw::core::Widget::correctExtremesOfChild? Currently, all widgets
 *   are used the default implementation.)
 *
 * - Iterators have to consider widgets out of flow;
 *   dw::oof::OOFAwareWidget::OOFAwareWidgetIterator is recommended as
 *   base class.
 *
 * - dw::core::Widget::parentRef has to be set for widgets in flow; if
 *   not used further, a simple *makeParentRefInFlow(0)* is sufficient
 *   (as dw::Table::addCell does). Widgets which are only containers,
 *   but not generators, do not have to care about widgets out of
 *   flow in this regard.
 *
 * For both generators and containers of floats (which is only
 * implemented by dw::Textblock) it gets a bit more complicated.
 *
 * \todo Currently, on the level of dw::oof::OOFAwareWidget, nothing
 * is done about dw::core::Widget::markSizeChange and
 * dw::core::Widget::markExtremesChange. This does not matter, though:
 * dw::Textblock takes care of these, and dw::Table is only connected
 * to subclasses of dw::oof::OOFPositionedMgr, which do care about
 * these. However, this should be considered for completeness.
 */
class OOFAwareWidget: public core::Widget
{ 
protected:
   enum { OOFM_FLOATS, OOFM_ABSOLUTE, OOFM_RELATIVE, OOFM_FIXED, NUM_OOFM };
   static const char *OOFM_NAME[NUM_OOFM];
   enum { PARENT_REF_OOFM_BITS = 3,
          PARENT_REF_OOFM_MASK = (1 << PARENT_REF_OOFM_BITS) - 1 };

   class OOFAwareWidgetIterator: public core::Iterator
   {
   private:
      enum { NUM_SECTIONS = NUM_OOFM + 1 };
      int sectionIndex; // 0 means in flow, otherwise OOFM index + 1
      int index;

      int numParts (int sectionIndex, int numContentsInFlow = -1);
      void getPart (int sectionIndex, int index, core::Content *content);

   protected:
      virtual int numContentsInFlow () = 0;
      virtual void getContentInFlow (int index, core::Content *content) = 0;

      void setValues (int sectionIndex, int index);
      inline void cloneValues (OOFAwareWidgetIterator *other)
      { other->setValues (sectionIndex, index); }         

      inline bool inFlow () { return sectionIndex == 0; }
      inline int getInFlowIndex () { assert (inFlow ()); return index; }
      void highlightOOF (int start, int end, core::HighlightLayer layer);
      void unhighlightOOF (int direction, core::HighlightLayer layer);
      void getAllocationOOF (int start, int end, core::Allocation *allocation);

   public:
      OOFAwareWidgetIterator (OOFAwareWidget *widget, core::Content::Type mask,
                              bool atEnd, int numContentsInFlow);

      void intoStringBuffer(lout::misc::StringBuffer *sb);
      int compareTo(lout::object::Comparable *other);

      bool next ();
      bool prev ();
   };

   inline bool isParentRefOOF (int parentRef)
   { return parentRef != -1 && (parentRef & PARENT_REF_OOFM_MASK); }

   inline int makeParentRefInFlow (int inFlowSubRef)
   { return (inFlowSubRef << PARENT_REF_OOFM_BITS); }
   inline int getParentRefInFlowSubRef (int parentRef)
   { assert (!isParentRefOOF (parentRef));
      return parentRef >> PARENT_REF_OOFM_BITS; }

   inline int makeParentRefOOF (int oofmIndex, int oofmSubRef)
   { return (oofmSubRef << PARENT_REF_OOFM_BITS) | (oofmIndex + 1); }
   inline int getParentRefOOFSubRef (int parentRef)
   { assert (isParentRefOOF (parentRef));
      return parentRef >> PARENT_REF_OOFM_BITS; }
   inline int getParentRefOOFIndex (int parentRef)
   { assert (isParentRefOOF (parentRef));
      return (parentRef & PARENT_REF_OOFM_MASK) - 1; }
   inline oof::OutOfFlowMgr *getParentRefOutOfFlowMgr (int parentRef)
   { return outOfFlowMgr[getParentRefOOFIndex (parentRef)]; }

   inline bool isWidgetOOF (Widget *widget)
   { return isParentRefOOF (widget->parentRef); }

   inline int getWidgetInFlowSubRef (Widget *widget)
   { return getParentRefInFlowSubRef (widget->parentRef); }

   inline int getWidgetOOFSubRef (Widget *widget)
   { return getParentRefOOFSubRef (widget->parentRef); }
   inline int getWidgetOOFIndex (Widget *widget)
   { return getParentRefOOFIndex (widget->parentRef); }
   inline oof::OutOfFlowMgr *getWidgetOutOfFlowMgr (Widget *widget)
   { return getParentRefOutOfFlowMgr (widget->parentRef); }

   OOFAwareWidget *oofContainer[NUM_OOFM];
   OutOfFlowMgr *outOfFlowMgr[NUM_OOFM];
   core::Requisition requisitionWithoutOOF;

   inline OutOfFlowMgr *searchOutOfFlowMgr (int oofmIndex)
   { return oofContainer[oofmIndex] ?
         oofContainer[oofmIndex]->outOfFlowMgr[oofmIndex] : NULL; }

   static int getOOFMIndex (Widget *widget);

   void initOutOfFlowMgrs ();
   void correctRequisitionByOOF (core::Requisition *requisition,
                                 void (*splitHeightFun) (int, int*, int*));
   void correctExtremesByOOF (core::Extremes *extremes);
   void sizeAllocateStart (core::Allocation *allocation);
   void sizeAllocateEnd ();
   void containerSizeChangedForChildrenOOF ();

   virtual void drawLevel (core::View *view, core::Rectangle *area, int level,
                           core::DrawingContext *context);
   void drawOOF (core::View *view, core::Rectangle *area,
                 core::DrawingContext *context);

   Widget *getWidgetAtPoint (int x, int y,
                             core::GettingWidgetAtPointContext *context);
   virtual Widget *getWidgetAtPointLevel (int x, int y, int level,
                                          core::GettingWidgetAtPointContext
                                          *context);
   Widget *getWidgetOOFAtPoint (int x, int y,
                                core::GettingWidgetAtPointContext *context);

   static bool isOOFContainer (Widget *widget, int oofmIndex);

   void notifySetAsTopLevel();
   void notifySetParent();

   void removeChild (Widget *child);

   virtual bool adjustExtraSpaceWhenCorrectingRequisitionByOOF ();

public:
   enum {
      SL_START, SL_BACKGROUND, SL_SC_BOTTOM, SL_IN_FLOW, SL_OOF_REF,
      SL_OOF_CONT, SL_SC_TOP, SL_END };

   static int CLASS_ID;

   OOFAwareWidget ();
   ~OOFAwareWidget ();

   static const char *stackingLevelText (int level);

   static inline bool testStyleFloat (core::style::Style *style)
   { return style->vloat != core::style::FLOAT_NONE; }

   static inline bool testStyleAbsolutelyPositioned (core::style::Style *style)
   { return IMPL_POS && style->position == core::style::POSITION_ABSOLUTE; }
   static inline bool testStyleFixedlyPositioned (core::style::Style *style)
   { return IMPL_POS && style->position == core::style::POSITION_FIXED; }
   static inline bool testStyleRelativelyPositioned (core::style::Style *style)
   { return IMPL_POS && style->position == core::style::POSITION_RELATIVE; }

   static inline bool testStylePositioned (core::style::Style *style)
   { return testStyleAbsolutelyPositioned (style) ||
         testStyleRelativelyPositioned (style) ||
         testStyleFixedlyPositioned (style); }

   static inline bool testStyleOutOfFlow (core::style::Style *style)
   {  // Second part is equivalent to testStylePositioned(), but we still keep
      // the two seperately.
      return testStyleFloat (style) || testStyleAbsolutelyPositioned (style)
         || testStyleRelativelyPositioned (style)
         || testStyleFixedlyPositioned (style); }
   
   static inline bool testWidgetFloat (Widget *widget)
   { return testStyleFloat (widget->getStyle ()); }

   static inline bool testWidgetAbsolutelyPositioned (Widget *widget)
   { return testStyleAbsolutelyPositioned (widget->getStyle ()); }
   static inline bool testWidgetFixedlyPositioned (Widget *widget)
   { return testStyleFixedlyPositioned (widget->getStyle ()); }
   static inline bool testWidgetRelativelyPositioned (Widget *widget)
   { return testStyleRelativelyPositioned (widget->getStyle ()); }

   static inline bool testWidgetPositioned (Widget *widget)
   { return testStylePositioned (widget->getStyle ()); }

   static inline bool testWidgetOutOfFlow (Widget *widget)
   { return testStyleOutOfFlow (widget->getStyle ()); }

   inline core::Requisition *getRequisitionWithoutOOF ()
   { return &requisitionWithoutOOF; }

   bool doesWidgetOOFInterruptDrawing (Widget *widget);

   void draw (core::View *view, core::Rectangle *area,
              core::DrawingContext *context);

   /**
    * Update content in flow, down from `ref`. Uses e. g. when floats sizes have
    * changed.
    */
   virtual void updateReference (int ref);

   /**
    * Called by an implementation of dw::oof::OutOfFlowMgr (actually only
    * OOFPosRelMgr) for the generator of a widget out of flow, when the
    * reference size has changed. (The size of the reference is 0 * 0 for all
    * other implementations of dw::oof::OutOfFlowMgr.)
    */
   virtual void widgetRefSizeChanged (int externalIndex);
   
   /**
    * Called by an implementation of dw::oof::OutOfFlowMgr when the size of the
    * container has changed, typically in sizeAllocateEnd.
    */
   virtual void oofSizeChanged (bool extremesChanged);

   /**
    * Return position relative to container, not regarding
    * margin/border/padding, Called by OOFFloatsMgr to position floats.
    */
   virtual int getGeneratorX (int oofmIndex);

   /**
    * Return position relative to container, not regarding
    * margin/border/padding, Called by OOFFloatsMgr to position floats.
    */
   virtual int getGeneratorY (int oofmIndex);

   /**
    * Return width including margin/border/padding Called by OOFFloatsMgr to
    * position floats.
    */
   virtual int getGeneratorWidth ();

   virtual int getMaxGeneratorWidth ();

   virtual bool usesMaxGeneratorWidth ();
   
   virtual bool isPossibleOOFContainer (int oofmIndex);

   virtual bool isPossibleOOFContainerParent (int oofmIndex);
};

} // namespace oof

} // namespace dw

#endif // __DW_OOFAWAREWIDGET_HH__
