#ifndef INCLUDED_BOBCAT_CMDFINDER_
#define INCLUDED_BOBCAT_CMDFINDER_

#include <algorithm>
#include <string>

#include <bobcat/cmdfinderbase>
#include <bobcat/fswap>

namespace FBB
{

template <typename FP>
class CmdFinder: public CmdFinderBase
{
    size_t d_count;

    protected:
        typedef FP FunctionPtr;                 // define template type
                                                // as a named type

                                                // elements of the array
                                                // of keys/f-ptrs
        typedef std::pair<char const *, FP> Entry;

    private:
        Entry const *d_begin;
        Entry const *d_end;

    protected:
        CmdFinder(Entry const *begin, Entry const *end,                 // 1
                                                    size_t mode = 0);
        CmdFinder(CmdFinder &&tmp);                                     // 2

        CmdFinder &operator=(CmdFinder const &rhs)  = default;
        CmdFinder &operator=(CmdFinder &&tmp);                  // opis1

        FP findCmd(std::string const &cmd);
        size_t count() const;

        void swap(CmdFinder &rhs);

    private:
        class MatchKey
        {
            FP *d_fp;
            CmdFinder<FP> *d_cmdFinder;

            public:
                MatchKey(FunctionPtr *fp, CmdFinder<FP> *cmdFinder);    // 1
                bool operator()(CmdFinder::Entry const &entry);     // opfun
        };

        bool match(std::string const &key) const;

    friend MatchKey;

};

template <typename FP>
CmdFinder<FP>::CmdFinder(Entry const *begin, Entry const *end, size_t mode)
:
    CmdFinderBase(mode),
    d_count(0),
    d_begin(begin),
    d_end(end)
{}
template <typename FP>
CmdFinder<FP>::CmdFinder(CmdFinder &&tmp)
:
    CmdFinderBase(move(tmp)),
    d_count(tmp.d_count),
    d_begin(tmp.d_begin),
    d_end(tmp.d_end)
{}
template <typename FP>
inline size_t CmdFinder<FP>::count() const
{
    return d_count;
}
template <typename FP>
FP CmdFinder<FP>::findCmd(std::string const &cmd)
{
    (this->*d_useCmd)(cmd);     // store the cmd to use

    FunctionPtr fp;
    MatchKey matchKey(&fp, this);   // create an object matching
                                    // a cmd with a key

                                // count the number of occurrences
    d_count = std::count_if(d_begin, d_end, matchKey);


    return d_count == 1 ?
                fp              // return fp if cmd found
            :                   // otherwise return the last fptr
                (d_end - 1)->second;
}
template <typename FP>
inline CmdFinder<FP> &CmdFinder<FP>::operator=(CmdFinder &&tmp)
{
    swap(tmp);
    return *this;
}
template <typename FP>
void CmdFinder<FP>::swap(CmdFinder<FP> &rhs)
{
    CmdFinderBase::swap(rhs);

    fswap(&d_count, *this, rhs);
}
template <typename FP>
inline bool CmdFinder<FP>::match(std::string const &key) const
{
    return (this->*d_match)(key);
}
template <typename FP>
CmdFinder<FP>::MatchKey::MatchKey(FunctionPtr *fp, CmdFinder<FP> *cmdFinder)
:
    d_fp(fp),
    d_cmdFinder(cmdFinder)
{}
template <typename FP>
bool CmdFinder<FP>::MatchKey::operator()(CmdFinder::Entry const &entry)
{
    if (!d_cmdFinder->match(entry.first))
        return false;

    *d_fp = entry.second;
    return true;
}

} // FBB

#endif
