/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.display;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;

/**
 * Draws a coil spring in a drawing panel.
 * @author F. Esquembre
 * @author W. Christian
 * @version 1.0
 */
public class Spring implements Measurable {
  private GeneralPath springPath = new GeneralPath();
  // Configuration variables
  protected boolean thinExtremes = true;
  protected boolean visible = true;
  protected int loops = -1, pointsPerLoop = -1;            // -1 to make sure the arrays are allocated
  protected float x = 0.0f, y = 0.0f;
  protected float sizex = 0.1f, sizey = 0.0f;

  /**
   * The radius of the spring (normal to its direction)
   */
  protected float radius = 0.1f;
  protected float solenoid = 0.0f;
  protected Color edgeColor = Color.BLACK;
  protected Stroke edgeStroke = new BasicStroke(1.0f);
  // Implementation variables
  protected boolean hasChanged = true, zeroLength = false; // Whether the element should recompute data that depends on position, size, scale, resolution, ...
  private int segments = 0;
  private float xPoints[] = null, yPoints[] = null;

  /**
   * Constructs a 0.1 radius Spring.
   */
  public Spring() {
    this(0.1);
  }

  /**
   * Special constructor that allows to specify the radius of the spring
   * @param _radius the radius of the spring (normal to its direction)
   */
  public Spring(double _radius) {
    setRadius(_radius);
    setResolution(8, 15);
  }

  // -------------------------------------
  // Configuration methods
  // -------------------------------------

  /**
   * Sets the X position of the origin of the spring
   * @param x double
   */
  public void setX(double x) {
    this.x = (float) x;
    hasChanged = true;
  }

  /**
   * Gets the X position of the origin of the spring
   * @return double
   */
  public double getX() {
    return this.x;
  }

  /**
   * Sets the Y position of the origin of the spring
   * @param y double
   */
  public void setY(double y) {
    this.y = (float) y;
    hasChanged = true;
  }

  /**
   * Gets the Y position of the origin of the spring
   * @return double
   */
  public double getY() {
    return this.y;
  }

  /**
   * Sets the position of the origin of the spring
   * @param x double
   * @param y double
   */
  public void setXY(double x, double y) {
    this.x = (float) x;
    this.y = (float) y;
    hasChanged = true;
  }

  /**
   * Sets the X size of the spring
   * @param sizeX double
   */
  public void setSizeX(double sizeX) {
    this.sizex = (float) sizeX;
    hasChanged = true;
  }

  /**
   * Gets the X size of the spring
   * @return double
   */
  public double getSizeX() {
    return this.sizex;
  }

  /**
   * Sets the Y size of the spring
   * @param sizeY double
   */
  public void setSizeY(double sizeY) {
    this.sizey = (float) sizeY;
    hasChanged = true;
  }

  /**
   * Gets the Y size of the spring
   * @return double
   */
  public double getSizeY() {
    return this.sizey;
  }

  /**
   * Sets the size of the spring
   * @param sizeX double
   * @param sizeY double
   */
  public void setSizeXY(double sizeX, double sizeY) {
    this.sizex = (float) sizeX;
    this.sizey = (float) sizeY;
    hasChanged = true;
  }

  /**
   * Set the radius of the spring.
   * @param _radius the radius of the spring (normal to its direction)
   */
  public void setRadius(double radius) {
    this.radius = (float) radius;
    hasChanged = true;
  }

  /**
   * Get the radius of the spring.
   */
  public double getRadius() {
    return this.radius;
  }

  /**
   * Sets the visibiliby of the spring
   * @param _visible boolean
   */
  public void setVisible(boolean visible) {
    this.visible = visible;
  }

  public boolean isVisible() {
    return this.visible;
  }

  public void setEdgeColor(Color color) {
    this.edgeColor = color;
  }

  public Color getEdgeColor() {
    return this.edgeColor;
  }

  public void setEdgeStroke(Stroke stroke) {
    this.edgeStroke = stroke;
  }

  public Stroke getEdgeStroke() {
    return this.edgeStroke;
  }

  /**
   * Sets the number of spires and points per spire used to draw the spring
   * @param nLoops int
   * @param nPointsPerLoop int
   */
  public void setResolution(int nLoops, int nPointsPerLoop) {
    if((nLoops==loops)&&(nPointsPerLoop==pointsPerLoop)) {
      return; // No need to reallocate arrays
    }
    this.loops = nLoops;
    this.pointsPerLoop = nPointsPerLoop;
    segments = loops*pointsPerLoop;
    int n = segments+1;
    xPoints = new float[n];
    yPoints = new float[n];
    hasChanged = true;
  }

  public int getLoops() {
    return this.loops;
  }

  public int getPointsPerLoop() {
    return this.pointsPerLoop;
  }

  /**
   * Sets a double factor that makes the spring look like a solenoid by
   * causing the spires to go back and forth
   * Default is 0, which makes a standard spring
   * @param factor double
   */
  public void setSolenoid(double factor) {
    solenoid = (float) factor;
    hasChanged = true;
  }

  /**
   * Whether the spring should show thin extremes. Default is true
   * @param thin boolean
   */
  public void setThinExtremes(boolean thin) {
    thinExtremes = thin;
    hasChanged = true;
  }

  /**
   * Implementation of Drawable.
   * @param panel DrawingPanel
   * @param g Graphics
   */
  public void draw(DrawingPanel panel, Graphics g) {
    if(!visible) {
      return;
    }
    if(hasChanged) {
      computePoints();
    }
    Graphics2D g2 = (Graphics2D) g;
    g2.setStroke(edgeStroke);
    g2.setColor(edgeColor);
    if(zeroLength) {
      int a = panel.xToPix(x), b = panel.yToPix(y);
      g2.drawLine(a, b, a, b);
      return;
    }
    Shape s = springPath.createTransformedShape(panel.getPixelTransform());
    g2.draw(s);
  }

  // -------------------------------------
  // Implementation of Measured3D
  // -------------------------------------
  public boolean isMeasured() {
    return visible;
  }

  public double getXMin() {
    return(sizex>0) ? x : x+sizex;
  }

  public double getXMax() {
    return(sizex>0) ? x+sizex : x;
  }

  public double getYMin() {
    return(sizey>0) ? y : y+sizey;
  }

  public double getYMax() {
    return(sizey>0) ? y+sizey : y;
  }

  // -------------------------------------
  //  Private methods
  // -------------------------------------
  private void computeGeneralPath() {
    if((xPoints==null)||(xPoints.length<2)) {
      return;
    }
    int n = xPoints.length;
    springPath = new GeneralPath();
    springPath.moveTo(xPoints[0], yPoints[0]);
    for(int i = 1; i<n; i++) {
      springPath.lineTo(xPoints[i], yPoints[i]);
    }
  }

  private void computePoints() {
    float length = sizex*sizex+sizey*sizey;
    if(length==0) {
      zeroLength = true;
      return;
    }
    zeroLength = false;
    length = (float) Math.sqrt(length);
    float u2x = -sizey/length, u2y = sizex/length;
    float delta = (float) (2.0f*Math.PI/pointsPerLoop);
    if(radius<0) {
      delta *= -1;
    }
    int pre = pointsPerLoop/2;
    for(int i = 0; i<=segments; i++) {
      int k;
      if(thinExtremes) {
        if(i<pre) {
          k = 0;
        } else if(i<pointsPerLoop) {
          k = i-pre;
        } else if(i>(segments-pre)) {
          k = 0;
        } else if(i>(segments-pointsPerLoop)) {
          k = segments-i-pre;
        } else {
          k = pre;
        }
      } else {
        k = pre;
      }
      float angle = (float) (Math.PI/2+i*delta);
      float cos = (float) Math.cos(angle); //, sin = Math.sin(angle);
      xPoints[i] = (x+i*sizex/segments+k*radius*cos*u2x/pre);
      yPoints[i] = (y+i*sizey/segments+k*radius*cos*u2y/pre);
      if(solenoid!=0.0) {
        double cte = k*Math.cos(i*2*Math.PI/pointsPerLoop)/pre;
        xPoints[i] += solenoid*cte*sizex;
        yPoints[i] += solenoid*cte*sizey;
      }
    }
    computeGeneralPath();
    hasChanged = false;
  }

}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
