/*
 * Copyright 2017 NXP
 * All rights reserved.
 *
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "erpc_crc16.hpp"
#include "erpc_tcp_transport.hpp"
#include "erpc_transport.hpp"
#include "erpc_transport_setup.h"
#include "erpc_version.h"

#include "ErpcLexer.hpp"
#include "InterfaceDefinition.hpp"
#include "Logging.hpp"
#include "SearchPath.hpp"
#include "Sniffer.hpp"
#include "UniqueIdChecker.hpp"
#include "annotations.h"
#include "options.hpp"

#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <vector>

/*!
 * @brief Entry point for the tool.
 */
int main(int argc, char *argv[], char *envp[]);

using namespace erpc;
using namespace erpcgen;
using namespace std;
namespace erpcsniffer {

////////////////////////////////////////////////////////////////////////////////
// Variables
////////////////////////////////////////////////////////////////////////////////

/*! The tool's name. */
const char k_toolName[] = "erpcsniffer";

/*! Current version number for the tool. */
const char k_version[] = ERPC_VERSION;

/*! Copyright string. */
const char k_copyright[] = "Copyright 2017 NXP. All rights reserved.";

static const char *k_optionsDefinition[] = { "?|help",
                                             "V|version",
                                             "o:output <filePath>",
                                             "v|verbose",
                                             "I:path <filePath>",
                                             "t:transport <transport>",
                                             "q:quantity <quantity>",
                                             "b:baudrate <baudrate>",
                                             "p:port <port>",
                                             "h:host <host>",
                                             NULL };

/*! Help string. */
const char k_usageText[] =
    "\nOptions:\n\
  -?/--help                    Show this help\n\
  -V/--version                 Display tool version\n\
  -o/--output <filePath>       Set path to output file (file name included)\n\
  -v/--verbose                 Print extra detailed log information\n\
  -I/--path <filePath>         Add search path for imports\n\
  -t/--transport <transport>   Type of transport.\n\
  -q/--quantity <quantity>     Record messages count (0 - infinity).\n\
  -b/--baudrate <baudrate>     Baud rate.\n\
  -p/--port <port>             Port name or port number.\n\
  -h/--host <host>             Host definition.\n\
\n\
Available transports (use with -t option):\n\
  tcp      Tcp transport type (host, port number).\n\
  serial   Serial transport type (port name, baud rate).\n\
\n";

////////////////////////////////////////////////////////////////////////////////
// Code
////////////////////////////////////////////////////////////////////////////////

/*!
 * @brief Class that encapsulates the erpcsniffer tool.
 *
 * A single global logger instance is created during object construction. It is
 * never freed because we need it up to the last possible minute, when an
 * exception could be thrown.
 */
class erpcsnifferTool
{
protected:
    enum class verbose_type_t
    {
        kWarning,
        kInfo,
        kDebug,
        kExtraDebug
    }; /*!< Types of verbose outputs from erpcsniffer application. */

    enum class transports_t
    {
        kNoneTransport,
        kTcpTransport,
        kSerialTransport
    }; /*!< Type of transport to use. */

    typedef vector<string> string_vector_t;

    int m_argc;                   /*!< Number of command line arguments. */
    char **m_argv;                /*!< String value for each command line argument. */
    StdoutLogger *m_logger;       /*!< Singleton logger instance. */
    verbose_type_t m_verboseType; /*!< Which type of log is need to set (warning, info, debug). */
    const char *m_outputFilePath; /*!< Path to the output file. */
    const char *m_ErpcFile;       /*!< ERPC file. */
    string_vector_t m_positionalArgs;
    transports_t m_transport; /*!< Transport used for receiving messages. */
    uint64_t m_quantity;      /*!< Quantity of logs to store. */
    uint32_t m_baudrate;      /*!< Baudrate rate speed. */
    const char *m_port;       /*!< Name or number of port. Based on used transport. */
    const char *m_host;       /*!< Host name */

public:
    /*!
     * @brief Constructor.
     *
     * @param[in] argc Count of arguments in argv variable.
     * @param[in] argv Pointer to array of arguments.
     *
     * Creates the singleton logger instance.
     */
    erpcsnifferTool(int argc, char *argv[]) :
    m_argc(argc), m_argv(argv), m_logger(0), m_verboseType(verbose_type_t::kWarning), m_outputFilePath(NULL),
    m_ErpcFile(NULL), m_transport(transports_t::kNoneTransport), m_quantity(10), m_baudrate(115200), m_port(NULL),
    m_host(NULL)
    {
        // create logger instance
        m_logger = new StdoutLogger();
        m_logger->setFilterLevel(Logger::log_level_t::kWarning);
        Log::setLogger(m_logger);
    }

    /*!
     * @brief Destructor.
     */
    ~erpcsnifferTool() {}

    /*!
     * @brief Reads the command line options passed into the constructor.
     *
     * This method can return a return code to its caller, which will cause the
     * tool to exit immediately with that return code value. Normally, though, it
     * will return -1 to signal that the tool should continue to execute and
     * all options were processed successfully.
     *
     * The Options class is used to parse command line options. See
     * #k_optionsDefinition for the list of options and #k_usageText for the
     * descriptive help for each option.
     *
     * @retval -1 The options were processed successfully. Let the tool run normally.
     * @return A zero or positive result is a return code value that should be
     *      returned from the tool as it exits immediately.
     */
    int processOptions()
    {
        Options options(*m_argv, k_optionsDefinition);
        OptArgvIter iter(--m_argc, ++m_argv);

        // process command line options
        int optchar;
        const char *optarg;
        while ((optchar = options(iter, optarg)))
        {
            switch (optchar)
            {
                case '?':
                {
                    printUsage(options);
                    return 0;
                }

                case 'V':
                {
                    printf("%s %s\n%s\n", k_toolName, k_version, k_copyright);
                    return 0;
                }

                case 'o':
                {
                    m_outputFilePath = optarg;
                    break;
                }

                case 'v':
                {
                    if (m_verboseType != verbose_type_t::kExtraDebug)
                    {
                        m_verboseType = (verbose_type_t)(((int)m_verboseType) + 1);
                    }
                    break;
                }

                case 'I':
                {
                    PathSearcher::getGlobalSearcher().addSearchPath(optarg);
                    break;
                }

                case 't':
                {
                    string transport = optarg;
                    if (transport == "tcp")
                    {
                        m_transport = transports_t::kTcpTransport;
                    }
                    else if (transport == "serial")
                    {
                        m_transport = transports_t::kSerialTransport;
                    }
                    else
                    {
                        Log::error("error: unknown transport type %s", transport.c_str());
                        return 1;
                    }
                    break;
                }

                case 'q':
                {
                    m_quantity = strtoul(optarg, NULL, 10);
                    break;
                }

                case 'b':
                {
                    m_baudrate = strtoul(optarg, NULL, 10);
                    break;
                }

                case 'p':
                {
                    m_port = optarg;
                    break;
                }

                case 'h':
                {
                    m_host = optarg;
                    break;
                }

                default:
                {
                    Log::error("error: unrecognized option\n\n");
                    printUsage(options);
                    return 0;
                }
            }
        }

        // handle positional args
        if (iter.index() < m_argc)
        {
            //            Log::debug("positional args:\n");
            int i;
            for (i = iter.index(); i < m_argc; ++i)
            {
                //                Log::debug("%d: %s\n", i - iter.index(), m_argv[i]);
                m_positionalArgs.push_back(m_argv[i]);
            }
        }

        // all is well
        return -1;
    }

    /*!
     * @brief Prints help for the tool.
     *
     * @param[in] options Options, which can be used.
     */
    void printUsage(Options &options)
    {
        options.usage(cout, "files...");
        printf(k_usageText);
    }

    /*!
     * @brief Core of the tool.
     *
     * Calls processOptions() to handle command line options before performing the
     * real work the tool does.
     *
     * @retval 1 The functions wasn't processed successfully.
     * @retval 0 The function was processed successfully.
     *
     * @exception Log::error This function is called, when function wasn't
     *              processed successfully.
     * @exception runtime_error Thrown, when positional args is empty.
     */
    int run()
    {
        try
        {
            // read command line options
            int result;
            if ((result = processOptions()) != -1)
            {
                return result;
            }

            // set verbose logging
            setVerboseLogging();

            // check argument values
            checkArguments();
            if (!m_positionalArgs.size())
            {
                throw runtime_error("no input file provided");
            }

            m_ErpcFile = m_positionalArgs[0].c_str();
            if (!m_outputFilePath)
            {
                m_outputFilePath = "";
            }

            // Parse and build definition model.
            InterfaceDefinition def;

            // Check for duplicate function IDs
            UniqueIdChecker uniqueIdCheck;
            uniqueIdCheck.makeIdsUnique(def);

            Transport *_transport;
            switch (m_transport)
            {
                case transports_t::kTcpTransport:
                {
                    uint16_t portNumber = strtoul(m_port, NULL, 10);
                    TCPTransport *tcpTransport = new TCPTransport(m_host, portNumber, true);
                    if (erpc_status_t err = tcpTransport->open())
                    {
                        return err;
                    }
                    _transport = tcpTransport;
                    break;
                }

                case transports_t::kSerialTransport:
                {
                    erpc_transport_t transport = erpc_transport_serial_init(m_port, m_baudrate);
                    _transport = reinterpret_cast<Transport *>(transport);
                    assert(_transport);
                    break;
                }

                default:
                {
                    break;
                }
            }

            Crc16 crc;
            if (def.hasProgramSymbol())
            {
                Program *program = def.getProgramSymbol();
                if (program->findAnnotation(CRC_ANNOTATION, Annotation::program_lang_t::kC) != nullptr)
                {
                    crc.setCrcStart(def.getIdlCrc16());
                }
            }
            _transport->setCrc16(&crc);
            Sniffer s(_transport, &def, m_outputFilePath, m_quantity);
            return s.run();
        }
        catch (exception &e)
        {
            Log::error("error: %s\n", e.what());
            return 1;
        }
        catch (...)
        {
            Log::error("error: unexpected exception\n");
            return 1;
        }

        return 0;
    }

    /*!
     * @brief Validate arguments that can be checked.
     *
     * @exception runtime_error Thrown if an argument value fails to pass validation.
     */
    void checkArguments()
    {
        //      if (m_outputFilePath == NULL)
        //      {
        //          throw runtime_error("no output file was specified");
        //      }
    }

    /*!
     * @brief Turns on verbose logging.
     */
    void setVerboseLogging()
    {
        // verbose only affects the INFO and DEBUG filter levels
        // if the user has selected quiet mode, it overrides verbose
        switch (m_verboseType)
        {
            case verbose_type_t::kWarning:
            {
                Log::getLogger()->setFilterLevel(Logger::log_level_t::kWarning);
                break;
            }
            case verbose_type_t::kInfo:
            {
                Log::getLogger()->setFilterLevel(Logger::log_level_t::kInfo);
                break;
            }
            case verbose_type_t::kDebug:
            {
                Log::getLogger()->setFilterLevel(Logger::log_level_t::kDebug);
                break;
            }
            case verbose_type_t::kExtraDebug:
            {
                Log::getLogger()->setFilterLevel(Logger::log_level_t::kDebug2);
                break;
            }
        }
    }
};

} // namespace erpcsniffer

/*!
 * @brief Main application entry point.
 *
 * Creates a tool instance and lets it take over.
 */
int main(int argc, char *argv[], char *envp[])
{
    (void)envp;
    try
    {
        return erpcsniffer::erpcsnifferTool(argc, argv).run();
    }
    catch (...)
    {
        Log::error("error: unexpected exception\n");
        return 1;
    }

    return 0;
}
