// ShapeOutline.java

import java.awt.Graphics;
import java.util.StringTokenizer;

/**
 * Class of objects representing shapes that can be
 * drawn and their area calculated.
 *
 * @author D. Nixon
 */
public abstract class ShapeOutline {

   /** Width of shape in pixels */
   private int width;

   /** Count of ShapeOutline objects instantiated so far */
   private static int objectCount = 0;

   /**
    * Creates a ShapeOutline with the specified width.
    *
    * @param w integer specifying width in pixels
    * @exception NegativeSizeException if
    *            <code>w</code> < 0.
    */
   public ShapeOutline( int w )
   {
      if ( w < 0 )
         throw new NegativeSizeException(
                 getClass().getName()
                 + " width " + w + " must be >= 0.");
      width = w;

      //  So that getCount will know how many
      //  ShapeOutline objects have been created:
      objectCount++;
   }  // constructor ShapeOutline(int)

   /**
    * Gets this ShapeOutline's width in pixels
    *
    * @return integer width
    */
   public int getWidth()  { return width; }

   /**
    * Draws this ShapeOutline at a specified location
    * using a specified Graphics context, and using
    * the Graphics context's pre-existing Color.
    *
    * @param g the Graphics context used
    * @param pixelsFromLeft integer distance from left
    *        side of display area to left side of
    *        drawing of this ShapeOutline
    * @param pixelsFromTop integer distance from top
    *        of display area to top of drawing of this
    *        ShapeOutline
    */
   public abstract void draw( Graphics g,
                              int pixelsFromLeft,
                              int pixelsFromTop );

   /**
    * Determines approximate number of pixels
    * enclosed by this ShapeOutline
    *
    * @return floating-point area of this
    * ShapeOutline in pixels
    */
   public abstract float getArea();

   /**
    * Determines whether this ShapeOutline
    * is equal to another object.
    *
    * @param other the other object.
    * @return <code>true</code> if <code>other</code>
    *         is of the same class and has the same
    *         width as this ShapeOutline,
    *         <code>false</code> otherwise.
    */
   public boolean equals(Object other)
   {
      return ( other != null
                    && getClass() == other.getClass()
                    && width == ((ShapeOutline) other).width );
   }  // method equals

   /**
    * Returns a brief string describing
    * this ShapeOutline, stating its class,
    * width, and other dimension if any.
    */
   public String toString()
   {
      return (getClass().getName() + " width=" + width);
   }  // method toString

   /**
    * Returns a full sentence describing this
    * ShapeOutline, stating its class, width,
    * other dimension, if any, and area.
    * (Subclasses should override
    * <code>secondDimensionDescriptor</code>
    * rather than this method.)
    */
   public final String toDescriptorString()
   {
       return("This " + getClass().getName()
              + " has width " + width
              + secondDimensionDescriptor()
              + " and area " + getArea() + ".");
   }  // method toDescriptorString()

   /**
    * Returns text to be inserted after "width"
    * in the string returned by
    * <code>toDescriptorString</code>.
    * Empty by default.
    * Should be overridden for a ShapeOutline
    * with additional dimensions.
    */
   protected String secondDimensionDescriptor()  { return ""; }

   /**
    * Gets a count of ShapeOutline objects instantiated so far.
    *
    * @return integer number of ShapeOutline objects
    */
   public static int getCount()
   {
       return objectCount;
   }  // method getCount()

   /**
    * Creates a ShapeOutline object specified by the
    *         parameter string <code>line</code>
    *
    * @param line string with a format consistent with the
    *             text returned by the toString method of
    *             at least one ShapeOutline subclass.
    *
    * @return a ShapeOutline object containing the data
    *             represented by <code>line</code>
    *
    * @exception ShapeTextFormatException if <code>line</code>
    *               is not a valid string representation of
    *               a ShapeOutline object
    */
   public static ShapeOutline parseShape(String line)
   {
      String exceptionText = line + " <--- ";

      StringTokenizer st = new StringTokenizer(line);
      if ( ! st.hasMoreTokens() )
         throw new ShapeTextFormatException(exceptionText
                                            + "(empty line)");

      String className = st.nextToken();
      if ( ! st.hasMoreTokens() )
         throw new ShapeTextFormatException(exceptionText
                                            + "no dimensions");

      String widthText = st.nextToken();
      StringTokenizer widthTokens
                        = new StringTokenizer(widthText, "=");
      if ( (! widthTokens.hasMoreTokens() )
               || (! widthTokens.nextToken().equals("width") )
               || (! widthTokens.hasMoreTokens() ) )
         throw new ShapeTextFormatException(
                                  exceptionText
                                  + "missing or invalid width text");

      try {
         int width = Integer.parseInt(widthTokens.nextToken());

         String secondDimensionName = "";
         int secondDimensionValue = 0;

         if ( st.hasMoreTokens() )
         {
            String secondDimensionText = st.nextToken();
            StringTokenizer dimensionTokens
                        = new StringTokenizer(secondDimensionText, "=");

            if ( ! dimensionTokens.hasMoreTokens() )
               throw new ShapeTextFormatException(
                                       exceptionText
                                       + "invalid dimension format: "
                                       + secondDimensionText);
            secondDimensionName = dimensionTokens.nextToken();

            if ( ! dimensionTokens.hasMoreTokens() )
               throw new ShapeTextFormatException(
                                       exceptionText
                                       + "invalid dimension format: "
                                       + secondDimensionText);
            secondDimensionValue
                      = Integer.parseInt(dimensionTokens.nextToken());
         }  // if

         if ( className.equals("RightTriangle") )
         {
            if ( ! (secondDimensionName.equals("height")) )
               throw new ShapeTextFormatException(
                                            exceptionText
                                            + "Second dimension "
                                            + secondDimensionName
                                            + " should be height");
            return new RightTriangle(width, secondDimensionValue);
         }
         else if ( className.equals("Circle") )
            return new Circle(width);
         else if ( className.equals("Wheel") )
         {
            if ( ! (secondDimensionName.equals("spokes")) )
               throw new ShapeTextFormatException(
                                            exceptionText
                                            + "Second dimension "
                                            + secondDimensionName
                                            + " should be spokes");
            return new Wheel(width, secondDimensionValue);
         }
         else
            throw new ShapeTextFormatException(
                                exceptionText + className
                                + " not a known ShapeOutline subclass.");
      }  // try
      catch (NumberFormatException nfe) {
         throw new ShapeTextFormatException(exceptionText
                                            + nfe.getMessage());
      }  // catch
      catch (NegativeSizeException nse) {
         throw new ShapeTextFormatException(exceptionText
                                            + nse.getMessage());
      }  // catch
   }  // method parseShape
}  // class ShapeOutline