/**
 *
 * @file src/trace/tree/BinaryTree.hpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Mathieu Faverge
 * @author Kevin Coulomb
 * @author Augustin Degomme
 * @author Clement Vuchener
 *
 * @date 2024-07-17
 */
#ifndef BINARYTREE_HPP
#define BINARYTREE_HPP

/*!
 * \file BinaryTree.hpp
 */

#include <vector>
/* -- */
#include "trace/tree/Node.hpp"

/*!
 * \class BinaryTree
 * \brief Store a tree made from a list
 */
template <typename E>
class BinaryTree
{
private:
    Node<E> *_root;

public:
    /*!
     * \brief Constructor
     * \param list List from which the tree is made
     * \param size Number of element in the list
     */
    BinaryTree(E *list[], unsigned int size) {
        if (size == 0 || list == NULL || list[0] == NULL) {
            _root = NULL;
            return;
        }

        // Calculate n and m as size = 2^n - 1 + m and m < 2^n
        int n = 0;
        unsigned int a = 1;
        while (size >= 2 * a - 1) {
            a *= 2;
            n++;
        }
        int m = size - a + 1;

        // Initialize the array
        Node<E> **temp = new Node<E> *[n + 1];
        int start = 0;
        int i = 0;
        for (i = 0; i < n + 1; i++)
            temp[i] = NULL;

        i = 0;
        // Reads elements from the list and empty it
        for (unsigned int j = 0; j < size; j++) {
            E *element = list[j];

            while (temp[i]) // Find the first free element in temp
                i++;

            temp[i] = new Node<E>(element);

            if (i == 0 && m > 0) { // The node is a leaf and its depth is n+1
                m--;
                if (m == 0)
                    start = 1;
            }

            if (i < n && temp[i + 1]) { // The node has a parent
                temp[i]->_parent = temp[i + 1];
                temp[i + 1]->_right_child = temp[i];
            }

            if (i > 0 && temp[i - 1]) { // The node is not a leaf
                temp[i]->_left_child = temp[i - 1];
                temp[i - 1]->_parent = temp[i];
                while (i > 0) {
                    i--;
                    temp[i] = NULL;
                }
                i = start;
            }
        }

        _root = temp[n - 1 + start];

        delete[] temp;
        // delete[] list;
    }

    /*!
     * \brief Constructor
     * \param list List from which the tree is made
     * \param size Number of element in the list
     */
    BinaryTree(E *list, unsigned int size) {
        if (size == 0 || list == NULL) {
            _root = NULL;
            return;
        }

        // Calculate n and m as size = 2^n - 1 + m and m < 2^n
        int n = 0;
        unsigned int a = 1;
        while (size >= 2 * a - 1) {
            a *= 2;
            n++;
        }
        int m = size - a + 1;

        // Initialize the array
        Node<E> **temp = new Node<E> *[n + 1];
        int start = 0;
        int i = 0;
        for (i = 0; i < n + 1; i++)
            temp[i] = NULL;

        i = 0;
        // Reads elements from the list and empty it
        for (unsigned int j = 0; j < size; j++) {
            E *element = &(list[j]);

            while (temp[i]) // Find the first free element in temp
                i++;

            temp[i] = new Node<E>(element);

            if (i == 0 && m > 0) { // The node is a leaf and its depth is n+1
                m--;
                if (m == 0)
                    start = 1;
            }

            if (i < n && temp[i + 1]) { // The node has a parent
                temp[i]->_parent = temp[i + 1];
                temp[i + 1]->_right_child = temp[i];
            }

            if (i > 0 && temp[i - 1]) { // The node is not a leaf
                temp[i]->_left_child = temp[i - 1];
                temp[i - 1]->_parent = temp[i];
                while (i > 0) {
                    i--;
                    temp[i] = NULL;
                }
                i = start;
            }
        }

        _root = temp[n - 1 + start];

        delete[] temp;
        // delete[] list;
    }

    /*!
     * \brief Constructor
     * \param list List from which the tree is made
     * \param size Number of element in the list
     */
    BinaryTree(std::list<E *> &list, unsigned int size) {
        if (size == 0 || list.empty()) {
            _root = nullptr;
            return;
        }

        // Calculate n and m as size = 2^n - 1 + m and m < 2^n
        int n = 0;
        unsigned int a = 1;
        while (size >= 2 * a - 1) {
            a *= 2;
            n++;
        }
        int m = size - a + 1;

        // Initialize the array
        Node<E> **temp = new Node<E> *[n + 1];
        int start = 0;
        int i = 0;
        for (i = 0; i < n + 1; i++)
            temp[i] = nullptr;

        i = 0;
        // Reads elements from the list and empty it
        while (!list.empty()) {
            E *element = list.front();
            list.pop_front();

            while (temp[i]) // Find the first free element in temp
                i++;

            temp[i] = new Node<E>(element);

            if (i == 0 && m > 0) { // The node is a leaf and its depth is n+1
                m--;
                if (m == 0)
                    start = 1;
            }

            if (i < n && temp[i + 1]) { // The node has a parent
                temp[i]->_parent = temp[i + 1];
                temp[i + 1]->_right_child = temp[i];
            }

            if (i > 0 && temp[i - 1]) { // The node is not a leaf
                temp[i]->_left_child = temp[i - 1];
                temp[i - 1]->_parent = temp[i];
                while (i > 0) {
                    i--;
                    temp[i] = nullptr;
                }
                i = start;
            }
        }

        _root = temp[n - 1 + start];

        delete[] temp;
    }

    /*!
     * \brief Constructor
     * \param list List from which the tree is made
     * \param size Number of element in the list
     */
    BinaryTree(std::vector<E *> &list, unsigned int size) {
        if (size == 0 || list.empty()) {
            _root = NULL;
            return;
        }

        // Calculate n and m as size = 2^n - 1 + m and m < 2^n
        int n = 0;
        unsigned int a = 1;
        while (size >= 2 * a - 1) {
            a *= 2;
            n++;
        }
        int m = size - a + 1;

        // Initialize the array
        Node<E> **temp = new Node<E> *[n + 1];
        int start = 0;
        int i = 0;
        for (i = 0; i < n + 1; i++)
            temp[i] = NULL;

        i = 0;
        // Reads elements from the list and empty it
        int it = 0;
        int end = list.size();
        for (it = 0; it < end; it++) {
            E *element = list[it];

            while (temp[i]) // Find the first free element in temp
                i++;

            temp[i] = new Node<E>(element);

            if (i == 0 && m > 0) { // The node is a leaf and its depth is n+1
                m--;
                if (m == 0)
                    start = 1;
            }

            if (i < n && temp[i + 1]) { // The node has a parent
                temp[i]->_parent = temp[i + 1];
                temp[i + 1]->_right_child = temp[i];
            }

            if (i > 0 && temp[i - 1]) { // The node is not a leaf
                temp[i]->_left_child = temp[i - 1];
                temp[i - 1]->_parent = temp[i];
                while (i > 0) {
                    i--;
                    temp[i] = NULL;
                }
                i = start;
            }
        }

        list.clear();
        _root = temp[n - 1 + start];

        delete[] temp;
    }

    /*!
     * \brief Destructor
     */
    ~BinaryTree() {
        delete _root;
        _root = NULL;
    }

    /*!
     * \brief Get the root node of the tree
     */
    Node<E> *get_root() const {
        return _root;
    }

    /*!
     * \fn empty() const
     * \brief Return if the tree is empty
     */
    bool empty() const {
        return (_root == nullptr);
    }
};

#endif
