/*
 * (C) 2004 - Geotechnical Software Services
 * 
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
 * MA  02111-1307, USA.
 */
package no.geosoft.cc.graphics;



import no.geosoft.cc.geometry.Rect;



/**
 * Common base class for objects that can be positioned through
 * hints within their container. 
 *
 * @author <a href="mailto:info@geosoft.no">GeoSoft</a>
 */   
abstract class GPositional
{
  protected GSegment  segment_;
  protected int       positionHint_;
  protected Rect      rectangle_;
  protected boolean   isVisible_;
  protected GStyle    style_;
  protected GStyle    actualStyle_;  // Adjusted for owner inherits

  private final boolean   isAllowingOverlaps_;
  

  
  /**
   * Create a positional instance with specified position hint.
   * @see #setPositionHint(int)
   * 
   * @param positionHint        Position hint for this instance.
   * @param isAllowingOverlaps  True if this instance can be overlapped by
   *                            other positionals, false otherwise.
   */
  GPositional (int positionHint, boolean isAllowingOverlaps)
  {
    rectangle_          = new Rect();
    isVisible_          = false;
    isAllowingOverlaps_ = isAllowingOverlaps;
    segment_            = null;
    style_              = null;
    actualStyle_        = new GStyle();

    setPositionHint (positionHint);
  }
  

  
  /**
   * Set owner segment of this positional.
   * 
   * @param segment  New owner segment of this positional.
   */
  void setSegment (GSegment segment)
  {
    segment_ = segment;
    updateStyle();
  }

  
  
  /**
   * Return owner segment of this positional.
   * 
   * @return  Owner segment of this positional (or null if not
   *          attached to a segment).
   */
  GSegment getSegment()
  {
    return segment_;
  }
  

  
  /**
   * Return graphics object of this positional.
   * 
   * @return  Object of this positional (or null if not attached to an
   *          object).
   */
  protected GObject getObject()
  {
    return segment_ != null ? segment_.getOwner() : null;
  }
  
  

  /**
   * Set position hint for this positional.
   * <p>
   * Position hints is a or'ed list of:
   *
   * <ul>
   * <li>Line position hint  - FIRST, LAST, TOP, BOTTON, LEFT, RIGHT
   * <li>Point position hint - CENTER, NORTH, SOUTH, EAST, WEST, NORTHEAST,
   *                           NORTHWEST, SOUTHEAST, SOUTHWEST
   * <li>Algorithm           - STATIC, DYNAMIC
   * </ul>
   *
   * <p>
   * Line position hints are interpreted as follows:
   *
   * <ul>
   * <li> GPosition.FIRST  - Attach to first polyline point.
   * <li> GPosition.LAST   - Attach to last polyline point.
   * <li> GPosition.TOP    - Attach to top polyline as it appears on screen
   *                         when rendered.
   * <li> GPosition.BOTTOM - Attach to bottom polyline as it appears on screen
   *                         when rendered.
   * <li> GPosition.RIGHT  - Attach to right polyline as it appears on screen
   *                         when rendered.
   * <li> GPosition.LEFT   - Attach to left polyline as it appears on screen
   *                         when rendered.
   * </ul>
   *
   * If a line position hint is not given, the text is attached to the n'th
   * point of the polyline according to when it was added to the segment,
   * i.e, the first text added is attached to the first coordinate, the second
   * text added is attatched to the second coordinate and so on. This is
   * convenient if there is a text associated with each line vertex.
   *
   * <p>
   * For the above, the given point becomes the initial approach for
   * positioning. If this initial position is outside the window (and position
   * hint not explicitly set to GPosition.STATIC) the text is moved along
   * the polyline till it becomes fully visible.
   *
   * <p>
   * If text position hint is GPosition.MIDDLE, the initial position is
   * the center of the polyline bounding box. If this point is not visble
   * the text is not rendered.
   *
   * <p>
   * At this point an anchor point is found for the text. Now the point
   * positioning text hints are examined:
   *
   * <ul>
   * <li> GPosition.CENTER    - Put text centered on top of anchor point.
   * <li> GPosition.NORTH     - Put text above anchor point.
   * <li> GPosition.SOUTH     - Put text below anchor point.
   * <li> GPosition.EAST      - Put text to the right of anchor point.
   * <li> GPosition.WEST      - Put text to the left of anchor point.   
   * <li> GPosition.NORTHEAST - Put text above and right of the anchor point.
   * <li> GPosition.NORTHWEST - Put text above and left of the anchor point.
   * <li> GPosition.SOUTHEAST - Put text below and right of the anchor point.
   * <li> GPosition.SOUTHWEST - Put text below and left of the anchor point.
   * </ul>
   *
   * <p>
   * If a point positioing hint is not given, the defualt is GPosition.CENTER
   * unless line position hint is GPosition.TOP (implying GPosition.NORTH),
   * GPosition.BOTTOM (implying GPosition.SOUTH), GPosition.LEFT (implying
   * GPosition.WEST) or GPosition:RIGHT (implying GPosition.EAST).
   *
   * <p>
   * Now, if the text in this location overlap an already positioned text,
   * it is further adjusted (unless position hint is not explicitly set to
   * GPosition.STATIC as discussed above). It is again moved along the
   * polyline till a free location is found. If this cannot be acheived,
   * the text is not rendered
   *
   * @see GPosition
   * 
   * @param positionHint  Position hint for this positional.
   */
  public void setPositionHint (int positionHint)
  {
    positionHint_ = positionHint;

    // If point hint is not specified, default it according to line hint
    if ((positionHint_ &
         (GPosition.CENTER    |  
          GPosition.NORTHWEST |
          GPosition.NORTH     |
          GPosition.NORTHEAST |
          GPosition.WEST      |
          GPosition.EAST      |
          GPosition.SOUTHWEST |
          GPosition.SOUTH     |
          GPosition.SOUTHEAST)) == 0) {
      if      ((positionHint_ & GPosition.TOP)    != 0)
        positionHint_ |= GPosition.NORTH;
      else if ((positionHint_ & GPosition.BOTTOM) != 0)
        positionHint_ |= GPosition.SOUTH;
      else if ((positionHint_ & GPosition.LEFT)   != 0)
        positionHint_ |= GPosition.WEST;
      else if ((positionHint_ & GPosition.RIGHT)  != 0)
        positionHint_ |= GPosition.EAST;
    }

    // If algorithm is not specified, default to dynamic
    if ((positionHint_ & GPosition.DYNAMIC) == 0 &&
        (positionHint_ & GPosition.STATIC) == 0)
      positionHint_ |= GPosition.DYNAMIC;
  }


  
  /**
   * Return position hint of this positional.
   * @see #setPositionHint(int).
   * 
   * @return  Position hint of this positional.
   */
  public int getPositionHint()
  {
    return positionHint_;
  }


  
  /**
   * Return if this positional is allowed to be overlapped by other positionals.
   * 
   * @return  True if this positional is allowed to be overlapped other
   *          positionals false otherwise.
   */
  boolean isAllowingOverlaps()
  {
    return isAllowingOverlaps_;
  }
  

  
  /**
   * Return true if the position of this positional is along the line
   * (as opposed to be fixed at a point on the line).
   * 
   * @return  True if this poitional is "line positional", false otherwise.
   */
  boolean isLinePositional()
  {
    return (positionHint_ & (GPosition.FIRST  |
                             GPosition.LAST   |
                             GPosition.TOP    |
                             GPosition.BOTTOM |
                             GPosition.LEFT   |
                             GPosition.RIGHT  |
                             GPosition.MIDDLE)) != 0;
  }


  
  /**
   * Return true if this positional is visible after the annotation process.
   * 
   * @return  True if this positional is visible, false otherwise.
   */
  boolean isVisible()
  {
    return isVisible_;
  }



  /**
   * Set visibility of this positional. Visibility is due to positional
   * layout, not visibility settings on the owner object.
   *
   * @param isVisible  True if positional is visible, false otherwise.
   */
  void setVisible (boolean isVisible)
  {
    isVisible_ = isVisible;
  }


  
  /**
   * Return rectangle bounding box of this positional.
   * 
   * @return  Rectangle bounding box of this positional.
   */
  Rect getRectangle()
  {
    return rectangle_;
  }

  

  /**
   * Compute the size (width and height component of the rectangle variable)
   * of this positional.
   */
  abstract void computeSize();
  


  /**
   * Return margin to use when positioning this positional at a point.
   * If point position hint is NORTH, the positional rectangle is put
   * this many pixels above the associated point etc.
   * 
   * @return  Margin for point positioning.
   */
  int getMargin()
  {
    // Default to 0, override in subclass if necessary.
    return 0;
  }
  
  
  
  /**
   * Set new style for this object. Style elements not explicitly
   * set within this GStyle object are inherited from parent objects.
   * Child objects without explicit set style will inherit from this
   * style object.
   * 
   * @param style  Style for this object.
   */
  public void setStyle (GStyle style)
  {
    style_ = style;
    updateStyle();
  }
  


  /**
   * Get style of this GText. This is the style set by setStyle()
   * and not necesserily the style as it appears on screen as unset
   * style elements are inherited from parents.
   * 
   * @return  Style of this object.
   */
  public GStyle getStyle()
  {
    return style_;
  }

  

  /**
   * These are the actual style used for this object when
   * inheritance for unset values are resolved.
   * TODO: Make this public?
   * 
   * @return  Actual style for this segment.
   */
  GStyle getActualStyle()
  {
    return actualStyle_;
  }



  /**
   * Update actualStyle based on this style and style of parent
   * elements. Actual style is how this element is actually rendered
   * on screen.
   */
  void updateStyle()
  {
    // Initialize style
    actualStyle_ = new GStyle();

    // Update with parent style
    if (segment_ != null)
      actualStyle_.update (segment_.getActualStyle());

    // Update (and possible override) with present style
    if (style_ != null)
      actualStyle_.update (style_);

    // Flag owner region and scene annotation as invalid
    GObject object = getObject();
    GScene  scene  = object != null ? object.getScene() : null;

    if (object != null) object.flagRegionValid (false);
    if (scene  != null) scene.setAnnotationValid (false);

    updateDamage();
  }


  
  /**
   * Mark the extent of this positional as damaged.
   */
  void updateDamage()
  {
    GObject object = getObject();
    
    if (isVisible()           &&
        rectangle_ != null    &&
        !rectangle_.isEmpty() &&
        object != null        &&
        object.getWindow() != null)
      object.getWindow().updateDamageArea (rectangle_);
  }
}