//Point (100, 200)
//Point (10000, 0)
//
// class Point3D extends Point
//public class Point3D implements Comparable<Point> {
//    int z;
//    @Override
//    public int compareTo(Point other){
//        // mine and other is same
//        return 0;
//        //mine is greater
//        return p; //where p is positive integer
//        //mine is smaller
//        return n; //where n is negative integer
//    }
//}

//constraint T to implement Comparable interface


//public class BinarySearchTree<T extends Comparable<T>> {
public class BinarySearchTreeCompleted<E extends Comparable<? super E>> {

    private BinaryNode<E> overallRoot;

    public BinarySearchTreeCompleted() {
        overallRoot = null;
    }

    //insert: modifying existing tree: x = change(x);
    public void insert(E targetData) {
        BinaryNode<E> newNode = new BinaryNode<>(targetData);
        overallRoot = insert(newNode, overallRoot);
    }

    //helper method that will insert target into the right location
    //return the new subtree with the target node inserted
    //Time complexity O(D) where D = log n (best case) or D = n (worst case)
    private BinaryNode<E> insert(BinaryNode<E> target, BinaryNode<E> root) {
        if (root == null) { //base case
            root = target;
        }
        if (target.data.compareTo(root.data) > 0) { //target is greater than current root
            root.right = insert(target, root.right); //insert to right subtree
        } else if (target.data.compareTo(root.data) < 0) { //target is less than current root
            root.left = insert(target, root.left); //insert to left subtree
        } else { //equal
            //no insert (depends on the spec)
        }
        return root;
    }

    //find
    public boolean find(E targetData) {
        return find(targetData, overallRoot);
    }

    private boolean find(E targetData, BinaryNode<E> root) {
        //base case
        if (root == null) return false;
        if (targetData.compareTo(root.data) == 0) {
            return true;
        }
        if (targetData.compareTo(root.data) < 0) {
            return find(targetData, root.left); //search left subtree
        } else {
            return find(targetData, root.right); //search right subtree
        }
    }

    //remove
    //x = change(x)
    public void remove(E targetData) {
        overallRoot = remove(targetData, overallRoot);
    }

    //removes a node that has targetData and returns the subtree
    //by recursion
    private BinaryNode<E> remove(E targetData, BinaryNode<E> node) {
        //base-case
        if (node == null) return null;
        if (targetData.compareTo(node.data) < 0) { //targetData is smaller than current data
            //remove from my left subtree
            node.left = remove(targetData, node.left);
        } else if (targetData.compareTo(node.data) > 0) {//target is greater
            //remove from my right subtree
            node.right = remove(targetData, node.right);
        } else { //data found!!! node's data is the target itself
            //case 1: node is leaf (remove node)
            if (node.left == null && node.right == null) {
                node = null;
            } else if (node.left == null) { //case 2: only left child is null
                node = node.right; //make my non null child the root
            } else if (node.right == null) { //case 3: only right child is null
                node = node.left; //make my non-null child the root
            } else { //case 4: both are not null
                //option 1: pick the max from the left subtree (right subtree remains unchanged)
                node.data = findMax(node.left); //
                node.left = remove(node.data, node.left);

//                //option 2: pick the min from the right subtree (left subtree remains unchanged)
//                node.data = findMin(node.right); //
//                node.right = remove(node.data, node.right);
            }
        }
        return node;
    }

    public E findMax(){
        return findMax(overallRoot);
    }

    private E findMax(BinaryNode<E> root){
        while(root.right!= null){
            root = root.right;
        }
        //root.right is null (current root is the right most node)
        return root.data;
    }

    public E findMin(){
        return findMin(overallRoot);
    }

    private E findMin(BinaryNode<E> root){
        while(root.left!= null){
            root = root.left;
        }
        //root.left is null (current root is the left most node)
        return root.data;
    }

}
