/*global require*/
'use strict';
/************************************************************************
### Drone.arc() method

The arc() method can be used to create 1 or more 90 degree arcs in the
horizontal or vertical planes. This method is called by cylinder() and
cylinder0() and the sphere() and sphere0() methods.

#### Parameters

arc() takes a single parameter - an object with the following named properties...

 * radius - The radius of the arc.
 * blockType - The type of block to use - this is the block Id only (no meta). See [Data Values][dv].
 * meta - The metadata value. See [Data Values][dv].
 * orientation (default: 'horizontal' ) - the orientation of the arc - can be 'vertical' or 'horizontal'.
 * stack (default: 1 ) - the height or length of the arc (depending on the orientation - if orientation is horizontal then this parameter refers to the height, if vertical then it refers to the length ).
 * strokeWidth (default: 1 ) - the width of the stroke (how many blocks) - if drawing nested arcs it's usually a good idea to set strokeWidth to at least 2 so that there are no gaps between each arc. The arc method uses a [bresenham algorithm][bres] to plot points along the circumference.
 * fill - If true (or present) then the arc will be filled in.
 * quadrants (default: `{topleft:true,topright:true,bottomleft:true,bottomright:true}` - An object with 4 properties indicating which of the 4 quadrants of a circle to draw. If the quadrants property is absent then all 4 quadrants are drawn.

#### Examples

To draw a 1/4 circle (top right quadrant only) with a radius of 10 and
stroke width of 2 blocks ...

    arc({blockType: blocks.iron, 
         meta: 0, 
         radius: 10,
         strokeWidth: 2,
         quadrants: { topright: true },
         orientation: 'vertical', 
         stack: 1,
         fill: false
         } );

![arc example 1](img/arcex1.png)

[bres]: http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
[dv]: http://www.minecraftwiki.net/wiki/Data_values

***/
/*
 do the bresenham thing
 */
function bresenham(x0, y0, radius, setPixel, quadrants) {
  //
  // credit: Following code is copied almost verbatim from
  // http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
  // Bresenham's circle algorithm
  //
  var f = 1 - radius;
  var ddF_x = 1;
  var ddF_y = -2 * radius;
  var x = 0;
  var y = radius;
  var defaultQuadrants = {
    topleft: true,
    topright: true,
    bottomleft: true,
    bottomright: true
  };
  quadrants = quadrants ? quadrants : defaultQuadrants;
  /*
   II  | I
   ------------
   III | IV
   */
  if (quadrants.topleft || quadrants.topright) setPixel(x0, y0 + radius); // quadrant I/II topmost
  if (quadrants.bottomleft || quadrants.bottomright) setPixel(x0, y0 - radius); // quadrant III/IV bottommost
  if (quadrants.topright || quadrants.bottomright) setPixel(x0 + radius, y0); // quadrant I/IV rightmost
  if (quadrants.topleft || quadrants.bottomleft) setPixel(x0 - radius, y0); // quadrant II/III leftmost

  while (x < y) {
    if (f >= 0) {
      y--;
      ddF_y += 2;
      f += ddF_y;
    }
    x++;
    ddF_x += 2;
    f += ddF_x;
    if (quadrants.topright) {
      setPixel(x0 + x, y0 + y); // quadrant I
      setPixel(x0 + y, y0 + x); // quadrant I
    }
    if (quadrants.topleft) {
      setPixel(x0 - x, y0 + y); // quadrant II
      setPixel(x0 - y, y0 + x); // quadrant II
    }
    if (quadrants.bottomleft) {
      setPixel(x0 - x, y0 - y); // quadrant III
      setPixel(x0 - y, y0 - x); // quadrant III
    }
    if (quadrants.bottomright) {
      setPixel(x0 + x, y0 - y); // quadrant IV
      setPixel(x0 + y, y0 - x); // quadrant IV
    }
  }
}

function getStrokeDir(x, y) {
  var absY = Math.abs(y);
  var absX = Math.abs(x);
  var strokeDir = 0;
  if (y > 0 && absY >= absX) strokeDir = 0;
  else if (y < 0 && absY >= absX)
    //down
    strokeDir = 1;
  else if (x > 0 && absX >= absY)
    // up
    strokeDir = 2;
  else if (x < 0 && absX >= absY)
    // left
    strokeDir = 3; // right
  return strokeDir;
}

/*
 The daddy of all arc-related API calls - 
 if you're drawing anything that bends it ends up here.
 */
function arcImpl(params) {
  var drone = params.drone;
  var orientation = params.orientation ? params.orientation : 'horizontal';
  var quadrants = params.quadrants
    ? params.quadrants
    : {
        topright: 1,
        topleft: 2,
        bottomleft: 3,
        bottomright: 4
      };
  var stack = params.stack ? params.stack : 1;
  var radius = params.radius;
  var strokeWidth = params.strokeWidth ? params.strokeWidth : 1;
  drone.chkpt('arc2');
  var x0, y0, gotoxy, setPixel;

  if (orientation == 'horizontal') {
    gotoxy = function(x, y) {
      return drone.right(x).fwd(y);
    };
    drone
      .right(radius)
      .fwd(radius)
      .chkpt('center');
    switch (drone.dir) {
      case 0: // east
      case 2: // west
        x0 = drone.z;
        y0 = drone.x;
        break;
      case 1: // south
      case 3: // north
        x0 = drone.x;
        y0 = drone.z;
    }
    setPixel = function(x, y) {
      x = x - x0;
      y = y - y0;
      if (params.fill) {
        // wph 20130114 more efficient esp. for large cylinders/spheres
        if (y < 0) {
          drone
            .fwd(y)
            .right(x)
            .cuboidX(
              params.blockType,
              params.meta,
              1,
              stack,
              Math.abs(y * 2) + 1
            )
            .back(y)
            .left(x);
        }
      } else {
        if (strokeWidth == 1) {
          gotoxy(x, y)
            .cuboidX(
              params.blockType,
              params.meta,
              1, // width
              stack, // height
              strokeWidth // depth
            )
            .move('center');
        } else {
          var strokeDir = getStrokeDir(x, y);
          var width = 1,
            depth = 1;
          switch (strokeDir) {
            case 0: // down
              y = y - (strokeWidth - 1);
              depth = strokeWidth;
              break;
            case 1: // up
              depth = strokeWidth;
              break;
            case 2: // left
              width = strokeWidth;
              x = x - (strokeWidth - 1);
              break;
            case 3: // right
              width = strokeWidth;
              break;
          }
          gotoxy(x, y)
            .cuboidX(params.blockType, params.meta, width, stack, depth)
            .move('center');
        }
      }
    };
  } else {
    // vertical
    gotoxy = function(x, y) {
      return drone.right(x).up(y);
    };
    drone
      .right(radius)
      .up(radius)
      .chkpt('center');
    switch (drone.dir) {
      case 0: // east
      case 2: // west
        x0 = drone.z;
        y0 = drone.y;
        break;
      case 1: // south
      case 3: // north
        x0 = drone.x;
        y0 = drone.y;
    }
    setPixel = function(x, y) {
      x = x - x0;
      y = y - y0;
      if (params.fill) {
        // wph 20130114 more efficient esp. for large cylinders/spheres
        if (y < 0) {
          drone
            .up(y)
            .right(x)
            .cuboidX(
              params.blockType,
              params.meta,
              1,
              Math.abs(y * 2) + 1,
              stack
            )
            .down(y)
            .left(x);
        }
      } else {
        if (strokeWidth == 1) {
          gotoxy(x, y)
            .cuboidX(params.blockType, params.meta, strokeWidth, 1, stack)
            .move('center');
        } else {
          var strokeDir = getStrokeDir(x, y);
          var width = 1,
            height = 1;
          switch (strokeDir) {
            case 0: // down
              y = y - (strokeWidth - 1);
              height = strokeWidth;
              break;
            case 1: // up
              height = strokeWidth;
              break;
            case 2: // left
              width = strokeWidth;
              x = x - (strokeWidth - 1);
              break;
            case 3: // right
              width = strokeWidth;
              break;
          }
          gotoxy(x, y)
            .cuboidX(params.blockType, params.meta, width, height, stack)
            .move('center');
        }
      }
    };
  }
  /*
   setPixel assumes a 2D plane - need to put a block along appropriate plane
   */
  bresenham(x0, y0, radius, setPixel, quadrants);

  params.drone.move('arc2');
}

module.exports = function(Drone) {
  Drone.extend(function arc(params) {
    params.drone = this;
    arcImpl(params);
  });
};
