// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-

// Copyright (c) 2001-2009 XORP, Inc.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2, June
// 1991 as published by the Free Software Foundation. Redistribution
// and/or modification of this program under the terms of any other
// version of the GNU General Public License is not permitted.
// 
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For more details,
// see the GNU General Public License, Version 2, a copy of which can be
// found in the XORP LICENSE.gpl file.
// 
// XORP Inc, 2953 Bunker Hill Lane, Suite 204, Santa Clara, CA 95054, USA;
// http://xorp.net




//
// CLI (Command-Line Interface) test program.
//


#include "cli_module.h"

#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/eventloop.hh"
#include "libxorp/exceptions.hh"
#include "libxorp/token.hh"
#include "libxorp/xlog.h"

#include "libxipc/finder_server.hh"

#include "cli_client.hh"
#include "xrl_cli_node.hh"

#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

//
// Local variables
//
CliNode *global_cli_node = NULL;

//
// Local functions prototypes
//
static	bool wakeup_hook();
static	bool wakeup_hook2(int, int);
static	CliNode& cli_node();
static	void usage(const char *argv0, int exit_value);

/**
 * usage:
 * @argv0: Argument 0 when the program was called (the program name itself).
 * @exit_value: The exit value of the program.
 *
 * Print the program usage.
 * If @exit_value is 0, the usage will be printed to the standart output,
 * otherwise to the standart error.
 **/
static void
usage(const char *argv0, int exit_value)
{
    FILE *output;
    const char *progname = strrchr(argv0, '/');

    if (progname != NULL)
	progname++;		// Skip the last '/'
    if (progname == NULL)
	progname = argv0;

    //
    // If the usage is printed because of error, output to stderr, otherwise
    // output to stdout.
    //
    if (exit_value == 0)
	output = stdout;
    else
	output = stderr;

    fprintf(output, "Usage: %s [-F <finder_hostname>[:<finder_port>]]\n",
	    progname);
    fprintf(output, "           -F <finder_hostname>[:<finder_port>]  : finder hostname and port\n");
    fprintf(output, "           -h                                    : usage (this message)\n");
    fprintf(output, "\n");
    fprintf(output, "Program name:   %s\n", progname);
    fprintf(output, "Module name:    %s\n", XORP_MODULE_NAME);
    fprintf(output, "Module version: %s\n", XORP_MODULE_VERSION);

    exit (exit_value);

    // NOTREACHED
}

class Foo {
public:
    Foo() {}
    bool print() { printf("Hello %p\n", this); return (1); }
};

int
add_my_cli_commands(CliNode& cli_node);

static CliNode&
cli_node()
{
    return (*global_cli_node);
}

static void
cli_main(const string& finder_hostname, uint16_t finder_port,
	 bool start_finder)
{
    string error_msg;
    EventLoop eventloop;

    //
    // Init stuff
    //

    //
    // Start our own finder
    //
    FinderServer *finder = NULL;
    if (start_finder) {
	try {
	    finder = new FinderServer(eventloop, IPv4(finder_hostname.c_str()),
				      finder_port);
	} catch (const InvalidPort&) {
	    XLOG_FATAL("Could not start in-process Finder");
	}
    }

    //
    // The main body
    //
    Foo f;

    //
    // CLI
    //
    // XXX: we use a single CLI node to handle both IPv4 and IPv6
    CliNode cli_node(AF_INET, XORP_MODULE_CLI, eventloop);
    cli_node.set_cli_port(12000);

    //
    // CLI access
    //
    IPvXNet enable_ipvxnet1("127.0.0.1/32");
    // IPvXNet enable_ipvxnet2("192.150.187.0/25");
    IPvXNet disable_ipvxnet1("0.0.0.0/0");	// Disable everything else
    //
    cli_node.add_enable_cli_access_from_subnet(enable_ipvxnet1);
    // cli_node.add_enable_cli_access_from_subnet(enable_ipvxnet2);
    cli_node.add_disable_cli_access_from_subnet(disable_ipvxnet1);

    //
    // Create and configure the CLI XRL interface
    //
    XrlCliNode xrl_cli_node(eventloop,
			    cli_node.module_name(),
			    finder_hostname,
			    finder_port,
			    "finder",
			    cli_node);
    wait_until_xrl_router_is_ready(eventloop, xrl_cli_node.xrl_router());

#if 0
#ifdef HAVE_IPV6
    CliNode cli_node6(AF_INET6, XORP_MODULE_CLI, eventloop);
    cli_node6.set_cli_port(12000);
    XrlCliNode xrl_cli_node(eventloop,
			    cli_node6.module_name(),
			    finder_hostname,
			    finder_port,
			    "finder",
			    cli_node6);
    wait_until_xrl_router_is_ready(eventloop, xrl_cli_node.xrl_router());
#endif // HAVE_IPV6
#endif // 0


    //
    // XXX: CLI test-specific setup
    //
    global_cli_node = &cli_node;
    add_my_cli_commands(cli_node);
    // add_my_cli_commands(cli_node6);

    //
    // Start the nodes
    //
    cli_node.enable();
    cli_node.start();
    // cli_node6.enable();
    // cli_node6.start();

    // Test timer
    XorpTimer wakeywakey = eventloop.new_periodic_ms(
	1000,
	callback(wakeup_hook));
    XorpTimer wakeywakey2 = eventloop.new_periodic_ms(
	5000,
	callback(wakeup_hook2, 3, 5));
    XorpTimer wakeywakey3 = eventloop.new_periodic_ms(
	2000,
	callback(f, &Foo::print));

    //
    // Main loop
    //
    for (;;) {
	eventloop.run();
    }
}

static bool
wakeup_hook()
{
    fprintf(stdout, "%s\n", xlog_localtime2string());
    fflush(stdout);

    return (true);
}

static bool
wakeup_hook2(int a, int b)
{
    fprintf(stdout, "%s ARGS = %d %d\n", xlog_localtime2string(), a, b);
    fflush(stdout);

    return (true);
}

int
cli_myset_func(const string& ,		// server_name
	       const string& cli_term_name,
	       uint32_t ,		// cli_session_id
	       const vector<string>& command_global_name,
	       const vector<string>& argv)
{
    CliClient *cli_client = cli_node().find_cli_by_term_name(cli_term_name);
    if (cli_client == NULL)
	return (XORP_ERROR);

    string command_line = token_vector2line(command_global_name);
    if (command_global_name.size() > 0)
	cli_client->cli_print(c_format("MYSET_FUNC command_global_name = %s\n",
				       command_line.c_str()));
    for (size_t i = 0; i < argv.size(); i++)
	cli_client->cli_print(c_format("MYSET_FUNC arg = %s\n",
				       argv[i].c_str()));

    return (XORP_OK);
}

int
cli_print(const string& ,		// server_name
	  const string& cli_term_name,
	  uint32_t ,			// cli_session_id
	  const vector<string>& ,	// command_global_name,
	  const vector<string>& argv)
{
    CliClient *cli_client = cli_node().find_cli_by_term_name(cli_term_name);
    if (cli_client == NULL)
	return (XORP_ERROR);

    if (argv.size() > 0) {
	cli_client->cli_print("Error: unexpected arguments\n");
	return (XORP_ERROR);
    }

    //
    // Test to print a number of lines at once
    //
    string my_string = "";
    for (int i = 0; i < 100; i++)
	my_string += c_format("This is my multi-line number %u\n", i);
    cli_client->cli_print(my_string);

    //
    // Test to print a number of lines one-by-one
    //
    for (int i = 0; i < 100; i++)
	cli_client->cli_print(c_format("This is my line number %u\n", i));


    return (XORP_OK);
}

int
cli_print2(const string& ,		// server_name
	   const string& cli_term_name,
	   uint32_t ,			// cli_session_id
	   const vector<string>& ,	// command_global_name,
	   const vector<string>& argv)
{
    CliClient *cli_client = cli_node().find_cli_by_term_name(cli_term_name);
    if (cli_client == NULL)
	return (XORP_ERROR);

    if (argv.size() > 0) {
	cli_client->cli_print("Error: unexpected arguments\n");
	return (XORP_ERROR);
    }

    for (int i = 0; i < 10; i++)
	cli_client->cli_print(c_format("This is my line number %d\n", i));

    return (XORP_OK);
}

int
cli_print2_newline(const string& ,		// server_name
		   const string& cli_term_name,
		   uint32_t ,			// cli_session_id
		   const vector<string>& ,	// command_global_name,
		   const vector<string>& argv)
{
    CliClient *cli_client = cli_node().find_cli_by_term_name(cli_term_name);
    if (cli_client == NULL)
	return (XORP_ERROR);

    if (argv.size() > 0) {
	cli_client->cli_print("Error: unexpected arguments\n");
	return (XORP_ERROR);
    }

    for (int i = 0; i < 10; i++)
	cli_client->cli_print(c_format("This is my line number %d\n", i));
    cli_client->cli_print(c_format("\n"));

    return (XORP_OK);
}

int
cli_print_wide(const string& ,		// server_name
	       const string& cli_term_name,
	       uint32_t ,		// cli_session_id
	       const vector<string>& ,	// command_global_name,
	       const vector<string>& argv)
{
    CliClient *cli_client = cli_node().find_cli_by_term_name(cli_term_name);
    if (cli_client == NULL)
	return (XORP_ERROR);

    if (argv.size() > 0) {
	cli_client->cli_print("Error: unexpected arguments\n");
	return (XORP_ERROR);
    }

    string trail = "111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999";
#if 1
    for (int i = 0; i < 200; i++)
	cli_client->cli_print(c_format("This is my line number %d %s %d\n",
				       i, trail.c_str(), i));
#endif

    return (XORP_OK);
}

int
cli_print_test(const string& ,		// server_name
	       const string& cli_term_name,
	       uint32_t ,		// cli_session_id
	       const vector<string>& ,	// command_global_name,
	       const vector<string>& argv)
{
    CliClient *cli_client = cli_node().find_cli_by_term_name(cli_term_name);
    if (cli_client == NULL)
	return (XORP_ERROR);

    if (argv.size() > 0) {
	cli_client->cli_print("Error: unexpected arguments\n");
	return (XORP_ERROR);
    }

    string s = "\
Group           Source          RP              Flags\n\
ff0e::8320:1    ::              ffff:fff:ffff:300:1:: WC   \n\
    Upstream interface (RP):   gif0\n\
    Upstream MRIB next hop (RP): fe80::ffff:19\n\
    Upstream RPF'(*,G):        fe80::ffff:19\n\
    Upstream state:            Joined \n\
    Join timer:                31\n\
    Local receiver include WC: .........O..\n\
    Joins RP:                  ............\n\
    Joins WC:                  ............\n\
    Join state:                ............\n\
    Prune state:               ............\n\
    Prune pending state:       ............\n\
    I am assert winner state:  .........O..\n\
    I am assert loser state:   ............\n\
    Assert winner WC:          .........O..\n\
    Assert lost WC:            ............\n\
    Assert tracking WC:        .....O...O..\n\
    Could assert WC:           .........O..\n\
    I am DR:                   .....O...O..\n\
    Immediate olist RP:        ............\n\
    Immediate olist WC:        .........O..\n\
    Inherited olist SG:        .........O..\n\
    Inherited olist SG_RPT:    .........O..\n\
    PIM include WC:            .........O..\n\
ff0e::8320:1    ffff:700:0:fff2::20 ffff:fff:ffff:300:1:: SG_RPT DirectlyConnectedS \n\
    Upstream interface (S):    rl0\n\
    Upstream interface (RP):   gif0\n\
    Upstream MRIB next hop (RP): fe80::ffff:19\n\
    Upstream RPF'(S,G,rpt):    fe80::ffff:19\n\
    Upstream state:            Pruned \n\
    Override timer:            -1\n\
    Local receiver include WC: .........O..\n\
    Joins RP:                  ............\n\
    Joins WC:                  ............\n\
    Prunes SG_RPT:             ............\n\
    Join state:                ............\n\
    Prune state:               ............\n\
    Prune pending state:       ............\n\
    Prune tmp state:           ............\n\
    Prune pending tmp state:   ............\n\
    Assert winner WC:          .........O..\n\
    Assert lost WC:            ............\n\
    Assert lost SG_RPT:        ............\n\
    Could assert WC:           .........O..\n\
    Could assert SG:           ...........O\n\
    I am DR:                   .....O...O..\n\
    Immediate olist RP:        ............\n\
    Immediate olist WC:        .........O..\n\
    Inherited olist SG:        .........O..\n\
    Inherited olist SG_RPT:    .........O..\n\
    PIM include WC:            .........O..\n\
ff0e::8320:1    ffff:700:0:fff2::20 ffff:fff:ffff:300:1:: SG SPT DirectlyConnectedS \n\
    Upstream interface (S):    rl0\n\
    Upstream interface (RP):   gif0\n\
    Upstream MRIB next hop (RP): fe80::ffff:19\n\
    Upstream MRIB next hop (S):  UNKNOWN\n\
    Upstream RPF'(S,G):        UNKNOWN\n\
    Upstream state:            Joined \n\
    Register state:            RegisterJoin RegisterCouldRegister \n\
    Join timer:                39\n\
    Local receiver include WC: .........O..\n\
    Local receiver include SG: ............\n\
    Local receiver exclude SG: ............\n\
    Joins RP:                  ............\n\
    Joins WC:                  ............\n\
    Joins SG:                  ...........O\n\
    Join state:                ...........O\n\
    Prune state:               ............\n\
    Prune pending state:       ............\n\
    I am assert winner state:  ............\n\
    I am assert loser state:   ............\n\
    Assert winner WC:          .........O..\n\
    Assert winner SG:          ............\n\
    Assert lost WC:            ............\n\
    Assert lost SG:            ............\n\
    Assert lost SG_RPT:        ............\n\
    Assert tracking SG:        .........O.O\n\
    Could assert WC:           .........O..\n\
    Could assert SG:           ...........O\n\
    I am DR:                   .....O...O..\n\
    Immediate olist RP:        ............\n\
    Immediate olist WC:        .........O..\n\
    Immediate olist SG:        ...........O\n\
    Inherited olist SG:        .........O.O\n\
    Inherited olist SG_RPT:    .........O..\n\
    PIM include WC:            .........O..\n\
    PIM include SG:            ............\n\
    PIM exclude SG:            ............\n\
";

    cli_client->cli_print(s);

    return (XORP_OK);
}

int
add_my_cli_commands(CliNode& cli_node)
{
    CliCommand *com0, *com1, *com2, *com3;
    string error_msg;

    com0 = cli_node.cli_command_root();
    com2 = com0->add_command("myset", "Set my variable", true, cli_myset_func,
			     error_msg);
    com1 = com0->add_command("myshow", "Show my information", "Myshow> ",
			     true, error_msg);
    com2 = com0->add_command("myshow version",
			     "Show my information about system", true,
			     error_msg);
    com3 = com0->add_command("myshow version pim",
			     "Show my information about PIM", true, error_msg);
    com3 = com0->add_command("myshow version igmp",
			     "Show my information about IGMP", true, error_msg);

    com1 = com0->add_command("myset2", "Set my variable2", true,
			     cli_myset_func, error_msg);
    com1 = com0->add_command("myshow2", "Show my information2", "Myshow2> ",
			     true, error_msg);

    com1 = com0->add_command("print", "Print numbers", true, cli_print,
			     error_msg);
    com1 = com0->add_command("print2", "Print few numbers", true, cli_print2,
			     error_msg);
    com1 = com0->add_command("print2 newline",
			     "Print few numbers with an extra newline",
			     true, cli_print2_newline, error_msg);
    com1 = com0->add_command("print_wide", "Print wide lines", true,
			     cli_print_wide, error_msg);
    com1 = com0->add_command("print_test", "Print test lines", true,
			     cli_print_test, error_msg);

    return (XORP_OK);
}

int
main(int argc, char *argv[])
{
    int ch;
    string::size_type idx;
    const char *argv0 = argv[0];
    string finder_hostname = FinderConstants::FINDER_DEFAULT_HOST().str();
    uint16_t finder_port = FinderConstants::FINDER_DEFAULT_PORT();

    //
    // Initialize and start xlog
    //
    xlog_init(argv[0], NULL);
    xlog_set_verbose(XLOG_VERBOSE_LOW);	// Least verbose messages
    // XXX: verbosity of the error messages temporary increased
    xlog_level_set_verbose(XLOG_LEVEL_ERROR, XLOG_VERBOSE_HIGH);
    xlog_add_default_output();
    xlog_start();

    //
    // Get the program options
    //
    while ((ch = getopt(argc, argv, "F:h")) != -1) {
	switch (ch) {
	case 'F':
	    // Finder hostname and port
	    idx = finder_hostname.find(':');
	    if (idx != string::npos) {
		if (idx + 1 >= finder_hostname.length()) {
		    // No port number
		    usage(argv0, 1);
		    // NOTREACHED
		}
		char* p = &finder_hostname[idx + 1];
		finder_port = static_cast<uint16_t>(atoi(p));
		finder_hostname = finder_hostname.substr(0, idx);
	    }
	    break;
	case 'h':
	case '?':
	    // Help
	    usage(argv0, 0);
	    break;
	default:
	    usage(argv0, 1);
	    break;
	}
    }
    argc -= optind;
    argv += optind;
    if (argc != 0) {
	usage(argv0, 1);
	// NOTREACHED
    }

    //
    // Run everything
    //
    try {
	cli_main(finder_hostname, finder_port, true);
    } catch(...) {
	xorp_catch_standard_exceptions();
    }

    //
    // Gracefully stop and exit xlog
    //
    xlog_stop();
    xlog_exit();

    exit (0);
}
