/* @(#)GameShapes.java  10 November 2006 */

/* Useful imports */

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.codec.*;
import edu.neu.ccs.console.*;
import edu.neu.ccs.filter.*;
import edu.neu.ccs.jpf.*;
import edu.neu.ccs.parser.*;
import edu.neu.ccs.pedagogy.*;
import edu.neu.ccs.quick.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.border.*;
import java.io.*;
import java.util.*;
import java.math.*;
import java.beans.*;
import java.lang.reflect.*;
import java.net.URL;
import java.util.regex.*;
import java.text.ParseException;

/**
 * <p>Class <code>GameShapes</code> is a special purpose class
 * for the construction of shapes and corresponding paintables.</p>
 * 
 * <p>The basic methods are quite general.  The specific methods
 * are designed for use in the Concentration game.</p>
 */

public class GameShapes {

    /** Prevent instantiation. */
    private GameShapes() { }
    
    
    /** The tile size. */
    private static final int edge = 100;
    
    /** The tile bounds. */
    private static XRect bounds = new XRect(0, 0, edge, edge);
    
    
    /**
     * <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 be at
     * least 3.</p>
     *
     * <p>If skip is zero or divisible by vertices
     * then skip is forced to 1.</p>
     *
     * <p>If vertices is even
     * and skip equals vertices/2
     * then skip is forced to 1.</p>
     */
    public static Shape regularPolygon
        (double centerX,
         double centerY,
         double radius,
         double start,
         int vertices,
         int skip)
    {
        vertices = Math.abs(vertices);
        
        if (vertices < 3)
            vertices = 3;
        
        skip = Math.abs(skip);
        
        skip %= vertices;
        
        int half = vertices / 2;
        
        if (skip > half)
            skip = vertices - skip;
        
        if (skip == 0)
            skip = 1;

        if ((vertices % 2) == 0)
            if (skip == half)
                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 PolygonShape(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 be at
     * least 3.</p>
     */
    public static Shape regularWavygon
        (double centerX,
         double centerY,
         double radius,
         double start,
         int vertices)
    {
        vertices = Math.abs(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 AutomaticCurve
            (data, Tangent.chordStrategy(1.0f / 4.0f));
    }
    
    
    /**
     * <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 be at
     * least 1.</p>
     */
    public static Shape regularSpokes
        (double centerX,
         double centerY,
         double radius,
         double start,
         int vertices)
    {
        vertices = Math.abs(vertices);
        
        if (vertices < 1)
            vertices = 1;
        
        Shape[] shapes = new Shape[vertices];
        
        double degrees = start;
        double delta = 360.0 / vertices;
        
        float[] vertex;
        
        for (int i = 0; i < vertices; i++) {
            vertex = makeVertex(centerX, centerY, radius, degrees);
            
            shapes[i] = new XLine2D
                (centerX, centerY, vertex[0], vertex[1]);
            
            degrees += delta;
        }
        
        return Path.append(null, shapes, false);
    }
    
    
    /** <p>Returns the filled game shapes.</p> */
    public static Shape[] getFilledGameShapes() {
        return new Shape[] {
            new XRect(4, 4, 92, 92),
            new XOval(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 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 paintables that combine the game shapes
     * and game colors.</p>
     */
    public static ShapePaintable[] makeShapePaintables() {
        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;
    }
    
}
