/* @(#)GameShapes.java 1.0  12 October 2004 */

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.geom.*;

/**
 * <p>Class <code>GameShapes</code> is a special purpose class for the
 * construction of shapes and for images based on shapes.</p>
 */

public class GameShapes {

    /** Prevent instantiation. */
    private GameShapes() { }
    
    
    /**
     * <p>Returns a polygon vertex as a <code>float[]</code> for use in
     * the automatic shape tools.</p>
     *
     * <p>The angle is measured clockwise from the vertical (north).</p>
     */
    public static float[] makeVertex
        (double centerX, double centerY, double radius, double degrees)
    {
        double s = MathUtilities.sindeg(degrees);
        double c = MathUtilities.cosdeg(degrees);
        
        double x = centerX + radius * s;
        double y = centerY - radius * c;
        
        return new float[] { (float) x, (float) y };
    }
    
    
    /**
     * <p>Returns the regular polygon shape with the given number of vertices,
     * the given skip between vertices, and the given start angle in degrees
     * (from the vertical).</p>
     *
     * <p>The value of vertices is forced to at least 3.</p>
     *
     * <p>If skip is zero or divisible by vertices then skip is forced to one.</p>
     *
     * <p>If vertices is even and skip equals vertices/2 then skip is forced
     * to one.</p>
     */
    public static Shape regularPolygon
        (double centerX, double centerY, double radius, double start, int vertices, int skip)
    {
        if (vertices < 3)
            vertices = 3;
        
        skip %= vertices;
        
        if (skip < 0)
            skip += vertices;
        
        if (skip == 0)
            skip = 1;
        else
        if ((vertices % 2) == 0)
            if (skip == (vertices / 2))
                skip = 1;
        
        int gcd = MathUtilities.GCD(vertices, skip);
        
        if (gcd == 1)
            return regularPolygonHelper(centerX, centerY, radius, start, vertices, skip);
        
        Shape[] shapes = new Shape[gcd];
        
        double delta = 360.0 / vertices;
        
        vertices /= gcd;
        skip /= gcd;
        
        for (int i = 0; i < gcd; i++) {
            shapes[i] = regularPolygonHelper(centerX, centerY, radius, start, vertices, skip);
            start += delta;
        }
        
        return Path.append(null, shapes, false);
    }
    
    
    /**
     * <p>Returns the regular polygon shape with the given number of vertices
     * the given skip between vertices, and the given start angle in degrees
     * (from the vertical) under the assumption gcd(vertices, skip) = 1.</p>
     */
    private static Shape regularPolygonHelper
        (double centerX, double centerY, double radius, double start, int vertices, int skip)
    {
        float[][] data = new float[vertices][2];
        
        double degrees = start;
        double delta = (360.0 * skip) / vertices;
        
        for (int i = 0; i < vertices; i++) {
            data[i] = makeVertex(centerX, centerY, radius, degrees);
            degrees += delta;
        }
        
        return new AutomaticShape(data);
    }
    
    
    /**
     * <p>Returns the regular wavygon shape with the given number of outer and inner
     * vertices and the given start angle in degrees (from the vertical).</p>
     *
     * <p>A wavygon is defined as a closed curve with the given number of vertices
     * on a circle of the given radius and an equal number of vertices mixed in on
     * a circle of half of the given radius.  This makes a wavygon a curved star.</p>
     *
     * <p>The value of vertices is forced to at least 3.</p>
     */
    public static Shape regularWavygon
        (double centerX, double centerY, double radius, double start, int vertices)
    {
        if (vertices < 3)
            vertices = 3;
        
        float[][] data = new float[2 * vertices][2];
        
        double degrees = start;
        double delta = 180.0 / vertices;
        double innerradius = radius / 2;
        
        int j = 0;
        
        for (int i = 0; i < vertices; i++) {
            data[j] = makeVertex(centerX, centerY, radius,      degrees);
            degrees += delta;
            
            j++;
            
            data[j] = makeVertex(centerX, centerY, innerradius, degrees);
            degrees += delta;
            
            j++;
        }
        
        return new AutomaticShape
            (data, null, Tangent.chordStrategy(1.0f / 4.0f), Path.BEZIER_CUBIC);
    }
    
    
    /**
     * <p>Returns a shape consisting of radial spokes, one for each of the given
     * number of vertices; the spokes begin at the given start angle.</p>
     *
     * <p>The value of vertices is forced to at least 1.</p>
     */
    public static Shape regularSpokes
        (double centerX, double centerY, double radius, double start, int vertices)
    {
        if (vertices < 1)
            vertices = 1;
        
        Shape[] shapes = new Shape[vertices];
        
        double degrees = start;
        double delta = 360.0 / vertices;
        
        for (int i = 0; i < vertices; i++) {
            float[] vertex = makeVertex(centerX, centerY, radius, degrees);
            shapes[i] = new Line2D.Double(centerX, centerY, vertex[0], vertex[1]);
            degrees += delta;
        }
        
        return Path.append(null, shapes, false);
    }
    
    
    /** <p>Returns the colors used for the game shapes.</p> */
    public static Color[] getGameColors() {
        return new Color[] {
            Colors.red,
            Colors.green,
            Colors.black,
            Colors.blue
        };
    }
    
    
    /** <p>Returns the filled game shapes.</p> */
    public static Shape[] getFilledGameShapes() {
        return new Shape[] {
            new Ellipse2D.Double  (4, 4, 92, 92),
            new Rectangle2D.Double(4, 4, 92, 92),
            regularPolygon(50, 50, 48, 0, 3, 1),
            regularPolygon(50, 50, 48, 0, 4, 1),
            regularPolygon(50, 50, 48, 0, 5, 1),
            regularPolygon(50, 50, 48, 0, 6, 1),
            regularPolygon(50, 50, 48, 0, 5, 2),
            regularPolygon(50, 50, 48, 0, 6, 2),
            regularPolygon(50, 50, 48, 0, 7, 3),
            regularPolygon(50, 50, 48, 0, 8, 3),
            regularWavygon(50, 50, 48, 0, 3),
            regularWavygon(50, 50, 48, 0, 4),
            regularWavygon(50, 50, 48, 0, 5),
            regularWavygon(50, 50, 48, 0, 6),
            regularWavygon(50, 50, 48, 0, 7),
            regularWavygon(50, 50, 48, 0, 8)
        };
    }
    
    
    /** <p>Returns the stroke game shapes.</p> */
    public static Shape[] getStrokeGameShapes() {
        return new Shape[] {
            regularSpokes(50, 50, 43, 0, 5),
            regularSpokes(50, 50, 43, 0, 6),
            regularSpokes(50, 50, 43, 0, 7)
        };
    }
    
    
    /** <p>Returns the paintables that combine the game shapes and game colors.</p> */
    public static ShapePaintable[] getGameShapePaintables() {
        Rectangle2D bounds =
            new Rectangle2D.Double(0, 0, 100, 100);
        
        Stroke stroke = 
            new BasicStroke(10, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
        
        Shape[] filledshapes = getFilledGameShapes();
        Shape[] strokeshapes = getStrokeGameShapes();
        Color[] colors = getGameColors();
        
        int f = filledshapes.length;
        int s = strokeshapes.length;
        int c = colors.length;
        
        int t = f + s;
        
        ShapePaintable[] paintables = new ShapePaintable[t * c];
        
        for (int i = 0; i < t; i++)
            for (int j = 0; j < c; j++) {
                int k = c * i + j;
                
                if (i < f)
                    paintables[k] = new ShapePaintable
                        (filledshapes[i], PaintMode.FILL, colors[j]);
                else
                    paintables[k] = new ShapePaintable
                        (strokeshapes[i - f], PaintMode.DRAW, null, colors[j], stroke);
                
                paintables[k].setDefaultBounds2D(bounds);
            }
        
        return paintables;
    }
    
    
    /** <p>Returns the images constructed from the game shapes and game colors.</p> */
    public static Image[] getGameShapeImages() {
        ShapePaintable[] paintables = getGameShapePaintables();
        
        int n = paintables.length;
        
        Image[] images = new Image[n];
        
        for (int i = 0; i < n; i++)
            images[i] = PaintableTools.makeBufferedImage(paintables[i]);
        
        return images;
    }
}