// Copyright 2015, Tobias Hermann and the FunctionalPlus contributors.
// https://github.com/Dobiasd/FunctionalPlus
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)

#pragma once

#include <fplus/container_common.hpp>
#include <fplus/maybe.hpp>

#include <queue>
#include <vector>

namespace fplus {

template <typename T>
struct tree {
    tree(const T& value, const std::vector<tree<T>>& children)
        : value_(value)
        , children_(children)
    {
    }
    T value_;
    std::vector<tree<T>> children_;
};

namespace internal {
    template <typename T>
    tree<T> make_singleton_tree(const T& x)
    {
        return { x, {} };
    }
} // namespace internal

namespace internal {

    template <typename BinaryPredicate, typename T>
    std::vector<tree<T>> presort_trees(BinaryPredicate tree_is_child_of,
        std::vector<tree<T>> xs_orig)
    {
        auto xs = fplus::convert_container<std::list<tree<T>>>(xs_orig);
        std::vector<tree<T>> result;
        while (!xs.empty()) {
            for (auto it = std::begin(xs); it != std::end(xs);) {
                bool has_children = false;
                for (auto it_rest = std::begin(xs); it_rest != std::end(xs); ++it_rest) {
                    if (it_rest != it && tree_is_child_of(*it_rest, *it)) {
                        has_children = true;
                    }
                }
                if (!has_children) {
                    result.push_back(*it);
                    it = xs.erase(it);
                } else {
                    ++it;
                }
            }
        }
        return result;
    }

    template <typename BinaryPredicate, typename TreeCont> // todo: name?
    TreeCont trees_from_sequence_helper(
        BinaryPredicate tree_is_child_of, TreeCont xs_unsorted)
    {
        TreeCont result;
        auto xs = presort_trees(tree_is_child_of, xs_unsorted);
        for (auto it = std::begin(xs); it != std::end(xs); ++it) {
            const auto find_pred = bind_1st_of_2(tree_is_child_of, *it);
            auto it_find_begin = it;
            internal::advance_iterator(it_find_begin, 1);
            auto parent_it = std::find_if(it_find_begin, std::end(xs), find_pred);
            if (parent_it != std::end(xs)) {
                parent_it->children_.push_back(*it);
            } else {
                result.push_back(*it);
            }
        }
        return result;
    }

} // namespace internal

// API search type: trees_from_sequence : (((a, a) -> Bool), [a]) -> [Tree a]
// fwd bind count: 1
// Converts the sequence into a tree considering the given binary predicate.
template <typename BinaryPredicate, typename Container> // todo: name?
std::vector<tree<typename Container::value_type>> trees_from_sequence(
    BinaryPredicate is_child_of, const Container& xs)
{
    internal::check_binary_predicate_for_container<BinaryPredicate, Container>();
    typedef typename Container::value_type T;
    typedef tree<T> Tree;
    const auto singletons = transform_convert<std::vector<Tree>>(
        internal::make_singleton_tree<T>, xs);
    const auto tree_is_child_of =
        [is_child_of](const tree<T>& a, const tree<T>& b) -> bool {
        return is_child_of(a.value_, b.value_);
    };
    return internal::trees_from_sequence_helper(
        tree_is_child_of, std::move(singletons));
}

namespace internal {

    // -1 = a < b
    //  0 = a == b
    //  1 = b < a
    template <typename T>
    int tree_cmp(const tree<T>& a, const tree<T>& b)
    {
        if (a.value_ < b.value_) {
            return -1;
        } else if (b.value_ < a.value_) {
            return 1;
        } else {
            const auto results = zip_with(tree_cmp<T>,
                sort_by(tree_cmp<T>, a.children_),
                sort_by(tree_cmp<T>, b.children_));
            return just_with_default(0, find_first_by(bind_1st_of_2(is_not_equal<int>, 0), results));
        }
    }

    template <typename T>
    bool tree_less(const tree<T>& a, const tree<T>& b)
    {
        return tree_cmp(a, b) < 0;
    }

} // namespace internal

namespace internal {

    template <typename T>
    bool are_normalized_trees_equal(const tree<T>& a, const tree<T>& b)
    {
        if (a.value_ != b.value_ || a.children_.size() != b.children_.size()) {
            return false;
        } else {
            return all(zip_with(are_normalized_trees_equal<T>,
                a.children_, b.children_));
        }
    }

    template <typename T>
    tree<T> normalize_tree(tree<T> x)
    {
        x.children_ = sort_by(
            internal::tree_less<T>,
            transform(normalize_tree<T>, x.children_));
        return x;
    }

} // namespace internal

// API search type: are_trees_equal : (Tree a, Tree a) -> Bool
// fwd bind count: 1
template <typename T>
bool are_trees_equal(const tree<T>& a, const tree<T>& b)
{
    return internal::are_normalized_trees_equal(
        internal::normalize_tree(a), internal::normalize_tree(b));
}

// API search type: tree_size : Tree a -> Int
// fwd bind count: 0
// A tree with only one element (root) has size 1.
template <typename T>
std::size_t tree_size(const tree<T>& x)
{
    return 1 + sum(transform(tree_size<T>, x.children_));
}

// API search type: tree_depth : Tree a -> Int
// fwd bind count: 0
// A tree with only one element (root) has depth 1.
template <typename T>
std::size_t tree_depth(const tree<T>& x)
{
    return 1 + just_with_default<std::size_t>(0, maximum_maybe(transform(tree_depth<T>, x.children_)));
}

// API search type: flatten_tree_depth_first : Tree a -> [a]
// fwd bind count: 0
// Like in DFS.
template <typename T>
std::vector<T> flatten_tree_depth_first(const tree<T>& x)
{
    return prepend_elem(x.value_,
        transform_and_concat(flatten_tree_depth_first<T>, x.children_));
}

// API search type: flatten_tree_breadth_first : Tree a -> [a]
// fwd bind count: 0
// Like in BFS.
template <typename T>
std::vector<T> flatten_tree_breadth_first(const tree<T>& x)
{
    std::vector<T> result;
    result.reserve(tree_size(x));
    std::queue<const tree<T>*> q;
    q.push(&x);
    while (!q.empty()) {
        const auto current = q.front();
        q.pop();
        result.push_back(current->value_);
        for (const auto& c : current->children_) {
            q.push(&c);
        }
    }
    return result;
}

} // namespace fplus
