import java.util.List;
import java.util.ArrayList;

/**
 * This class represents the node of the GDI2 B-Tree
 */
public class GDI2BTreeNode 
{
    private GDI2BTreeNode predecessor;	// the parent node
    private List<String> data;		// the list of keys (the data)
    private List<GDI2BTreeNode> children;	// the list of children nodes
    private boolean leaf;			// leaf or no leaf
    //private int depth = 0;                  // Depth of this node
    private int order = 0;                  // Tree-Order
    //TODO ORDER ERROR

    /**
     * Constructor
     * @param parent the predecessor node (if the current node is root, then parent = null)
     */
    public GDI2BTreeNode(GDI2BTreeNode parent){
        this.setPredecessor(parent);
        data = new ArrayList<String>();
        children = new ArrayList<GDI2BTreeNode>();
        leaf = true;

        if(parent == null)
        {
            //setDepth(0);
        } else
        {
            //setDepth(parent.getDepth() + 1);
            order = parent.getOrder();
        }
    }

    /**
     * Finde Node containing data searched for
     * @param entry Searchstring
     * @return Node with data / null
     */
    public GDI2BTreeNode find(String entry) {
        for(int i= 0; i< data.size(); i++)
        {
            int compared = data.get(i).compareTo(entry);

            //it is within this node
            if(compared == 0) {
                return this;
            }

            if(compared > 0) {
                if(children.size() > i){
                    return children.get(i).find(entry);
                } else {
                    return null;
                }
            }
        }

        //last child
        if( children.size() ==  data.size()+1){
            return  children.get(data.size()).find(entry);
        }

        return null;
    }

    /**
     * Add Data to Tree
     *
     * @param entry Data
     * @return New Rootnode (can be old root) / null
     */
    public GDI2BTreeNode add(String entry) {

        for(int i=0; i<data.size();i++)
        {
            int compared = data.get(i).compareTo(entry);

            //entry already in list?
            if(compared == 0){
                return null;
            }

            if(compared > 0)
            {
                if( !isLeaf() &&
                    i < children.size()){
                    return children.get(i).add(entry);
                }

            }
        }

        if( !isLeaf() &&
            data.size() <= children.size()){
            return children.get(children.size()-1).add(entry);
        }

        ////////////////////////////////////////////
        //add to this node?
        if(data.size() < 2*order)
        {
            //insert sorted
            for(int i=0; i<data.size();i++)
            {
                int compared = data.get(i).compareTo(entry);

                //entry already in list?
                if(compared == 0){
                    return null;
                }

                if(compared > 0)
                {
                    data.add(i, entry);
                    return getRoot();
                }
            }

            //last node in list
            data.add(entry);
            //children.add(data.size()-1,null);
            return getRoot();
        //add to children
        } else {
            //if(isLeaf()){

                boolean inserted = false;

                //insert sorted
                for(int i=0; i<data.size();i++)
                {
                   int compared = data.get(i).compareTo(entry);

                   if(compared > 0)
                   {
                        data.add(i, entry);
                        inserted = true;
                        break;
                   }
                }

                if(!inserted){
                    data.add(entry);
                }

                if(data.size() < 2*order){
                    return getRoot();
                }

                return split();
           
        }
    }

    /**
     * Splits Node. Must have order*2+1 keys
     * @return new root
     */
    private GDI2BTreeNode split(){

        if(predecessor == null){
            predecessor = new GDI2BTreeNode(null);
            predecessor.setOrder(order);
        }

        predecessor.setLeaf(false);

        GDI2BTreeNode left = new GDI2BTreeNode(predecessor);
        GDI2BTreeNode right = new GDI2BTreeNode(predecessor);

        ArrayList<String> lista = new ArrayList<String>();

        for(int i=0; i< order; i++){
            lista.add(data.get(i));
        }
        left.setData(lista);

        ArrayList<String> listb = new ArrayList<String>();

        for(int i=order+1; i< 2*order+1; i++){
            listb.add(data.get(i));
        }
        right.setData(listb);

        /*Vector<String> listc = new Vector<String>();

        listc.add(data.get(order));*/
        predecessor.getData().add(data.get(order));
        
        //predecessor.setData(listc);

        if(!isLeaf())
        {
            ArrayList<GDI2BTreeNode> listd = new ArrayList<GDI2BTreeNode>();
            for(int i=0; i< 2*order; i++){
                listd.add(children.get(i));
                listd.get(i).setPredecessor(left);
                //listd.get(i).setDepth(depth+1);
            }
            left.setChildren(listd);
            left.setLeaf(false);

            ArrayList<GDI2BTreeNode> liste = new ArrayList<GDI2BTreeNode>();
            for(int i=2*order; i <children.size(); i++){
                liste.add(children.get(i));
                liste.get(i-2*order).setPredecessor(right);
                //liste.get(i-2*order).setDepth(depth+1);
            }
            right.setChildren(liste);
            right.setLeaf(false);
        }

        int index = predecessor.children.indexOf(this);
        if(index < 0){
            index = 0;
        }

        predecessor.children.remove(this);
        predecessor.children.add(index,left);
        predecessor.children.add(index+1,right);

        if(predecessor.countKeys(false) > 2*order)
        {
            return predecessor.split();
        }

        return getRoot();
    }

    public boolean del(String entry) {

        //finde knoten
        GDI2BTreeNode node = find(entry);

        //nicht gefunden
        if(node == null){
            return false;
        }

        //rufe knoten auf:
        if(node != this){
            return node.del(entry);
        }


        /*int index = data.indexOf(entry);

        if(index < 0){
            return false;
        }

        data.remove(entry);

        if(children.size() > index ||
           data.size() <= order)
        {
            return rotate(index);
        }

        return true;*/

        //Trivalfall:
        if(isLeaf())
        {
            if(data.size() > order)
            {
                return data.remove(entry);
            } else            
            {
                data.remove(entry);
                if( predecessor == null ||
                    predecessor.rotate(this, entry)){
                    //predecessor.del(entry);
                    return true;
                }

                return false;
                
            }
        }

        //Innerer Knoten:
        if(data.size() > order)
        {
            //TODO CHECK ob kinder auch immer an der richtigen position
            int index = data.indexOf(entry);

            //links
            if(children.get(index).countKeys(false) > order)
            {
                String newentry = children.get(index).getLastDataEntry();

                if(newentry == null)
                {
                    return false;
                }

                //setze daten
                data.set(index, newentry);

                //lösche
                children.get(index).del(newentry);

            //rechts
            } else if(children.get(index+1).countKeys(false) > order)
            {
                String newentry = children.get(index+1).getFirstDataEntry();

                if(newentry == null)
                {
                    return false;
                }

                //setze daten
                data.set(index, newentry);

                //lösche
                children.get(index+1).del(newentry);
            } else
            {

                if(rotate(children.get(index), entry))
                {
                    children.get(index).del(entry);
                    return true;
                }

                return false;

            }
        } else
        {
            data.remove(entry);
            if( predecessor == null &&
                children.size() > 2)
            {
               //return rotateLeft(children.size()-2,true);
               return false; //error here
            } else
            if( predecessor.rotate(this, entry)){
                //predecessor.del(entry);
                return true;
            }

            return false;
        }

        //TODO check
        return false;

    }

    public boolean del(int index)
    {
        if(data.size() <= index)
        {
            return false;
        }

        return del(data.get(index));
    }

    

    private boolean rotate(GDI2BTreeNode underflowNode, String entry)
    {
        int index = children.indexOf(underflowNode);

        if(index < 0 )
        {
            //error
            return false;
        }

        if(index == 0)
        {
            if(rotateLeft(index, false)){
                return true;
            }
            return rotateLeft(index,true);
        }
        
        if(index == children.size() -1)
        {
            if(rotateRight(index, false)){
                return true;
            }
            return rotateLeft(index,true);
        }

        if( rotateLeft(index, false) ||
            rotateRight(index, false)){
            return true;
        } 

        return rotateLeft(index,true);

    }
    
    private boolean rotateLeft(int index, boolean forced)
    {
        //gibt es nächstes kind?
        if(children.size() > index+1) {
            if(children.get(index+1).countKeys(false) <= order){
                if(!rotateLeft(index+1, forced)){ return false;}
            }

            //hole 1 key
            String rightele = children.get(index+1).getFirstDataEntry();

            //löschen 1 key
            children.get(index+1).getData().remove(0);

            //hole key in data
            String topele = data.get(index);

            //schieb in predecessor @ index
            data.set(index, rightele);

            //shiebe wert von predecessor@index -> children(index) an letzte position(add)
            //children.get(index).add(topele);
            if(children.get(index).getData().size() >= 2*order)
            {
                GDI2BTreeNode node = new GDI2BTreeNode(this);
                node.getData().add(topele);
                node.children.add(children.get(index));
                node.children.get(node.children.size()-1).setPredecessor(node);

                children.set(index,node);


            } else
            {
                children.get(index).add(topele);
            }

            if(children.get(index+1).children.size() > 0)
            {
                children.get(index).children.add(children.get(index+1).children.get(0));
                children.get(index).children.get(children.get(index).children.size()-1).setPredecessor(children.get(index));
                children.get(index+1).children.remove(0);
            }

            return true;
        }
        
        if(forced){
            //hole 1 key
            String rightele = children.get(index).getFirstDataEntry();

            if(rightele == null){
                children.remove(index);
            }

            if( children.size() <= order ||
               (predecessor == null && children.size() < 2)){

                int count = 0;

                while(children.size() > 0)
                {
                    while(children.get(0).getData().size() > 0)
                    {
                        data.add(count, children.get(0).getData().get(0));
                        children.get(0).getData().remove(0);

                        if(children.get(0).getChildren().size() > 0)
                        {
                            children.add(count, children.get(0).getChildren().get(0));
                            children.get(children.size()-1).setPredecessor(this);
                            children.get(0).getChildren().remove(0);
                        }

                        count++;
                    }

                    children.remove(0);
                }

                if(children.size() == 0)
                {
                    leaf = true;
                }

                if(predecessor.getChildren().indexOf(this) == predecessor.getChildren().size()-1){
                    return predecessor.rotateRight(predecessor.getChildren().indexOf(this), forced);
                } else {
                    return predecessor.rotateLeft(predecessor.getChildren().indexOf(this), forced);
                }
                
            }
        }
        

       return false;
        
    }

    public boolean rotateRight(int index, boolean forced)
    {
        //gibt es letzte kind?
        if( children.size() > index-1 &&
            index > 0) {
            if(children.get(index-1).countKeys(false) <= order){
                return rotateRight(index-1, forced);
            }

            //hole 1 key
            String leftele = children.get(index-1).getLastDataEntry();

            //löschen 1 key
            children.get(index-1).getData().remove(children.get(index-1).getData().size()-1);

            //hole key in data
            String topele = data.get(index);

            //schieb in predecessor @ index
            data.set(index, leftele);

            //shiebe wert von predecessor@index -> children(index) an letzte position(add)
            //children.get(index).add(topele);
            if(children.get(index-1).getData().size() >= 2*order)
            {
                GDI2BTreeNode node = new GDI2BTreeNode(this);
                node.getData().add(topele);
                node.children.add(children.get(index-1));
                node.children.get(node.children.size()-1).setPredecessor(node);

                children.set(index-1,node);


            } else
            {
                children.get(index-1).add(topele);
            }

            if(children.get(index).children.size() > 0)
            {
                children.get(index-1).children.add(children.get(index).children.get(0));
                children.get(index-1).children.get(children.get(index-1).children.size()-1).setPredecessor(children.get(index-1));
                children.get(index).children.remove(0);
            }

            return true;
        }

        if(forced){
            //hole 1 key

            String lefttele = null;
            
            while((lefttele = children.get(index).getLastDataEntry()) != null){

                data.add(0,lefttele);
                children.get(index).getData().remove(children.get(index).getData().size()-1);
            }

            int count  = 0;

            while(children.get(index+count).children.size() > 0)
            {
                children.add(0,children.get(index+count).children.get(children.get(index+count).children.size()-1));
                children.get(0).setPredecessor(this);
                count++;
                children.get(index+count).children.remove(children.get(index+count).children.size()-1);
            }

            if(children.get(index+count).getData().size() == 0)
            {
                children.remove(index+count);
            }
            
            return true;
        }


       return false;
    }

    /**
     * Simple Functions
     */

    /**
     * Checks if the node has keys/data
     * @return true - if the node has keys/data, otherwise false
     */
    public boolean hasKeys(){
        return (data.size() > 0);
    }

    /**
     * Checks if the node has children
     * @return true - if the node has children, otherwise false
     */
    public boolean hasChildren(){
        return (children.size() > 0);
        //TODO
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString()
    {
        String result = "[";

        for(int i= 0; i< 2*order; i++)
        {
            if(data.size() > i){
                result += data.get(i) + ",";
            } else{
                result += "#,";
            }
        }

        //remove last comma
        return result.substring(0,result.length()-1)+"]";
    }

    public int countKeys(boolean recursiv)
    {
        int count = data.size();

        if(recursiv){
            for(int i = 0; i< children.size(); i++){
                if(children.get(i) != null){
                    count += children.get(i).countKeys(recursiv);
                }
            }
        }

        return count;
    }

    public int getTotalDepth()
    {
        if(isLeaf())
        {
            return 1;
        }

        return 1 + children.get(0).getTotalDepth();
    }

    public int getDepth()
    {
        int depth = 1;

        GDI2BTreeNode node = predecessor;

        while(node != null)
        {
            node = node.predecessor;
            depth++;
        }

        return depth;
    }

    private GDI2BTreeNode getRoot()
    {
        GDI2BTreeNode node = this;
        while(node.getPredecessor() != null)
        {
            node = node.getPredecessor();
        }

        return node;
    }

    /**
     * Gretter & Setters
     */
    
    public int getOrder(){
        return order;
    }

    public void setOrder(int o){
        order = o;
    }

    /*private void setDepth(int depth){
        this.depth = depth;

        for(int i=0; i< children.size(); i++)
        {
            children.get(i).setDepth(depth+1);
        }
    }*/

    public String getDataEntry(int index)
    {
        if(data.size() <= index ||
           index < 0){
            return null;
        }

        return data.get(index);
    }
    
    public String getLastDataEntry()
    {
        return getDataEntry(data.size()-1);
    }

    public String getFirstDataEntry()
    {
        return getDataEntry(0);
    }

    /**
     * @return the data
     */
    public List<String> getData() {
            return data;
    }
    /**
     * @param data the data to set
     */
    public void setData(List<String> data) {
            this.data = data;
    }
    /**
     * @return the children
     */
    public List<GDI2BTreeNode> getChildren() {
            return children;
    }
    /**
     * @param children the children to set
     */
    public void setChildren(List<GDI2BTreeNode> children) {
            this.children = children;
    }
    /**
     * Checks if the node is a leaf
     * @return true - if the node is a leaf, otherwise false
     */
    public boolean isLeaf()
    {
            return this.leaf;
    }
    /**
     * @param leaf the leaf to set
     */
    public void setLeaf(boolean leaf) {
            this.leaf = leaf;
    }

    private void setPredecessor(GDI2BTreeNode parent) {
        this.predecessor = parent;
    }

    private GDI2BTreeNode getPredecessor(){
        return this.predecessor;
    }

    /*public int getDepth() {
        return depth;
    }*/

}
