#include <utility>

/**
 * Copyright (c) 2007-2012, Timothy Stack
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * * Neither the name of Timothy Stack nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @file bookmarks.hh
 */

#ifndef bookmarks_hh
#define bookmarks_hh

#include <map>
#include <set>
#include <string>
#include <vector>
#include <algorithm>

#include "base/lnav_log.hh"

struct bookmark_metadata {
    static std::set<std::string> KNOWN_TAGS;

    std::string bm_name;
    std::string bm_comment;
    std::vector<std::string> bm_tags;

    void add_tag(const std::string &tag) {
        if (std::find(this->bm_tags.begin(),
                      this->bm_tags.end(),
                      tag) == this->bm_tags.end()) {
            this->bm_tags.push_back(tag);
        }
    };

    bool remove_tag(const std::string &tag) {
        auto iter = std::find(this->bm_tags.begin(),
                              this->bm_tags.end(),
                              tag);
        bool retval = false;

        if (iter != this->bm_tags.end()) {
            this->bm_tags.erase(iter);
            retval = true;
        }
        return retval;
    };

    bool empty() {
        return this->bm_name.empty() &&
               this->bm_comment.empty() &&
               this->bm_tags.empty();
    };

    void clear() {
        this->bm_comment.clear();
        this->bm_tags.clear();
    };
};

/**
 * Extension of the STL vector that is used to store bookmarks for
 * files being viewed, where a bookmark is just a particular line in
 * the file(s).  The value-added over the standard vector are some
 * methods for doing content-wise iteration.  In other words, given a
 * value that may or may not be in the vector, find the next or
 * previous value that is in the vector.
 *
 * @param LineType The type used to store line numbers.  (e.g.
 *   vis_line_t or content_line_t)
 *
 * @note The vector is expected to be sorted.
 */
template<typename LineType>
class bookmark_vector : public std::vector<LineType> {
    typedef std::vector<LineType> base_vector;

public:
    typedef typename base_vector::size_type       size_type;
    typedef typename base_vector::iterator        iterator;
    typedef typename base_vector::const_iterator  const_iterator;

    /**
     * Insert a bookmark into this vector, but only if it is not already in the
     * vector.
     *
     * @param vl The line to bookmark.
     */
    iterator insert_once(LineType vl)
    {
        iterator lb, retval;

        require(vl >= 0);

        lb = std::lower_bound(this->begin(), this->end(), vl);
        if (lb == this->end() || *lb != vl) {
            this->insert(lb, vl);
            retval = this->end();
        }
        else {
            retval = lb;
        }

        return retval;
    };

    std::pair<iterator, iterator> equal_range(LineType start, LineType stop) {
        auto lb = std::lower_bound(this->begin(), this->end(), start);

        if (stop == LineType(-1)) {
            return std::make_pair(lb, this->end());
        } else {
            auto up = std::upper_bound(this->begin(), this->end(), stop);

            return std::make_pair(lb, up);
        }
    };

    /**
     * @param start The value to start the search for the next bookmark.
     * @return The next bookmark value in the vector or -1 if there are
     * no more remaining bookmarks.  If the 'start' value is a bookmark,
     * the next bookmark is returned.  If the 'start' value is not a
     * bookmark, the next highest value in the vector is returned.
     */
    LineType next(LineType start) const;

    /**
     * @param start The value to start the search for the previous
     * bookmark.
     * @return The previous bookmark value in the vector or -1 if there
     * are no more prior bookmarks.
     * @see next
     */
    LineType prev(LineType start) const;
};

/**
 * Dummy type whose instances are used to distinguish between
 * bookmarks maintained by different source modules.
 */
class bookmark_type_t {
public:
    typedef std::vector<bookmark_type_t *>::iterator type_iterator;

    static type_iterator type_begin() {
        return get_all_types().begin();
    };

    static type_iterator type_end() {
        return get_all_types().end();
    };

    static bookmark_type_t *find_type(const std::string &name) {
        type_iterator iter = find_if(type_begin(), type_end(), mark_eq(name));
        bookmark_type_t *retval = NULL;

        if (iter != type_end()) {
            retval = (*iter);
        }
        return retval;
    };

    static std::vector<bookmark_type_t *> &get_all_types() {
        static std::vector<bookmark_type_t *> all_types;

        return all_types;
    };

    bookmark_type_t(std::string name) : bt_name(std::move(name)) {
        get_all_types().push_back(this);
    };

    const std::string &get_name() const {
        return this->bt_name;
    };

private:
    struct mark_eq {
        mark_eq(const std::string &name) : me_name(name) { };

        bool operator()(bookmark_type_t *bt) {
            return bt->bt_name == this->me_name;
        };

        const std::string &me_name;
    };

    const std::string bt_name;
};

template<typename LineType>
LineType bookmark_vector<LineType>::next(LineType start) const
{
    LineType retval(-1);

    require(start >= -1);

    auto ub = upper_bound(this->cbegin(), this->cend(), start);
    if (ub != this->cend()) {
        retval = *ub;
    }

    ensure(retval == -1 || start < retval);

    return retval;
}

template<typename LineType>
LineType bookmark_vector<LineType>::prev(LineType start) const
{
    LineType retval(-1);

    require(start >= 0);

    auto lb = lower_bound(this->cbegin(), this->cend(), start);
    if (lb != this->cbegin()) {
        lb    -= 1;
        retval = *lb;
    }

    ensure(retval < start);

    return retval;
}

/**
 * Map of bookmark types to bookmark vectors.
 */
template<typename LineType>
struct bookmarks {
    typedef std::map<bookmark_type_t *, bookmark_vector<LineType> > type;
};

#endif
