/*
 * Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jit.graph;

// This class defines the tree object.
public class RBTree {
    public final static int maxNodes = 70;      // maximum nodes allowed.
    public final static int INSERT = 0;         // constants indicating
    public final static int DELETE = 1;         // the current operation
    public final static int NOP = 2;
    public final static Node treeNull = new Node(); // the tree NULL node.

    private Node root;
    private int num_of_nodes;
    private int height;           // The tree height, it is updated
    // in each operation.

    // since the algorithm is executed in stages I have to remember data
    // on the state.
    private Node node;  // The current operation is being done on it.
    private int action; // The operation being performed (insert / delete)
    private int stage;  // The current stage of execution

    // the constructor initializes the object fields.
    public RBTree() {
        root = treeNull;
        node = treeNull;
        num_of_nodes = 0;
        height = 0;
        action = NOP;
        stage = 0;
    }

    // This method returns the root of the tree.
    public Node getRoot() {
        return root;
    }

    // This method returns the number of nodes in the tree.
    public int getNodes() {
        return num_of_nodes;
    }

    // This method returns the height of the tree.
    public int getHeight() {
        return height;
    }


    // This method inserts k into the Red Black Tree
    public boolean RBInsert(int k) {
        // checking similar to the RB_Insert method
        if (action != NOP) {
            System.out.println("Only one operation can be done at a time.");
            return false;
        }

        if (num_of_nodes == maxNodes) {
            System.out.println("The maximum nodes allowed is already reached.");
            return false;
        }

        // Check if there is already node with key k.
        if (Search(k) == treeNull) {
            action = INSERT;
            node = new Node(k);
            node.setNode(Node.Left_son, treeNull);
            node.setNode(Node.Right_son, treeNull);
            node.setNode(Node.Parent, treeNull);
            stage = 1;
            // This is the loop that perform all the operation steps.
            while (stage != 0) {
                // perform one step
                InsertStep();
                // update the tree height
                updateHeight();
            }
            // set the action to NoOPretion.
            action = NOP;
            return true;
        } else
            System.out.println("Insertion failed. This key already exist.");
        return false;
    }


    // This method deletes the element k from the Red Black tree
    public boolean RBDelete(int k) {
        // checking like in RB_Delete method
        if (action != NOP) {
            System.out.println("Only one operation can be done at a time.");
            return false;
        }
        node = Search(k);
        // Check if there is a node with key k.
        if (node != treeNull) {
            action = DELETE;
            stage = 1;
            // this loop perform all the operation steps.
            while (stage != 0) {
                // perform one step
                DeleteStep();
                // update the tree height
                updateHeight();
            }
            action = NOP;
            return true;
        } else
            System.out.println("Deletion failed. This key doesn't exist.");
        return false;
    }

    // This method performs one step in the insertion operation.
    // It performs a step according to the stage variable.
    // I will not explain exactly what each stage do, just that they
    // divided to 4 categories:
    // 1. inserting a node to the tree.
    // 2. marking nodes that will be recolored.
    // 3. recoloring nodes.
    // 4. rotating right or left.
    private void InsertStep() {
        // Pr is parent, GrPr is grandparent and Un is uncle.
        Node Pr, GrPr, Un;
        switch (stage) {
            // Inserting a node to the tree
            case 1:
                Tree_Insert();
                break;
            // mid stage that moves the algorithm to the proper next stage
            case 2:
                Pr = node.getNode(Node.Parent);
                GrPr = Pr.getNode(Node.Parent);
                if (Pr == GrPr.getNode(Node.Left_son)) {
                    Un = GrPr.getNode(Node.Right_son);
                    if (Un.getColor() == Node.Red) {
                        stage = 3;
                    } else if (node == Pr.getNode(Node.Right_son)) {
                        node = Pr;
                        stage = 5;
                    } else {
                        stage = 6;
                    }
                } else {
                    Un = GrPr.getNode(Node.Left_son);
                    if (Un.getColor() == Node.Red) {
                        stage = 3;
                    } else if (node == Pr.getNode(Node.Left_son)) {
                        node = Pr;
                        stage = 5;
                    } else {
                        stage = 6;
                    }
                }
                break;
            // This stage marks node that will be recolored
            case 3:
                Pr = node.getNode(Node.Parent);
                GrPr = Pr.getNode(Node.Parent);
                if (Pr == GrPr.getNode(Node.Left_son)) {
                    Un = GrPr.getNode(Node.Right_son);
                } else {
                    Un = GrPr.getNode(Node.Left_son);
                }
                node = GrPr;
                stage = 4;
                break;
            // This stage recolors marked nodes.
            case 4:
                node.setColor(Node.Red);
                node.getNode(Node.Left_son).setColor(Node.Black);
                node.getNode(Node.Right_son).setColor(Node.Black);

                if ((node == root) ||
                        (node.getNode(Node.Parent).getColor() == Node.Black)) {
                    if (root.getColor() == Node.Red) {
                        stage = 9;
                    } else
                        stage = 0;
                } else {
                    stage = 2;
                    InsertStep();
                }
                break;
            // This stage performs rotation operation
            case 5:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Left_Rotate(node);
                } else {
                    Right_Rotate(node);
                }
                stage = 6;
                break;
            // This stage marks nodes that will be recolor.
            case 6:
                Pr = node.getNode(Node.Parent);
                GrPr = Pr.getNode(Node.Parent);

                stage = 7;
                break;
            // This stage recolors marked nodes.
            case 7:
                Pr = node.getNode(Node.Parent);
                Pr.setColor(Node.Black);
                GrPr = Pr.getNode(Node.Parent);
                GrPr.setColor(Node.Red);

                stage = 8;
                break;
            // This stage performs rotation operation
            case 8:
                Pr = node.getNode(Node.Parent);
                GrPr = Pr.getNode(Node.Parent);
                if (Pr == GrPr.getNode(Node.Left_son)) {
                    Right_Rotate(GrPr);
                } else {
                    Left_Rotate(GrPr);
                }
                if (root.getColor() == Node.Red) {
                    stage = 9;
                } else
                    stage = 0;
                break;
            // this stage marks the root.
            case 9:
                stage = 10;
                break;
            // This stage recolors the root.
            case 10:
                root.setColor(Node.Black);
                stage = 0;
                break;
        }
    }

    // This method performs one step in the deletion operation.
    // It perform sa step according to the stage variable.
    // I will explain exactly what each stage do, just that they
    // divided to 4 categories:
    // 1. deleting a node from the tree.
    // 2. marking nodes that will be recolored.
    // 3. recoloring nodes.
    // 4. rotating right or left.
    public void DeleteStep() {
        // Pr is Parent, Br is Brother
        Node Pr, Br;
        switch (stage) {
            // This stage delete a node from the tree.
            case 1:
                Tree_Delete();
                break;
            // This stage marks a nodes that will be recolored or perform other stage.
            case 2:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Br = Pr.getNode(Node.Right_son);
                } else {
                    Br = Pr.getNode(Node.Left_son);
                }
                if (Br.getColor() == Node.Red) {
                    stage = 3;
                } else if ((Br.getNode(Node.Right_son).getColor() == Node.Black)
                        && (Br.getNode(Node.Left_son).getColor() == Node.Black)) {
                    stage = 5;
                    DeleteStep();
                } else {
                    stage = 7;
                    DeleteStep();
                }
                break;
            // This stage recolors marked nodes.
            case 3:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Br = Pr.getNode(Node.Right_son);
                } else {
                    Br = Pr.getNode(Node.Left_son);
                }
                Br.setColor(Node.Black);
                Pr.setColor(Node.Red);

                stage = 4;
                break;
            // This stage performs rotation operation
            case 4:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Left_Rotate(Pr);
                    Br = Pr.getNode(Node.Right_son);
                } else {
                    Right_Rotate(Pr);
                    Br = Pr.getNode(Node.Left_son);
                }
                if ((Br.getNode(Node.Right_son).getColor() == Node.Black)
                        && (Br.getNode(Node.Left_son).getColor() == Node.Black)) {
                    stage = 5;
                } else {
                    stage = 7;
                }

                break;
            // This stage marks nodes that will be recolor.
            case 5:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Br = Pr.getNode(Node.Right_son);
                } else {
                    Br = Pr.getNode(Node.Left_son);
                }
                stage = 6;
                break;
            // This stage recolors marked nodes.
            case 6:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Br = Pr.getNode(Node.Right_son);
                } else {
                    Br = Pr.getNode(Node.Left_son);
                }
                Br.setColor(Node.Red);
                node = Pr;

                if ((node != root) && (node.getColor() == Node.Black)) {
                    stage = 2;
                } else if (node.getColor() == Node.Red) {
                    stage = 13;
                } else
                    stage = 0;
                break;
            // This stage marks nodes that will be recolor or perform other stage.
            case 7:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Br = Pr.getNode(Node.Right_son);
                    if ((Br.getNode(Node.Right_son)).getColor() == Node.Black) {
                        stage = 8;
                    } else {
                        stage = 10;
                        DeleteStep();
                    }
                } else {
                    Br = Pr.getNode(Node.Left_son);
                    if ((Br.getNode(Node.Left_son)).getColor() == Node.Black) {
                        stage = 8;
                    } else {
                        stage = 10;
                        DeleteStep();
                    }
                }
                break;
            // This stage recolors marked nodes.
            case 8:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Br = Pr.getNode(Node.Right_son);
                    Br.getNode(Node.Left_son).setColor(Node.Black);
                } else {
                    Br = Pr.getNode(Node.Left_son);
                    Br.getNode(Node.Right_son).setColor(Node.Black);
                }
                Br.setColor(Node.Red);
                stage = 9;
                break;
            // This stage performs rotation operation
            case 9:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Br = Pr.getNode(Node.Right_son);
                    Right_Rotate(Br);
                } else {
                    Br = Pr.getNode(Node.Left_son);
                    Left_Rotate(Br);
                }

                stage = 10;
                break;
            // This stage marks node that will be recolor.
            case 10:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Br = Pr.getNode(Node.Right_son);
                } else {
                    Br = Pr.getNode(Node.Left_son);
                }

                stage = 11;
                break;
            // This stage recolors marked nodes.
            case 11:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Br = Pr.getNode(Node.Right_son);
                    Br.getNode(Node.Right_son).setColor(Node.Black);
                } else {
                    Br = Pr.getNode(Node.Left_son);
                    Br.getNode(Node.Left_son).setColor(Node.Black);

                }
                if (Br.getColor() != Pr.getColor()) {
                    Br.setColor(Pr.getColor());
                }
                if (Pr.getColor() != Node.Black) {
                    Pr.setColor(Node.Black);
                }

                stage = 12;
                break;
            // This stage performs rotation operation.
            case 12:
                Pr = node.getNode(Node.Parent);
                if (node == Pr.getNode(Node.Left_son)) {
                    Left_Rotate(Pr);
                } else {
                    Right_Rotate(Pr);
                }
                node = root;
                if (node.getColor() == Node.Red) {
                    stage = 13;
                } else {
                    stage = 0;
                }
                break;
            // This stage marks a node that will be recolored
            case 13:
                stage = 14;
                break;
            // This stage recolors marked node.
            case 14:
                node.setColor(Node.Black);
                stage = 0;
                break;
        }
    }

    // This method inserts the node 'node' to the tree.
    // it called from the first stage in the InsertStep method.
    // we 'dive' from the root to a leaf according to the node key
    // and insert the node in the proper place.
    private void Tree_Insert() {
        Node n1, n2;
        n1 = root;
        n2 = treeNull;
        while (n1 != treeNull) {
            n2 = n1;
            if (node.getKey() < n1.getKey()) {
                n1 = n1.getNode(Node.Left_son);
            } else {
                n1 = n1.getNode(Node.Right_son);
            }
        }
        node.setNode(Node.Parent, n2);
        if (n2 == treeNull) {
            root = node;
        }
        else {
            if (node.getKey() < n2.getKey()) {
                n2.setNode(Node.Left_son, node);
            } else {
                n2.setNode(Node.Right_son, node);
            }
        }
        // updating the insertion stage.
        if ((node == root) ||
                (node.getNode(Node.Parent).getColor() == Node.Black)) {
            if (root.getColor() == Node.Red) {
                stage = 9;
            } else {
                stage = 0;
            }
        } else {
            stage = 2;
            InsertStep();
        }
        num_of_nodes++;   // increasing the number of nodes
    }

    // This method deletes the node 'node' from the tree.
    // it called from the first stage in the DeleteStep method.
    // if node has at most one son we just remove it and connect
    // his son and parent. If it has 2 sons we delete his successor
    // that has at most one son and replace him with the successor.
    private void Tree_Delete() {
        Node n1, n2, n3;
        if ((node.getNode(Node.Left_son) == treeNull) ||
                (node.getNode(Node.Right_son) == treeNull)) {
            n1 = node;
        } else {
            n1 = Tree_Successor(node);
        }

        if (n1.getNode(Node.Left_son) != treeNull) {
            n2 = n1.getNode(Node.Left_son);
        } else {
            n2 = n1.getNode(Node.Right_son);
        }

        n3 = n1.getNode(Node.Parent);
        n2.setNode(Node.Parent, n3);
        if (n3 == treeNull) {
            root = n2;
        } else if (n1 == n3.getNode(Node.Left_son)) {
            n3.setNode(Node.Left_son, n2);
        } else {
            n3.setNode(Node.Right_son, n2);
        }

        if (n1 != node) {
            node.setKey(n1.getKey());
        }

        node = n2;
        if (n1.getColor() == Node.Black) {
            if ((node != root) && (node.getColor() == Node.Black)) {
                stage = 2;
            } else if (node.getColor() == Node.Red) {
                stage = 13;
            } else {
                stage = 0;
            }
        } else {
            stage = 0;
        }
        // decrease the number of nodes.
        num_of_nodes--;
    }

    // This method returns the successor of the node n in the tree.
    private Node Tree_Successor(Node n) {
        Node n1;
        if (n.getNode(Node.Right_son) != treeNull) {
            n = n.getNode(Node.Right_son);
            while (n.getNode(Node.Left_son) != treeNull) {
                n = n.getNode(Node.Left_son);
            }
            return n;
        }
        n1 = n.getNode(Node.Parent);
        while ((n1 != treeNull) && (n == n1.getNode(Node.Right_son))) {
            n = n1;
            n1 = n1.getNode(Node.Parent);
        }
        return n1;
    }

    // This method performs Left Rotation with n1.
    private void Left_Rotate(Node n1) {
        Node n2;

        n2 = n1.getNode(Node.Right_son);
        n1.setNode(Node.Right_son, n2.getNode(Node.Left_son));
        if (n2.getNode(Node.Left_son) != treeNull) {
            n2.getNode(Node.Left_son).setNode(Node.Parent, n1);
        }
        n2.setNode(Node.Parent, n1.getNode(Node.Parent));
        if (n1.getNode(Node.Parent) == treeNull) {
            root = n2;
        } else if (n1 == n1.getNode(Node.Parent).getNode(Node.Left_son)) {
            n1.getNode(Node.Parent).setNode(Node.Left_son, n2);
        } else {
            n1.getNode(Node.Parent).setNode(Node.Right_son, n2);
        }
        n2.setNode(Node.Left_son, n1);
        n1.setNode(Node.Parent, n2);
    }

    // This method performs Right Rotation with n1.
    private void Right_Rotate(Node n1) {
        Node n2;

        n2 = n1.getNode(Node.Left_son);
        n1.setNode(Node.Left_son, n2.getNode(Node.Right_son));
        if (n2.getNode(Node.Right_son) != treeNull) {
            n2.getNode(Node.Right_son).setNode(Node.Parent, n1);
        }
        n2.setNode(Node.Parent, n1.getNode(Node.Parent));
        if (n1.getNode(Node.Parent) == treeNull) {
            root = n2;
        } else if (n1 == (n1.getNode(Node.Parent)).getNode(Node.Left_son)) {
            n1.getNode(Node.Parent).setNode(Node.Left_son, n2);
        } else {
            n1.getNode(Node.Parent).setNode(Node.Right_son, n2);
        }
        n2.setNode(Node.Right_son, n1);
        n1.setNode(Node.Parent, n2);
    }

    // This method searches the tree for a node with key 'key', and
    // returns the node on success otherwise treeNull.
    public Node Search(int key) {
        Node node;
        node = root;
        while ((node != treeNull) && (key != node.getKey())) {
            if (key < node.getKey()) {
                node = node.getNode(Node.Left_son);
            } else {
                node = node.getNode(Node.Right_son);
            }
        }
        return node;
    }

    // This method updates the tree height. it uses a recursive method
    // findHeight.
    private void updateHeight() {
        height = 0;
        if (root != treeNull) {
            findHeight(root, 1);
        }
    }

    // This is a recursive method that find a node height.
    private void findHeight(Node n, int curr) {
        if (height < curr) {
            height = curr;
        }
        if (n.getNode(Node.Left_son) != treeNull) {
            findHeight(n.getNode(Node.Left_son), curr + 1);
        }
        if (n.getNode(Node.Right_son) != treeNull) {
            findHeight(n.getNode(Node.Right_son), curr + 1);
        }
    }

}