
import java.util.ArrayList;

/**
 * This class represents the GDI2 B-Tree
 */

public class GDI2BTree 
{
	private GDI2BTreeNode root;	// the root of the B-Tree
	private int order;		// the order (=k) of the B-Tree
	
	/**
         * Constructor
         */
        public GDI2BTree(){
            root = new GDI2BTreeNode(null);
        }

	/**
	 * This method inserts into the B-Tree the given key/entry (String)
	 * @param entry the value to add into the tree
	 * @return true - if successful, false otherwise
	 */
	public boolean addData(String entry) {
                if(root == null){
                    return false;
                }

                //if current root is splitted new root is created
                GDI2BTreeNode newroot = root.add(entry);

                //error
                if(newroot == null)
                {
                    return false;
                }

                root = newroot;
                return true;
	}

	/**
	 * This method deletes from the B-Tree the given key/entry (String)
	 * @param entry the value to delete from the tree
	 * @return true - if successful, false otherwise
	 */
	public boolean delData(String entry) {
		if(root == null){
                    return false;
                }

                if(!root.del(entry)){
                    return false;
                }

                return true;
	}

	/**
	 * Checks if the given entry/key can be found in the B-Tree
	 * @param entry The entry/key which is to be found
	 * @return the B-Tree node containing the given key/entry (String), otherwise null
	 */
	public GDI2BTreeNode findData(String entry) {
		if(root == null){
                    return null;
                }

                return root.find(entry);
	}

	/**
	 * Checks if the B-Tree is empty
	 * @return true - if B-Tree empty, otherwise false
	 */
	public boolean isBTreeEmpty(){
            return !(root == null || root.hasKeys());
	}

	/**
	 * This method prints the B-Tree
	 */
	public void printBTree()
	{
            if(isBTreeEmpty()){
                System.out.println("B-Tree is empty");
                return;
            }
		
            System.out.println( "GDI2 B-Tree: order = " +
                                this.getOrder() +
                                ", keys = " +
                                root.countKeys(true) +
                                ", height = " +
                                (root.getTotalDepth()));
            System.out.println("------------------------------------------------");

            ArrayList<GDI2BTreeNode> list = new ArrayList<GDI2BTreeNode>();
            list.add(root);
            int lastdepth = 1;

            while(  list.size() > 0 &&
                    list.get(0) != null)
            {
                //next level in tree
                if(list.get(0).getDepth() > lastdepth){
                    System.out.println("\n\n");
                    lastdepth++;
                }

                //get string representation of current node
                String node = list.get(0).toString();

                //print node
                System.out.print(node + " ");

                //add children to processlist
                list.addAll(list.get(0).getChildren());

                //remove current node
                list.remove(0);
            }

            //new line
            System.out.println();
	}

	/**
	 * This method helps to check the correctness 
	 * of the B-Tree after adding/deleting data.
	 *
	 * You can and should extend/modify this method 
	 * to check the correctness of your B-Tree implementation.
	 * You are allowed to change the signature 
	 * of this methods according to your needs.
	 * 
	 * We will use a similar method 
	 * correctnessCheckPrivate(...) to test your implementation.
	 */
	public void correctnessCheckPublic()
	{
		// OPTIONAL TODO: extend/modify this method
		
		// the following checks are only some examples 
		
		// check if the root has data
		if (getRoot().hasKeys())
		{
			// check if the number of children is correct (root not being a leaf)
			if (!getRoot().isLeaf() && (getRoot().getData().size() + 1 < getRoot().getChildren().size()))
			{
				throw new GDI2BTreeFileFormatException(
						GDI2BTreeFileFormatException.GDI2TypeOfException.BTREE, 
						"Too many children.");
			}
			// check if the number of keys is conform to the given order
			if (getRoot().getData().size() > 2 * getOrder()){
				throw new GDI2BTreeFileFormatException(
						GDI2BTreeFileFormatException.GDI2TypeOfException.BTREE, 
						"Too many keys - the given order is violated.");
                        }
		}
		// check if the root has children
		if (getRoot().hasChildren())
		{
			// check if root is a leaf
			if (getRoot().isLeaf()){
				throw new GDI2BTreeFileFormatException(
						GDI2BTreeFileFormatException.GDI2TypeOfException.BTREE, 
						"It should not be possible for a leaf to have children.");
                        }
			// check if the correct number of children is provided
			if (getRoot().getData().size() >= getRoot().getChildren().size()){
				throw new GDI2BTreeFileFormatException(
						GDI2BTreeFileFormatException.GDI2TypeOfException.BTREE, 
						"Too few children.");
                        }
		}
	}

	/**
	 * @param root the root to set
	 */
	public void setRoot(GDI2BTreeNode root) {
		this.root = root;
	}

	/**
	 * @return the root
	 */
	public GDI2BTreeNode getRoot() {
		return root;
	}

	/**
	 * @param order the order to set
	 */
	public void setOrder(int order) {
		this.order = order;
                if(root != null){
                    this.root.setOrder(order);
                }
	}

	/**
	 * @return the order
	 */
	public int getOrder() {
		return order;
	}
}
