/*
 * Copyright (c) 2011-2014, Intel Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
 */
#pragma once

#include <vector>
#include "RemoteCommandHandler.h"

template <class CCommandParser>
class TRemoteCommandHandlerTemplate : public IRemoteCommandHandler
{
public:
    /** Remote command parser execution return status */
    enum CommandStatus
    {
        EDone,      /**< Command succeded, return "Done" */
        ESucceeded, /**< Command succeeded */
        EFailed,    /**< Command failed */
        EShowUsage  /**< Command failed, show usage */
    };

    /** Type of the remote command callbacks
     *
     * @param[in] remoteCommand contains the arguments of the received command.
     * @param[out] strResult a string containing the result of the command.
     *
     * @return the command execution status, @see CommandStatus
     */
    typedef CommandStatus (CCommandParser::*RemoteCommandParser)(
        const IRemoteCommand &remoteCommand, std::string &strResult);

private:
    // Parser descriptions
    class CRemoteCommandParserItem
    {
    public:
        CRemoteCommandParserItem(const std::string &strCommandName, RemoteCommandParser pfnParser,
                                 size_t minArgumentCount, const std::string &strHelp,
                                 const std::string &strDescription)
            : _strCommandName(strCommandName), _pfnParser(pfnParser),
              _minArgumentCount(minArgumentCount), _strHelp(strHelp),
              _strDescription(strDescription)
        {
        }

        const std::string &getCommandName() const { return _strCommandName; }

        const std::string &getDescription() const { return _strDescription; }

        // Usage
        std::string usage() const { return _strCommandName + " " + _strHelp; }

        bool parse(CCommandParser *pCommandParser, const IRemoteCommand &remoteCommand,
                   std::string &strResult) const
        {
            // Check enough arguments supplied
            if (remoteCommand.getArgumentCount() < _minArgumentCount) {

                strResult = std::string("Not enough arguments supplied\nUsage:\n") + usage();

                return false;
            }

            switch ((pCommandParser->*_pfnParser)(remoteCommand, strResult)) {
            case EDone:
                strResult = "Done";
            // Fall through intentionally
            case ESucceeded:
                return true;
            case EShowUsage:
                strResult = usage();
            // Fall through intentionally
            case EFailed:
                return false;
            }

            return false;
        }

    private:
        std::string _strCommandName;
        RemoteCommandParser _pfnParser;
        size_t _minArgumentCount;
        std::string _strHelp;
        std::string _strDescription;
    };

public:
    TRemoteCommandHandlerTemplate(CCommandParser *pCommandParser)
        : _pCommandParser(pCommandParser), _maxCommandUsageLength(0)
    {
        // Help Command
        addCommandParser("help", NULL, 0, "", "Show commands description and usage");
    }
    ~TRemoteCommandHandlerTemplate()
    {
        // FIXME use unique_ptr
        for (auto *parser : _remoteCommandParserVector) {

            delete parser;
        }
    }

    // Parsers
    bool addCommandParser(const std::string &strCommandName, RemoteCommandParser pfnParser,
                          size_t minArgumentCount, const std::string &strHelp,
                          const std::string &strDescription)
    {
        if (findCommandParserItem(strCommandName)) {

            // Already exists
            return false;
        }

        // Add command
        _remoteCommandParserVector.push_back(new CRemoteCommandParserItem(
            strCommandName, pfnParser, minArgumentCount, strHelp, strDescription));

        return true;
    }

private:
    // Command processing
    bool remoteCommandProcess(const IRemoteCommand &remoteCommand, std::string &strResult)
    {
        // Dispatch
        const CRemoteCommandParserItem *pRemoteCommandParserItem =
            findCommandParserItem(remoteCommand.getCommand());

        if (!pRemoteCommandParserItem) {

            // Not found
            strResult = "Command not found!\nUse \"help\" to show available commands";

            return false;
        }

        if (remoteCommand.getCommand() == "help") {

            helpCommandProcess(strResult);

            return true;
        }

        return pRemoteCommandParserItem->parse(_pCommandParser, remoteCommand, strResult);
    }

    // Max command usage length, use for formatting
    void initMaxCommandUsageLength()
    {
        if (!_maxCommandUsageLength) {
            // Show usages
            for (const auto *pRemoteCommandParserItem : _remoteCommandParserVector) {

                size_t remoteCommandUsageLength = pRemoteCommandParserItem->usage().length();

                if (remoteCommandUsageLength > _maxCommandUsageLength) {

                    _maxCommandUsageLength = remoteCommandUsageLength;
                }
            }
        }
    }

    /////////////////// Remote command parsers
    /// Help
    void helpCommandProcess(std::string &strResult)
    {
        initMaxCommandUsageLength();

        // Show usages
        for (const auto *pRemoteCommandParserItem : _remoteCommandParserVector) {

            std::string strUsage = pRemoteCommandParserItem->usage();

            // Align
            size_t spacesToAdd = _maxCommandUsageLength + 5 - strUsage.length();

            strResult += strUsage + std::string(spacesToAdd, ' ') + "=> " +
                         pRemoteCommandParserItem->getDescription() + '\n';
        }
    }

    const CRemoteCommandParserItem *findCommandParserItem(const std::string &strCommandName) const
    {
        for (const auto *pRemoteCommandParserItem : _remoteCommandParserVector) {

            if (pRemoteCommandParserItem->getCommandName() == strCommandName) {

                return pRemoteCommandParserItem;
            }
        }
        return NULL;
    }

private:
    CCommandParser *_pCommandParser;
    std::vector<CRemoteCommandParserItem *> _remoteCommandParserVector;
    size_t _maxCommandUsageLength;
};