/**
 * @file avltree_node_imp.hpp
 *
 * CopyRight F. J. Madrid-Cuevas <fjmadrid@uco.es>
 *
 * Sólo se permite el uso de este código en la docencia de las asignaturas sobre
 * Estructuras de Datos de la Universidad de Córdoba.
 *
 * Está prohibido su uso para cualquier otro objetivo.
 */
#pragma once
#include <cassert>
#include <avltree_node.hpp>

template <class T>
typename AVLTNode<T>::Ref AVLTNode<T>::This() const
{
    return This_;
}

template <class T>
AVLTNode<T>::AVLTNode(T const &it)
{
    _item = it;
    _height = 0;

    _left=nullptr;
    _right=nullptr;
    _parent=nullptr;
    
    assert(item() == it);
    assert(left() == nullptr);
    assert(right() == nullptr);
#ifndef __ONLY_BSTREE__
    assert(parent() == nullptr);
    assert(height() == 0);
    assert(check_height_invariant());
#endif
}

template <class T>
typename AVLTNode<T>::Ref AVLTNode<T>::create(T const &it)
{
    auto This = typename AVLTNode<T>::Ref(new AVLTNode(it));
    This->This_ = This;
    return This;
}

template <class T>
const T &AVLTNode<T>::item() const
{
    return _item;
}

template <class T>
typename AVLTNode<T>::Ref const &AVLTNode<T>::left() const
{
    return _left;
}

template <class T>
typename AVLTNode<T>::Ref &AVLTNode<T>::left()
{
    return _left;
}

template <class T>
typename AVLTNode<T>::Ref const &AVLTNode<T>::right() const
{
    return _right;
}

template <class T>
typename AVLTNode<T>::Ref &AVLTNode<T>::right()
{
    return _right;
}

template <class T>
typename AVLTNode<T>::Ref const &AVLTNode<T>::child(int dir) const
{
    assert(dir == 0 || dir == 1);

    return (dir == 0) ? _left : _right;
}

template <class T>
typename AVLTNode<T>::Ref &AVLTNode<T>::child(int dir)
{
    assert(dir == 0 || dir == 1);

    return (dir == 0) ? _left : _right;
}

template <class T>
typename AVLTNode<T>::Ref const &AVLTNode<T>::parent() const
{
    return _parent;
}

template <class T>
typename AVLTNode<T>::Ref &AVLTNode<T>::parent()
{
    return _parent;
}

template <class T>
int AVLTNode<T>::height() const
{
    return _height;
}

template <class T>
int AVLTNode<T>::balance_factor() const
{
    int hl,hr;
    if(left()==nullptr){
        hl=-1;
    }else{
        hl=left()->height();
    }

    if(right()==nullptr){
        hr=-1;
    }else{
        hr=right()->height();
    }

    return (hr-hl);
}

template <class T>
bool AVLTNode<T>::check_height_invariant() const
{
    bool ret_val = false;
#ifdef __ONLY_BSTREE__
    ret_val = true; // In a BSTree we do not need to check the height invariant.
#else
    int hl,hr;
    if(left()==nullptr){
        hl=-1;
    }else{
        hl=left()->height();
    }

    if(right()==nullptr){
        hr=-1;
    }else{
        hr=right()->height();
    }

    int expected_h = 1 + std::max(hl, hr);
    ret_val = (static_cast<int>(_height) == expected_h);
#endif
    return ret_val;
}

template <class T>
void AVLTNode<T>::update_height()
{
    int hl,hr;
    if(left()==nullptr){
        hl=-1;
    }else{
        hl=left()->height();
    }

    if(right()==nullptr){
        hr=-1;
    }else{
        hr=right()->height();
    }
    
    _height = 1 + std::max(hl, hr);

    assert(check_height_invariant());
}

template <class T>
void AVLTNode<T>::set_item(const T &new_it)
{
    _item=new_it;

    assert(item() == new_it);
}

template <class T>
void AVLTNode<T>::set_parent(AVLTNode<T>::Ref new_parent)
{
    _parent=new_parent;

    assert(parent() == new_parent);
}

template <class T>
void AVLTNode<T>::set_left(Ref new_child)
{

    _left = new_child; //
    if (new_child != nullptr) 
    {
        new_child->set_parent(This()); //
    }

    update_height();
    //
    assert(left() == new_child);
#ifndef __ONLY_BSTREE__
    assert(check_height_invariant());
    assert(!new_child || new_child->parent() == This());
#endif
}

template <class T>
void AVLTNode<T>::set_right(AVLTNode<T>::Ref new_child)
{
    _right = new_child; //
    if (new_child != nullptr) 
    {
        new_child->set_parent(This()); //
    }

    update_height();
    //
    assert(right() == new_child);
#ifndef __ONLY_BSTREE__
    assert(check_height_invariant());
    assert(!new_child || new_child->parent() == This());
#endif
}

template <class T>
void AVLTNode<T>::set_child(int dir, Ref new_child)
{
    assert(dir == 0 || dir == 1);

    if (dir == 0) 
        _left = new_child;
    else 
        _right = new_child;

    if (new_child != nullptr) 
    {
        new_child->set_parent(This()); // El hijo debe apuntar al padre
    }

    update_height();

    assert(check_height_invariant());
    assert(dir == 0 || right() == new_child);
    assert(dir == 1 || left() == new_child);
    assert(!new_child || new_child->parent() == This());
}
