package randoop.test.perimeter;
/** A class representing a node in the quad tree. */
public abstract class QuadTreeNode {
  /** Variable used to determine the x-axis size of the image */
  static int gcmp = 4194304;
  /** Variable used to determine the y-axis size of the image */
  static int lcmp = 1048576;

  /**
   * The quadrant that this node represents (i.e., northwest, northeast, southwest, or southeast).
   */
  protected Quadrant quadrant;
  /** Node that represents the northwest quadrant of the image */
  protected QuadTreeNode nw;
  /** Node that represents the northeast quadrant of the image */
  protected QuadTreeNode ne;
  /** Node that represents the southwest quadrant of the image */
  protected QuadTreeNode sw;
  /** Node that represents the southeast quadrant of the image */
  protected QuadTreeNode se;
  /** Node that represents the parent quadrant of the image */
  protected QuadTreeNode parent;

  // enumeration for direction
  public static final int NORTH = 0;
  public static final int EAST = 1;
  public static final int SOUTH = 2;
  public static final int WEST = 3;

  /**
   * Create a leaf node in the Quad Tree.
   *
   * @param quad childType if there's a parent, the type of child this node represents
   * @param parent the parent quad tree node
   */
  public QuadTreeNode(Quadrant quad, QuadTreeNode parent) {
    this(quad, null, null, null, null, parent);
  }

  /**
   * Create a node in the quad tree.
   *
   * @param quad childType if there's a parent, the type of child
   * @param nw the node represent the northwest quadrant
   * @param ne the node represent the northeast quadrant
   * @param sw the node represent the southwest quadrant
   * @param se the node represent the southeast quadrant
   */
  private QuadTreeNode(
      Quadrant quad,
      QuadTreeNode nw,
      QuadTreeNode ne,
      QuadTreeNode sw,
      QuadTreeNode se,
      QuadTreeNode parent) {
    this.quadrant = quad;
    this.nw = nw;
    this.ne = ne;
    this.sw = sw;
    this.se = se;
    this.parent = parent;
  }

  /**
   * Set the children of the quad tree node.
   *
   * @param nw the node represent the northwest quadrant
   * @param ne the node represent the northeast quadrant
   * @param sw the node represent the southwest quadrant
   * @param se the node represent the southeast quadrant
   */
  protected void setChildren(QuadTreeNode nw, QuadTreeNode ne, QuadTreeNode sw, QuadTreeNode se) {
    this.nw = nw;
    this.ne = ne;
    this.sw = sw;
    this.se = se;
  }

  /**
   * Return the node representing the north west quadrant.
   *
   * @return the node representing the north west quadrant
   */
  public final QuadTreeNode getNorthWest() {
    return nw;
  }
  /**
   * Return the node representing the north east quadrant.
   *
   * @return the node representing the north east quadrant
   */
  public final QuadTreeNode getNorthEast() {
    return ne;
  }
  /**
   * Return the node representing the south west quadrant.
   *
   * @return the node representing the south west quadrant
   */
  public final QuadTreeNode getSouthWest() {
    return sw;
  }
  /**
   * Return the node representing the south east quadrant.
   *
   * @return the node representing the south east quadrant
   */
  public final QuadTreeNode getSouthEast() {
    return se;
  }

  /**
   * Create an image which is represented using a QuadTreeNode.
   *
   * @param size size of image
   * @param center_x x coordinate of center
   * @param center_y y coordinate of center
   * @param parent parent quad tree node
   * @param quadrant the quadrant that the sub tree is in
   * @param level the level of the tree
   */
  public static QuadTreeNode createTree(
      int size, int center_x, int center_y, QuadTreeNode parent, Quadrant quadrant, int level) {
    QuadTreeNode node;

    int intersect = checkIntersect(center_x, center_y, size);
    size = size / 2;
    if (intersect == 0 && size < 512) {
      node = new WhiteNode(quadrant, parent);
    } else if (intersect == 2) {
      node = new BlackNode(quadrant, parent);
    } else {
      if (level == 0) {
        node = new BlackNode(quadrant, parent);
      } else {
        node = new GreyNode(quadrant, parent);
        QuadTreeNode sw =
            createTree(
                size, center_x - size, center_y - size, node, Quadrant.cSouthWest, level - 1);
        QuadTreeNode se =
            createTree(
                size, center_x + size, center_y - size, node, Quadrant.cSouthEast, level - 1);
        QuadTreeNode ne =
            createTree(
                size, center_x + size, center_y + size, node, Quadrant.cNorthEast, level - 1);
        QuadTreeNode nw =
            createTree(
                size, center_x - size, center_y + size, node, Quadrant.cNorthWest, level - 1);
        node.setChildren(nw, ne, sw, se);
      }
    }
    return node;
  }

  /**
   * Compute the total perimeter of a binary image that is represented as a quadtree using Samet's
   * algorithm.
   *
   * @param size the size of the image that this node represents (size X size)
   * @return the size of the perimeter of the image
   */
  public abstract int perimeter(int size);

  /**
   * Sum the perimeter of all white leaves in the two specified quadrants of the sub quad tree
   * rooted at this node.
   *
   * @param quad1 the first specified quadrant
   * @param quad2 the second specified quadrant
   * @param size the size of the image represented by this node
   * @return the perimeter of the adjacent nodes
   */
  public abstract int sumAdjacent(Quadrant quad1, Quadrant quad2, int size);

  /**
   * Return the neighbor of this node in the given direction which is greater than or equal in size
   * to this node. If the node doesn't exist, then a grey node of equal size is returned. Otherwise,
   * the node is adjacent to the border of the image and null is returned.
   *
   * @param dir the direction of the neighbor
   * @return the appropriate neighbor based upon the direction, or null
   */
  public QuadTreeNode gtEqualAdjNeighbor(int dir) {
    QuadTreeNode q;
    if (parent != null && quadrant.adjacent(dir)) {
      q = parent.gtEqualAdjNeighbor(dir);
    } else {
      q = parent;
    }

    if (q != null && q instanceof GreyNode) {
      return quadrant.reflect(dir).child(q);
    } else {
      return q;
    }
  }

  /**
   * Count the number of leaves in the quad tree.
   *
   * @return the number of leaves in the quad tree
   */
  public int countTree() {
    if (nw == null && ne == null && sw == null && se == null) {
      return 1;
    } else {
      return sw.countTree() + se.countTree() + ne.countTree() + nw.countTree();
    }
  }

  private static int checkOutside(int x, int y) {
    int euclid = x * x + y * y;
    if (euclid > gcmp) return 1;
    if (euclid < lcmp) return -1;
    return 0;
  }

  private static int checkIntersect(int center_x, int center_y, int size) {
    if (checkOutside(center_x + size, center_y + size) == 0
        && checkOutside(center_x + size, center_y - size) == 0
        && checkOutside(center_x - size, center_y - size) == 0
        && checkOutside(center_x - size, center_y + size) == 0) return 2;

    int sum =
        checkOutside(center_x + size, center_y + size)
            + checkOutside(center_x + size, center_y - size)
            + checkOutside(center_x - size, center_y - size)
            + checkOutside(center_x - size, center_y + size);

    if ((sum == 4) || (sum == -4)) {
      return 0;
    }

    return 1;
  }

  @Override
  public String toString() {
    return getClass().getName() + " " + quadrant.getClass().getName();
  }
}
