// TypeAhead.hh for FbTk - Fluxbox Toolkit
// Copyright (c) 2007 Fluxbox Team (fluxgen at fluxbox dot org)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef FBTK_TYPEAHEAD_HH
#define FBTK_TYPEAHEAD_HH

#include "ITypeAheadable.hh"
#include <vector>
#include "SearchResult.hh"

namespace FbTk {

template <typename Items, typename Item_Type>
class TypeAhead {
/*

a class template can't be split into separate interface + implementation files, an interface summary is given here:

public:
    void init(Items const &items);

// accessors:
    inline int stringSize() const { return m_searchstr.size(); }
    Items matched() const;

// modifiers:
    Items putCharacter(char ch);
    void putBackSpace();
    void reset()

private:
    SearchResults m_search_results;
    std::string m_searchstr;
    Items const *m_ref;

// helper
    void fillValues(BaseItems const &search, ValueVec &fillin) const;

// reverts to searchstate before current
    void revert();

// search performs iteration and sets state
    void search(char char_to_test);
    void doSearch(char to_test,
            Items const &items,
            SearchResult &mySearchResult) const;
    void doSearch(char to_test,
            BaseItems const &search,
            SearchResult &mySearchResult) const;
*/

public:
    typedef std::vector < ITypeAheadable* > BaseItems;
    typedef BaseItems::const_iterator BaseItemscIt;
    typedef std::vector < SearchResult > SearchResults;
    typedef typename Items::const_iterator ItemscIt;

    void init(Items const &items) { m_ref = &items; }

    inline size_t stringSize() const { return m_searchstr.size(); }

    void seek() {
        if (!m_search_results.empty())
            m_searchstr = m_search_results.back().seekedString();
    }

    Items putCharacter(char ch) {
        if (isprint(ch))
            search(ch);
        return matched();
    }

    void putBackSpace() {
        if (!m_search_results.empty())
            revert();
    }

    void reset() {
        m_searchstr.clear();
        m_search_results.clear();
    }

    Items matched() const {
        Items last_matched;

        if (!m_search_results.empty())
            fillValues(m_search_results.back().result(), last_matched);
        else
            return *m_ref;
        return last_matched;
    }

private:
    SearchResults m_search_results;
    std::string m_searchstr;
    Items const *m_ref; // reference to vector we are operating on

    void fillValues(BaseItems const &search, Items &fillin) const {
        for (BaseItemscIt it = search.begin(); it != search.end(); it++) {
            Item_Type tmp = dynamic_cast<Item_Type>(*it);
            if (tmp)
                fillin.push_back(tmp);
        }
    }

    void revert() {
        m_search_results.pop_back();
        if (m_search_results.empty())
            m_searchstr.clear();
        else
            m_searchstr = m_search_results.back().seekedString();
    }

    void search(char char_to_test) {
        SearchResult mySearchResult(m_searchstr + char_to_test);
        size_t num_items = m_ref->size();

        // check if we have already a searched set
        if (m_search_results.empty())
            doSearch(char_to_test, *m_ref, mySearchResult);
        else {
            num_items = m_search_results.back().size();
            doSearch(char_to_test, m_search_results.back().result(),
                     mySearchResult);
        }

        if (mySearchResult.size() > 0 ) {
            if (mySearchResult.size() < num_items) {
                mySearchResult.seek();
                m_search_results.push_back(mySearchResult);
            }
            m_searchstr += char_to_test;
        }
    }

    // iteration based on original list of items
    void doSearch(char to_test, Items const &items,
                  SearchResult &mySearchResult) const {
        for (ItemscIt it = items.begin(); it != items.end(); it++) {
            if ((*it)->iTypeCompareChar(to_test, stringSize()) && (*it)->isEnabled())
                mySearchResult.add(*it);
        }
    }

    // iteration based on last SearchResult
    void doSearch(char to_test, BaseItems const &search,
                  SearchResult &mySearchResult) const {
        for (BaseItemscIt it = search.begin(); it != search.end(); it++) {
            if ((*it)->iTypeCompareChar(to_test, stringSize()) && (*it)->isEnabled())
                mySearchResult.add(*it);
        }
    }

}; // end Class TypeAhead

} // end namespace FbTk

#endif // FBTK_TYPEAHEAD_HH
