import ascb.drawing.FillMatrix; /** Use the Pen class as an abstracted drawing utility without having to use the MovieClip drawing methods directly. */ class ascb.drawing.Pen { private var _mTarget:MovieClip; private var _bLineStyleSet:Boolean; /** * Get or set the target movie clip. You can also set the target * movie clip when calling the constructor. When calling the * constructor you can also have the Pen automatically create a * new empty movie clip in a parent. * @example *
   *  import ascb.drawing.Pen;
   *  
   *  // Have the Pen create a new empty movie clip in this.
   *  var pCircle:Pen = new Pen(this, true);
   *  pCircle.drawCircle(10);
   *  trace(pCircle.target);
   */
  public function set target(mTarget:MovieClip):Void {
    _mTarget = mTarget;
  }

  public function get target():MovieClip {
    return _mTarget;
  }

  function Pen(mTarget:MovieClip, bMakeNew:Boolean) {
    if(bMakeNew) {
      var nDepth:Number = mTarget.getNextHighestDepth();
      mTarget = mTarget.createEmptyMovieClip("__PenClip" + nDepth, nDepth);
    }
    _mTarget = mTarget;
  }

  /**
   *  The lineStyle() method is optional. The Pen class uses a default
   *  line style of you don't call the method.
   *  @param  thickness   (optional) The point thickness of the line (default 0)
   *  @param  rgb         (optional) The RGB value of the line (default 0)
   *  @param  alpha       (optional) The alpha of the line (default 100)
   */
  public function lineStyle(nThickness:Number, nRGB:Number, nAlpha:Number):Void {
    nThickness = (nThickness == undefined) ? 0 : nThickness;
    nRGB = (nRGB == undefined) ? 0 : nRGB;
    nAlpha = (nAlpha == undefined) ? 100 : nAlpha;
    _mTarget.lineStyle(nThickness, nRGB, nAlpha);
    _bLineStyleSet = true;
  }

  public function beginFill(nRGB:Number, nAlpha:Number):Void {
    nAlpha = (nAlpha == undefined) ? 100 : nAlpha;
trace("fill alpha - " + nAlpha);
    _mTarget.beginFill(nRGB, nAlpha);
  }

  public function beginGradientFill(sFillType:String, aColors:Array, nAlphas:Array, nRatios:Array, fmMatrix:Object):Void {
    _mTarget.beginGradientFill(sFillType, aColors, nAlphas, nRatios, fmMatrix);
  }

  public function endFill():Void {
    _mTarget.endFill();
  }

  public function clear():Void {
    _mTarget.clear();
    _bLineStyleSet = false;
  }

  public function moveTo(nX:Number, nY:Number):Void {
    _mTarget.moveTo(nX, nY);
  }

  public function lineTo():Void {
    // Make sure the line style is set. Otherwise, use the default values.
    if(!_bLineStyleSet) {
      lineStyle(0, 0, 100);
    }
    _mTarget.lineTo(arguments[0], arguments[1]);
  }

  public function curveTo(nCtrlX:Number, nCtrlY:Number, nAnchorX:Number, nAnchorY:Number):Void {
    // Make sure the line style is set. Otherwise, use the default values.
    if(!_bLineStyleSet) {
      lineStyle(0, 0, 100);
    }
    _mTarget.curveTo(nCtrlX, nCtrlY, nAnchorX, nAnchorY);
  }

  /**
   *  Draw a line from one point to another.
   */
  public function drawLine(nX0:Number, nY0:Number, nX1:Number, nY1:Number):Void {
    // Make sure the line style is set. Otherwise, use the default values.
    if(!_bLineStyleSet) {
      lineStyle(0, 0, 100);
    }
    _mTarget.moveTo(nX0, nY0);
    _mTarget.lineTo(nX1, nY1);
  }

  /**
   *  Draw a curve from one point to another.
   */
  public function drawCurve(nX:Number, nY:Number, nCtrlX:Number, nCtrlY:Number, nAnchorX:Number, nAnchorY:Number):Void {
    // Make sure the line style is set. Otherwise, use the default values.
    if(!_bLineStyleSet) {
      lineStyle(0, 0, 100);
    }
    _mTarget.moveTo(nX, nY);
    _mTarget.curveTo(nCtrlX, nCtrlY, nAnchorX, nAnchorY);
  }

  /**
   *  Draw a rectangle.
   *  @param  width    The width of the rectangle
   *  @param  height   The height of the rectangle
   *  @param  round    (optional) The number of pixels to round the corners
   *  @param  rotation (optional) The amount to rotate the rectangle in degrees
   *  @param  x        (optional) The x coordinate at which to draw the rectangle
   *  @param  y        (optional) The y coordinate at which to draw the rectangle
   *  @param  align    (optional) The alignment of the rectangle relative to the x and y
   *                   coordinates. Can be upperleft, upperright, left, right, upper, lower,
   *                   lowerleft, lowerright.
   */
  public function drawRectangle (nWidth:Number, nHeight:Number, nRound:Number, nRotation:Number, nX:Number, nY:Number, sAlign:String):Void {

    nRound = (nRound == undefined) ? 0 : nRound;

    nRotation = (nRotation == undefined) ? 0 : nRotation;

    nX = (nX == undefined) ? 0 : nX;

    nY = (nY == undefined) ? 0 : nY;

    switch(sAlign) {
      case "upperleft":
        nX += nWidth/2;
        nY += nHeight/2;
        break;
      case "upperright":
        nX += -nWidth/2;
        nY += nHeight/2;
        break;
      case "left":
        nX += nWidth/2;
        break;
      case "right":
        nX += -nWidth/2;
        break;
      case "upper":
        nY += nHeight/2;
        break;
      case "lower":
        nY += -nHeight/2;
        break;
      case "lowerleft":
        nX += nWidth/2;
        nY += -nHeight/2;
        break;
      case "lowerright":
        nX += -nWidth/2;
        nY += -nHeight/2;
        break;
    }

    // Make sure the rectangle is at least as wide and tall as the rounded corners
    if (nWidth < (nRound * 2)) {
      nWidth = nRound * 2;
    }
    if (nHeight < (nRound * 2)) {
      nHeight = nRound * 2;
    }

    // Convert the nRotation from degrees to radians
    nRotation = nRotation * Math.PI / 180;

    // Calculate the distance from the rectangle's center to one of the corners
    // (or where the corner would be in rounded-cornered rectangles).
    // See the line labeled r in Figure 4-2.
    var nR:Number = Math.sqrt(Math.pow(nWidth/2, 2) + Math.pow(nHeight/2, 2));

    // Calculate the distance from the rectangle's center to the upper edge of
    // the bottom-right rounded corner. See the line labeled rx in Figure 4-2. 
    // When round is 0, rx is equal to r.
    var nRx:Number = Math.sqrt(Math.pow(nWidth/2, 2) + Math.pow((nHeight/2) - nRound, 2));

    // Calculate the distance from the rectangle's center to the lower edge of
    // the bottom-right rounded corner. See the line labeled ry in Figure 4-2.
    // When round is 0, ry is equal to r.
    var nRy:Number = Math.sqrt(Math.pow((nWidth/2) - nRound, 2) + Math.pow(nHeight/2, 2));

    // Calculate angles. nR1Angle is the angle between the X axis that runs through
    // the center of the rectangle and the line rx. nR2Angle is the angle between rx
    // and r. nR3Angle is the angle between r and ry. And nR4Angle is the angle
    // between ry and the Y axis that runs through the center of the rectangle.
    var nR1Angle:Number = Math.atan( ((nHeight/2) - nRound) /( nWidth/2) );
    var nR2Angle:Number = Math.atan( (nHeight/2) / (nWidth/2) ) - nR1Angle;
    var nR4Angle:Number = Math.atan( ((nWidth/2) - nRound) / (nHeight/2) );
    var nR3Angle:Number = (Math.PI/2) - nR1Angle - nR2Angle - nR4Angle;

    // Calculate the distance of the control point from the arc center for the
    // rounded corners.
    var nCtrlDist:Number = Math.sqrt(2 * Math.pow(nRound, 2));

    // Declare the local variables used to calculate the control point.
    var nCtrlX:Number;
    var nCtrlY:Number;

    // Calculate where to begin drawing the first side segment, and then draw it.
    nRotation += nR1Angle + nR2Angle + nR3Angle;
    var nX1:Number = nX + nRy * Math.cos(nRotation);
    var nY1:Number = nY + nRy * Math.sin(nRotation);
    moveTo(nX1, nY1);
    nRotation += 2 * nR4Angle;
    nX1 = nX + nRy * Math.cos(nRotation);
    nY1 = nY + nRy * Math.sin(nRotation);
    lineTo(nX1, nY1);

    // Set nRotation to the starting point for the next side segment and calculate
    // the x and y coordinates.
    nRotation += nR3Angle + nR2Angle;
    nX1 = nX + nRx * Math.cos(nRotation);
    nY1 = nY + nRx * Math.sin(nRotation);

    // If the corners are rounded, calculate the control point for the corner's
    // curve and draw it.
    if (nRound > 0) {
      nCtrlX = nX + nR * Math.cos(nRotation - nR2Angle);
      nCtrlY = nY + nR * Math.sin(nRotation - nR2Angle);
      curveTo(nCtrlX, nCtrlY, nX1, nY1);
    }

    // Calculate the end point of the second side segment and draw the line.
    nRotation += 2 * nR1Angle;
    nX1 = nX + nRx * Math.cos(nRotation);
    nY1 = nY + nRx * Math.sin(nRotation);
    lineTo(nX1, nY1);

    // Calculate the next line segment's starting point.
    nRotation += nR2Angle + nR3Angle;
    nX1 = nX + nRy * Math.cos(nRotation);
    nY1 = nY + nRy * Math.sin(nRotation);

    // Draw the rounded corner, if applicable.
    if (nRound > 0) {
      nCtrlX = nX + nR * Math.cos(nRotation - nR3Angle);
      nCtrlY = nY + nR * Math.sin(nRotation - nR3Angle);
      curveTo(nCtrlX, nCtrlY, nX1, nY1);
    }

    // Calculate the end point of the third segment and draw the line.
    nRotation += 2 * nR4Angle;
    nX1 = nX + nRy * Math.cos(nRotation);
    nY1 = nY + nRy * Math.sin(nRotation);
    lineTo(nX1, nY1);

    // Calculate the starting point of the next segment.
    nRotation += nR3Angle + nR2Angle;
    nX1 = nX + nRx * Math.cos(nRotation);
    nY1 = nY + nRx * Math.sin(nRotation);

    // If applicable, draw the rounded corner.
    if (nRound > 0) {
      nCtrlX = nX + nR * Math.cos(nRotation - nR2Angle);
      nCtrlY = nY + nR * Math.sin(nRotation - nR2Angle);
      curveTo(nCtrlX, nCtrlY, nX1, nY1);
    }

    // Calculate the end point for the fourth segment, and draw it.
    nRotation += 2 * nR1Angle;
    nX1 = nX + nRx * Math.cos(nRotation);
    nY1 = nY + nRx * Math.sin(nRotation);
    lineTo(nX1, nY1);

    // Calculate the end point for the next corner arc, and if applicable, draw it.
    nRotation += nR3Angle + nR2Angle;
    nX1 = nX + nRy * Math.cos(nRotation);
    nY1 = nY + nRy * Math.sin(nRotation);
    if (nRound > 0) {
      nCtrlX = nX + nR * Math.cos(nRotation - nR3Angle);
      nCtrlY = nY + nR * Math.sin(nRotation - nR3Angle);
      curveTo(nCtrlX, nCtrlY, nX1, nY1);
    }
  }

  public function drawCircle (nRadius:Number, nX:Number, nY:Number, sAlign:String):Void  {
    drawArc(360, nRadius, 0, nX, nY, sAlign);
  }

  public function drawSlice (nArc:Number, nRadius:Number, nStartingAngle:Number, nX:Number, nY:Number, sAlign:String):Void {
    drawArc(nArc, nRadius, nStartingAngle, nX, nY, sAlign, true);
  }

  public function drawArc (nArc:Number, nRadius:Number, nStartingAngle:Number, nX:Number, nY:Number, sAlign:String, bRadialLines:Boolean):Void  {

    nX = (nX == undefined) ? 0 : nX;
    nY = (nY == undefined) ? 0 : nY;

    switch(sAlign) {
      case "upperleft":
        nX += nRadius;
        nY += nRadius;
        break;
      case "upperright":
        nX += -nRadius;
        nY += nRadius;
        break;
      case "left":
        nX += nRadius;
        break;
      case "right":
        nX += -nRadius;
        break;
      case "upper":
        nY += nRadius;
        break;
      case "lower":
        nY += -nRadius;
        break;
      case "lowerleft":
        nX += nRadius;
        nY += -nRadius;
        break;
      case "lowerright":
        nX += -nRadius;
        nY += -nRadius;
        break;
    }

    // The angle of each of the eight segments is 45 degrees (360 divided by eight),
    // which equals p/4 radians.
    if(nArc > 360) {
      nArc = 360;
    }
    nArc = Math.PI/180 * nArc;
    var nAngleDelta:Number = nArc/8;

    // Find the distance from the circle's center to the control points
    // for the curves.
    var nCtrlDist:Number = nRadius/Math.cos(nAngleDelta/2);

    // Initialize the angle to 0 and define local variables that are used for the
    // control and ending points. 

    if(nStartingAngle == undefined) {
      nStartingAngle = 0;
    }

    nStartingAngle *= Math.PI / 180;

    var nAngle:Number = nStartingAngle;
    var nCtrlX:Number;
    var nCtrlY:Number;
    var nAnchorX:Number;
    var nAnchorY:Number;

    var nStartingX:Number = nX + Math.cos(nStartingAngle) * nRadius;
    var nStartingY:Number = nY + Math.sin(nStartingAngle) * nRadius;

    if(bRadialLines) {
      moveTo(nX, nY);
      lineTo(nStartingX, nStartingY);
    }
    else {
      // Move to the starting point, one radius to the right of the circle's center.
      moveTo(nStartingX, nStartingY);
    }

    // Repeat eight times to create eight segments.
    for (var i:Number = 0; i < 8; i++) {

      // Increment the angle by angleDelta (p/4) to create the whole circle (2p).
      nAngle += nAngleDelta;

      // The control points are derived using sine and cosine.
      nCtrlX = nX + Math.cos(nAngle-(nAngleDelta/2))*(nCtrlDist);
      nCtrlY = nY + Math.sin(nAngle-(nAngleDelta/2))*(nCtrlDist);

      // The anchor points (end points of the curve) can be found similarly to the
      // control points.
      nAnchorX = nX + Math.cos(nAngle) * nRadius;
      nAnchorY = nY + Math.sin(nAngle) * nRadius;

      // Draw the segment.
      curveTo(nCtrlX, nCtrlY, nAnchorX, nAnchorY);
    }
    if(bRadialLines) {
      lineTo(nX, nY);
    }
  }


  public function drawEllipse (nRadiusX:Number, nRadiusY:Number, nX:Number, nY:Number, sAlign:String):Void {

    nX = (nX == undefined) ? 0 : nX;
    nY = (nY == undefined) ? 0 : nY;

    switch(sAlign) {
      case "upperleft":
        nX += nRadiusX;
        nY += nRadiusY;
        break;
      case "upperright":
        nX += -nRadiusX;
        nY += nRadiusY;
        break;
      case "left":
        nX += nRadiusX;
        break;
      case "right":
        nX += -nRadiusX;
        break;
      case "upper":
        nY += nRadiusY;
        break;
      case "lower":
        nY += -nRadiusY;
        break;
      case "lowerleft":
        nX += nRadiusX;
        nY += -nRadiusY;
        break;
      case "lowerright":
        nX += -nRadiusX;
        nY += -nRadiusY;
        break;
    }

    var nAngleDelta:Number = Math.PI / 4;

    var nAngle:Number = 0;

    // Whereas the circle has only one distance to the control point 
    // for each segment, the ellipse has two distances: one that 
    // corresponds to xRadius and another that corresponds to yRadius.
    var nCtrlDistX:Number = nRadiusX / Math.cos(nAngleDelta/2);
    var nCtrlDistY:Number = nRadiusY / Math.cos(nAngleDelta/2);
    var nCtrlX:Number;
    var nCtrlY:Number;
    var nAnchorX:Number;
    var nAnchorY:Number;

    moveTo(nX + nRadiusX, nY);

    for (var i:Number = 0; i < 8; i++) {
      nAngle += nAngleDelta;
      nCtrlX = nX + Math.cos(nAngle-(nAngleDelta/2))*(nCtrlDistX);
      nCtrlY = nY + Math.sin(nAngle-(nAngleDelta/2))*(nCtrlDistY);
      nAnchorX = nX + Math.cos(nAngle) * nRadiusX;
      nAnchorY = nY + Math.sin(nAngle) * nRadiusY;
      this.curveTo(nCtrlX, nCtrlY, nAnchorX, nAnchorY);
    }
  }

  /**
   *  Draw a triangle given the lengths of two sides and the angle between them.
   */
  public function drawTriangle (nAB:Number, nAC:Number, nAngle:Number, nRotation:Number, nX:Number, nY:Number, sAlign:String):Void {

    nX = (nX == undefined) ? 0 : nX;
    nY = (nY == undefined) ? 0 : nY;

    nRotation = (nRotation == undefined) ? 0 : nRotation * Math.PI / 180;

    // Convert the angle between the sides from degrees to radians.
    nAngle = nAngle * Math.PI / 180;


    // Calculate the coordinates of points b and c.
    var nBx:Number = Math.cos(nAngle - nRotation) * nAB;
    var nBy:Number = Math.sin(nAngle - nRotation) * nAB;
    var nCx:Number = Math.cos(-nRotation) * nAC;
    var nCy:Number = Math.sin(-nRotation) * nAC;

    // Calculate the centroid's coordinates.
    var nCentroidX:Number = 0;
    var nCentroidY:Number = 0;

    /*if(bAlignUpperLeft) {
      nCentroidX = (nCx + nBx)/3 - nX;
      nCentroidY = (nCy + nBy)/3 - nY;
    }*/

    switch(sAlign) {
      case "upperleft":
        break;
      case "upperright":
        nCentroidX = nAB;
        break;
      case "left":
        nCentroidY = (nCy + nBy)/3;
        break;
      case "right":
        nCentroidX = nAB;
        nCentroidY = (nCy + nBy)/3;
        break;
      case "upper":
        nCentroidX = (nCx + nBx)/3;
        break;
      case "lower":
        nCentroidX = (nCx + nBx)/3;
        nCentroidY = nBy - nCentroidY;
        break;
      case "lowerleft":
        nCentroidY = nBy - nCentroidY;
        break;
      case "lowerright":
        nCentroidX = nAB;
        nCentroidY = nBy - nCentroidY;
        break;
      default:
        nCentroidX = (nCx + nBx)/3;
        nCentroidY = (nCy + nBy)/3;
    }
  // Move to point a, then draw line ac, then line cb, and finally ba (ab).
    drawLine(-nCentroidX + nX, -nCentroidY + nY, nCx - nCentroidX + nX, nCy - nCentroidY + nY);
    lineTo(nBx - nCentroidX + nX, nBy - nCentroidY + nY);
    lineTo(-nCentroidX + nX, -nCentroidY + nY);
  }

  public function drawRegularPolygon (nSides:Number, nLength:Number, nRotation:Number, nX:Number, nY:Number, sAlign:String):Void {

    nX = (nX == undefined) ? 0 : nX;
    nY = (nY == undefined) ? 0 : nY;

    // Convert nRotation from degrees to radians
    nRotation = (nRotation == undefined) ? 0 : nRotation * Math.PI / 180;

    // The angle formed between the segments from the polygon's center as shown in 
    // Figure 4-5. Since the total angle in the center is 360 degrees (2p radians),
    // each segment's angle is 2p divided by the number of sides.
    var nAngle:Number = (2 * Math.PI) / nSides;

    // Calculate the length of the radius that circumscribes the polygon (which is
    // also the distance from the center to any of the vertices).
    var nRadius:Number = (nLength/2)/Math.sin(nAngle/2);

    switch(sAlign) {
      case "upperleft":
        nX += nRadius;
        nY += nRadius;
        break;
      case "upperright":
        nX += -nRadius;
        nY += nRadius;
        break;
      case "left":
        nX += nRadius;
        break;
      case "right":
        nX += -nRadius;
        break;
      case "upper":
        nY += nRadius;
        break;
      case "lower":
        nY += -nRadius;
        break;
      case "lowerleft":
        nX += nRadius;
        nY += -nRadius;
        break;
      case "lowerright":
        nX += -nRadius;
        nY += -nRadius;
        break;
    }

    // The starting point of the polygon is calculated using trigonometry where 
    // radius is the hypotenuse and nRotation is the angle.
    var nPx:Number = (Math.cos(nRotation) * nRadius) + nX;
    var nPy:Number = (Math.sin(nRotation) * nRadius) + nY;

    // Move to the starting point without yet drawing a line.
    moveTo(nPx, nPy);

    // Draw each side. Calculate the vertex coordinates using the same trigonometric
    // ratios used to calculate px and py earlier.
    for (var i:Number = 1; i <= nSides; i++) {
      nPx = (Math.cos((nAngle * i) + nRotation) * nRadius) + nX;
      nPy = (Math.sin((nAngle * i) + nRotation) * nRadius) + nY;
      lineTo(nPx, nPy);
    }
  }

  public function drawStar(nPoints:Number, nInnerRadius:Number, nOuterRadius:Number, nRotation:Number, nX:Number, nY:Number, sAlign:Number):Void {

    if(nPoints < 3) {
      return;
    }

    var nAngleDelta:Number = (Math.PI * 2) / nPoints;
    if(nRotation == undefined) {
      nRotation = 0;
    }
    nRotation = Math.PI * (nRotation - 90) / 180;

    if(nX == undefined) {
      nX = 0;
    }
    if(nY == undefined) {
      nY = 0;
    }

    var nAngle:Number = nRotation;

    var nPenX:Number = nX + Math.cos(nAngle + nAngleDelta / 2) * nInnerRadius;
    var nPenY:Number = nY + Math.sin(nAngle + nAngleDelta / 2) * nInnerRadius;

    moveTo(nPenX, nPenY);

    nAngle += nAngleDelta;

    for(var i:Number = 0; i < nPoints; i++) {
      nPenX = nX + Math.cos(nAngle) * nOuterRadius;
      nPenY = nY + Math.sin(nAngle) * nOuterRadius;
      lineTo(nPenX, nPenY);
      nPenX = nX + Math.cos(nAngle + nAngleDelta / 2) * nInnerRadius;
      nPenY = nY + Math.sin(nAngle + nAngleDelta / 2) * nInnerRadius;
      lineTo(nPenX, nPenY);
      nAngle += nAngleDelta;
    }

  }

}