// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#pragma once

#include <map>
#include <memory>
#include <string>
#include <vector>

namespace doris {

// This tree is usd for manage restful api path.
template<class T>
class PathTrie {
public:
    PathTrie() :
            _root("/", "*"),
            _root_value(nullptr),
            _separator('/') {
    };

    ~PathTrie() {
        if (_root_value != nullptr) {
            _allocator.destroy(_root_value);
            _allocator.deallocate(_root_value, 1);
        }
    }

    class TrieNode {
    public:
        TrieNode(const std::string& key, const std::string& wildcard) :
                _value(nullptr), 
                _wildcard(wildcard) {
            if (is_named_wildcard(key)) {
                _named_wildcard = extract_template(key);
            }
        }

        TrieNode(const std::string& key, const T& value, const std::string& wildcard) :
                _value(nullptr),
                _wildcard(wildcard) {
            _value = _allocator.allocate(1);
            _allocator.construct(_value, value);
            if (is_named_wildcard(key)) {
                _named_wildcard = extract_template(key);
            }
        }

        ~TrieNode() {
            for (auto& iter : _children) {
                delete iter.second;
                iter.second = nullptr;
            }
            if (_value != nullptr) {
                _allocator.destroy(_value);
                _allocator.deallocate(_value, 1);
            }
        }

        // Return true if insert sucess.
        bool insert(const std::vector<std::string> path, int index, const T& value) {
            if (index >= path.size()) {
                return false;
            }
            const std::string& token = path[index];
            std::string key = token;

            if (is_named_wildcard(token)) {
                key = _wildcard;
            }

            TrieNode* node = get_child(key);

            if (node == nullptr) {
                // no exist child for this key
                if (index == path.size() - 1) {
                    node = new TrieNode(token, value, _wildcard);
                    _children.insert(std::make_pair(key, node));
                    return true;
                } else {
                    node = new TrieNode(token, _wildcard);
                    _children.insert(std::make_pair(key, node));
                }
            } else {
                // If this is a template, set this to the node
                if (is_named_wildcard(token)) {
                    std::string temp = extract_template(token);
                    if (node->_named_wildcard.empty() 
                            || node->_named_wildcard.compare(temp) == 0) {
                        node->_named_wildcard = temp;
                    } else {
                        // Duplicated
                        return false;
                    }
                }
                if (index == path.size() - 1) {
                    if (node->_value == nullptr) {
                        node->_value = _allocator.allocate(1);
                        _allocator.construct(node->_value, value);
                        return true;
                    }
                    // Already register by other path
                    return false;
                }
            }
            return node->insert(path, index + 1, value);
        }

        bool retrieve(const std::vector<std::string> path, int index, 
                      T* value, std::map<std::string, std::string>* params) {
            // check max index
            if (index >= path.size()) {
                return false;
            }
            bool use_wildcard = false;
            const std::string& token = path[index];
            TrieNode* node = get_child(token);
            if (node == nullptr) {
                node = get_child(_wildcard);
                if (node ==  nullptr) {
                    return false;
                }
                use_wildcard = true;
            } else {
                // If we the last one, but we have no value, check wildcard
                if (index == path.size() - 1 
                        && node->_value == nullptr 
                        && get_child(_wildcard) != nullptr) {
                    node = get_child(_wildcard);
                    use_wildcard = true;
                } else {
                    use_wildcard = (token.compare(_wildcard) == 0);
                }
            }

            put(params, node, token);

            if (index == path.size() - 1) {
                if (node->_value == nullptr) {
                    return false;
                }
                _allocator.construct(value, *node->_value);
                return true;
            }

            // find exact
            if (node->retrieve(path, index + 1, value, params)) {
                return true;
            }

            // backtrace to test if wildcard can match
            if (!use_wildcard) {
                node = get_child(_wildcard);
                if (node != nullptr) {
                    put(params, node, token);
                    return node->retrieve(path, index + 1, value, params);
                }
            }
            return false;
        }
    private:
        bool is_named_wildcard(const std::string& key) {
            if (key.find('{') != std::string::npos 
                    && key.find('}') != std::string::npos) {
                return true;
            }
            return false;
        }

        std::string extract_template(const std::string& key) {
            std::size_t left = key.find_first_of('{') + 1;
            std::size_t right = key.find_last_of('}');
            return key.substr(left, right - left);
        }

        TrieNode* get_child(const std::string& key) {
            auto pair = _children.find(key);
            if (pair == _children.end()) {
                return nullptr;
            }
            return pair->second;
        }

        void put(std::map<std::string, std::string>* params, 
                TrieNode* node, const std::string& token) {
            if (params != nullptr && !node->_named_wildcard.empty()) {
                params->insert(std::make_pair(node->_named_wildcard, token));
            }
        }

        T* _value;
        std::string _wildcard;
        std::string _named_wildcard;
        std::map<std::string, TrieNode*> _children;
        std::allocator<T> _allocator;
    };

    bool insert(const std::string& path, const T& value) {
        std::vector<std::string> path_array;
        split(path, &path_array);
        if (path_array.empty()) {
            if (_root_value == nullptr) {
                _root_value = _allocator.allocate(1);
                _allocator.construct(_root_value, value);
                return true;
            } else {
                return false;
            }
        }
        int index = 0;
        if (path_array[0].empty()) {
            index = 1;
        }
        return _root.insert(path_array, index, value);
    }

    bool retrieve(const std::string& path, T* value) {
        return retrieve(path, value, nullptr);
    }

    bool retrieve(const std::string& path, T* value, 
                  std::map<std::string, std::string>* params) {
        if (path.empty()) {
            if (_root_value == nullptr) {
                return false;
            } else {
                _allocator.construct(value, *_root_value);
                return true;
            }
        }
        std::vector<std::string> path_array;
        split(path, &path_array);
        if (path_array.empty()) {
            if (_root_value == nullptr) {
                return false;
            } else {
                _allocator.construct(value, *_root_value);
                return true;
            }
        }
        int index = 0;
        if (path_array[0].empty()) {
            index = 1;
        }
        return _root.retrieve(path_array, index, value, params);
    }

private:
    void split(const std::string& path, std::vector<std::string>* array) {
        const char* path_str = path.c_str();
        std::size_t start = 0;
        std::size_t pos = 0;
        for (; pos < path.length(); ++pos) {
            if (path_str[pos] == _separator) {
                if (pos - start > 0) {
                    array->push_back(path.substr(start, pos - start));
                }
                start = pos + 1;
            }
        }
        if (pos - start > 0) {
            array->push_back(path.substr(start, pos - start));
        }
    }

    TrieNode _root;
    T* _root_value;
    char _separator;
    std::allocator<T> _allocator;
};

}
