#pragma once

#include <ostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <type_traits>

namespace zt {
    template<class T>
    class BinarySearchTree {
    public:
        using valueType = std::remove_cv_t<T>;
    private:
        struct Node {
            valueType val;
            Node* leftSon{nullptr};
            Node* rightSon{nullptr};
        };
        Node* root{nullptr};

        void _deleteTree(Node* node){
            if(node == nullptr) return;
            _deleteTree(node->leftSon);
            _deleteTree(node->rightSon);
            delete node;
        }

        void _inorderTraversal(Node* now, void(*func)(Node* now)) const {
            if(now->leftSon != nullptr){
                _inorderTraversal(now->leftSon, func);
            }
            func(now);
            if(now->rightSon != nullptr){
                _inorderTraversal(now->rightSon, func);
            }
        }

        void _toString(const Node* now, std::stringstream& ss) const {
            if(now->leftSon != nullptr){
                _toString(now->leftSon, ss);
            }
            ss << now->val << ", ";
            if(now->rightSon != nullptr){
                _toString(now->rightSon, ss);
            }
        }

    public:
        BinarySearchTree() = default;

        void insert(const valueType& val) {
            Node* newNode = new Node{};
            newNode->val=val;
            if(root == nullptr){
                root = newNode;
                return;
            }
            Node* current = root;
            Node* parent = nullptr;
            while(current != nullptr){
                parent = current;
                if(val < current->val){
                    current = current->leftSon;
                } else {
                    current = current->rightSon;
                }
            }
            if(val < parent->val){
                parent->leftSon = newNode;
            } else {
                parent->rightSon = newNode;
            }
        }

        valueType findMin() const {
            if(root == nullptr){
                throw std::runtime_error("Tree is empty");
            }
            Node* current = root;
            while(current->leftSon != nullptr){
                current = current->leftSon;
            }
            return current->val;
        }

        valueType findMax() const {
            if(root == nullptr){
                throw std::runtime_error("Tree is empty");
            }
            Node* current = root;
            while(current->rightSon != nullptr){
                current = current->rightSon;
            }
            return current->val;
        }

        std::string toString() const {
            std::stringstream ss;
            ss << "{ ";
            if(root != nullptr){
                _toString(root, ss);
            }
            ss << "}";
            return ss.str();
        }
        
        ~BinarySearchTree(){
            _deleteTree(root);
        }
    };
}
