package DDrawing;


import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.print.*;
import java.util.Vector;
import javax.swing.*;
import javax.swing.border.Border;

/**
 * DArea is the junktion of the <code>JComponent</code>s and the <code>DComponent</code>s. It's the <code>DParent</code> which can be added to <code>JComponent</code>s
 */
public class DArea extends JComponent implements DParent, Printable{

  /**
   * the default minimal rectangle which is shown
   */
  public static final DRectangle DEFAULT_MIN_RECT = new DRectangle(-1, -1, 2, 2);

  private DContainer container;

  /**
   * min_rectangle is set, when all elements are removed
   * visible_rect is the currently visible rectangle
   */
  protected DRectangle min_rect = DEFAULT_MIN_RECT ;
  /**
   * min_rectangle is set, when all elements are removed
   * visible_rect is the currently visible rectangle
   */
  protected DRectangle visible_rect = DEFAULT_MIN_RECT ;

  /**
   * whether there is a grid gris is or not
   */
  private DGrid grid;
  private boolean auto_focus = false ;
  private boolean auto_grid = false ;
  private boolean grid_to_front = false ;

  /**
   * maximal number of grid lines
   */
  private int max_grid = 10;

  private DMeasures measures;

  /**
   * initializes the DArea with the initial capacity of 10 components
   */
  public DArea(){
    this( 10 );
  }

  /**
   * initializes the DArea with the specialized initial capacity of components
   * (@see java.util.Vector)
   *
   * @param the initial capacity
   */
  public DArea( int initial_capacity ){
    container = new DContainer();
    container.setDParent( this );
    grid = new DGrid( visible_rect, 1, 1 );
    grid.setVisible( false );
    grid.setDParent( this );
    measures = new DMeasures( this );
  }

  /**
   * returns the currently visible rectangle in DArea coordinates
   *
   * @return DRectangle the size and position of the visible area
   */
  public DRectangle getDRectangle(){
    return (DRectangle)visible_rect.clone();
  }

  /**
   * switches the auto focus of this DArea on or off
   *
   * @param b on or off
   */
  public void setAutoFocus( boolean b ){
    boolean old = auto_focus;
    auto_focus = b;
    if( old != b ) repaint();
  }

  /**
   * returns whether the DArea's auto focus is on or not
   *
   * @return <code>true</code> or <code>false</code>
   */
  public boolean isOnAutoFocus(){ return auto_focus; }

  /**
   * sets the visible rectangle to this size
   *
   * @param x the x coordinate of the left border
   * @param y the y coordinate of the bottom border
   * @param width the width of the area
   * @param height the height of the area
   */
  public void setVisibleRectangle( double x, double y, double width, double height ){
    //System.out.println("DArea.setVisibleRectangle(...)");
    setVisibleRectangle( new DRectangle( x, y, width, height ) );
  }

  /**
   * sets the visible rectangle
   *
   * @param rect the visible <code>DRectangle</code> in DArea coordinates
   */
  public void setVisibleRectangle( DRectangle rect ){
    //System.out.println("DArea.setVisibleRectangle(DRectangle)");
    if( rect == null || rect.isEmpty() ) rect = (DRectangle)min_rect.clone();
    if( !rect.equals( visible_rect ) && rect.width > 0 && rect.height > 0 ){
      auto_focus = false;
      visible_rect = (DRectangle)rect.clone();
      repaint();
    }
  }

  /**
   * sets the minimal rectangle
   *
   * @param x
   * @param y
   * @param width
   * @param height
   */
  public void setMinRectangle( double x, double y, double width, double height ){
    setMinRectangle( new DRectangle( x, y, width, height ) );
  }

  /**
   * sets the minimal rectangle
   *
   * @param rect the visible <code>DRectangle</code> in DArea coordinates
   */
  public void setMinRectangle( DRectangle rect ){
    if( rect == null || rect.isEmpty() ) min_rect = DEFAULT_MIN_RECT;
    else min_rect = (DRectangle)rect.clone();
  }

  /**
   * paints the DArea by a Graphics object
   *
   * @param g the java.awt.Graphics object
   */
  public void paint( Graphics g ){
    //System.out.println("DArea.paint(Graphics)");
    if( auto_focus )  {
      container.restore();
      visible_rect = (DRectangle)container.getRectangle().clone();
    }
    if( visible_rect.isEmpty() )  visible_rect = (DRectangle)min_rect.clone();
    super.paint( g );

    measures.setGraphics( g );
    if( grid.isVisible() && !grid_to_front ) paintGrid( measures );
    paintElements( measures );
    if( grid.isVisible() && grid_to_front ) paintGrid( measures );
  }

  /**
   * repaints a part of the visible area
   *
   * @param r the rectangle to repaint
   */
  public void repaint( DRectangle r ){
    //System.out.println("DArea.repaint(DRectangle)");
    if( r == null ) throw
      new IllegalArgumentException("Cannot repaint a null DRectangle");
    if( r.isAll() || auto_focus ) repaint();
    else{
      Point p1 = measures.getPoint( r.x, r.y ),
            p2 = measures.getPoint( r.x + r.width, r.y + r.height);
      super.repaint( p1.x, p2.y, p2.x - p1.x + 1, p1.y - p2.y + 1);
    }
  }

  /**
   * adds a new component to the area
   *
   * @param e the new DElement
   */
  public void addDElement( DElement e ){
    container.addDElement( e );
  }

  /**
   * removes a certain element from the area
   *
   * @param e the element to remove
   */
  public boolean removeDElement( DElement e ){
    return container.removeDElement( e );
  }

  /**
   * removes all elements from the area
   */
  public void removeAllDElements(){
    visible_rect = (DRectangle)min_rect.clone();
    container.removeAllDElements();
  }

  /**
   * sets the grid visible or not
   *
   * @param aFlag visible or not
   */
  public void setGridVisible( boolean aFlag ){
    grid.setVisible( aFlag );
  }

  /**
   * returns if the grid is visible
   * <code>true</code> if the grid is visible or <code>false</code> if not
   *
   * @return true or false
   */
  public boolean isGridVisible(){
    return grid.isVisible();
  }

  /**
   * sets the grid to the front
   * that means that the grid is painted as last element
   * default value is <code>false</code>
   *
   * @param aFlag grid t front or not
   */
  public void setGridToFront( boolean aFlag ){
    boolean old = grid_to_front;
    grid_to_front = aFlag;
    if( old != aFlag && grid.isVisible() ) repaint();
  }

  /**
   * sets the grid's horizontal and vertical distance
   * that means that the grid's lines will have these distances
   * in area coordinates
   *
   * @param hor_dist the horizontal distance
   * @param ver_dist the vertical distance
   */
  public void setGrid( double hor_dist, double ver_dist ){
    grid = new DGrid( visible_rect, hor_dist, ver_dist );
    grid.setDParent( this );
    auto_grid = false;
    repaint();
  }

  /**
   * sets tha auto grid on or off
   * if it's on, the grid's distances (@see #setGrid(double, double))
   * are automatically calculated that it looks pretty nice
   *
   * @param b auto grid on or not
   */
  public void setAutoGrid( boolean b ){
    if( b ) grid.setVisible( true );
    if( b == auto_grid ) return;
    auto_grid = b;
    repaint();
  }

  /**
   * returns if the auto grid is switched on
   *
   * @return true if the grid is on, else false
   */
  public boolean hasAutoGrid(){ return auto_grid; }

  /**
   * sets the color of the grid
   *
   * @param java.awt.Color
   */
  public void setGridColor( Color color ){
    grid.setColor( color );
  }

  /**
   * sets the maximal number of grid lines
   * default value is 10
   *
   * @param no maximal number of grid lines
   */
  public void setMaxGrid( int no ){
    if( no < 1 ) return;
    int old = max_grid;
    max_grid = no;
    if( old != no ) repaint();
  }

  /**
   * prints the area and it's content
   * @see java.awt.print.Printable and
   * @see java.awt.print.PrintJob
   *
   * @param g  the Graphics object
   * @param pf the @see java.awt.print.PageFormat
   * @param pi the page index
   *
   * @return int @see java.awt.print.Printable
   */
  public int print( Graphics g, PageFormat pf, int pi ){
    //System.out.println("DArea.print(...)");
    if( pi > 0 ) return Printable.NO_SUCH_PAGE;

    Border sb = getBorder();
    if( !(sb instanceof ScaledBorder) ) sb = null;
    else ( (ScaledBorder)sb ).show_outer_border = false;
    PagePrinter printer = new PagePrinter( this, g, pf );
    int ret = printer.print();
    if( sb != null ) ( (ScaledBorder)sb ).show_outer_border = true;
    return ret;
  }

  public void setXScale( DFunction x_s ){
    if( x_s == null && measures.x_scale == null ) return;
    measures.x_scale = x_s;
    repaint();
  }

  public void setYScale( DFunction y_s ){
    if( y_s == null && measures.y_scale == null ) return;
    measures.y_scale = y_s;
    repaint();
  }

  public DMeasures getDMeasures(){
    return measures;
  }

  /**
   * method paints the grid
   * how the method paints the grid depends on whether the area is wrapped in a
   * <code>ScaledBorder</code> or not and on the auto_grid option
   */
  private void paintGrid( DMeasures m ){
    //System.out.println("DArea.paintGrid(Measures)");
    grid.rectangle = visible_rect;
    if( auto_grid ){
      Border border = getBorder();
      if( border instanceof ScaledBorder ){
        ScaledBorder sb = (ScaledBorder)border;
        FontMetrics fm = m.getGraphics().getFontMetrics();
        Dimension d = getSize();
        DRectangle r = measures.getSourceOf( visible_rect );
        grid.hor_dist = sb.getSrcdX(fm, d, r.x, r.x + r.width);
        grid.ver_dist = sb.getSrcdY(fm, d, r.y, r.y + r.height);
      }
      else{
        grid.hor_dist = ScaledBorder.aBitBigger( visible_rect.width / max_grid );
        grid.ver_dist = ScaledBorder.aBitBigger( visible_rect.height / max_grid );
      }
    }
    grid.paint( m );
  }

  /**
   * this method asks the elements to paint themselves in the area if they
   * really lie in it and if they are visible
   */
  private void paintElements( DMeasures m ){
    //System.out.println("DArea.paintElements(Measures)");
    for( int i=0; i<container.elements.size(); i++ ){
      DElement e = (DElement)container.elements.get(i);
      if( e.isVisible() && visible_rect.getIntersection( e.getRectangle() ) != null )
        e.paint( m );
    }
  }
}
