// ----------------------------------------------------------------------------------;
// michael.mill@metacom.net --- (03/08/2018)
//
// https://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/examples.html
// https://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/echo/blocking_tcp_echo_server.cpp
// https://wiki.archlinux.org/index.php/Ipset
// https://stackoverflow.com/questions/17954432/creating-a-daemon-in-linux/17955149#17955149
// https://www.geeksforgeeks.org/binary-search-algorithms-the-c-standard-template-library-stl/
//
// https://theboostcpplibraries.com/boost.asio-io-services-and-io-objects
//
// https://stackoverflow.com/questions/47185326/how-to-write-binary-data-received-with-recv-to-file
//
// http://blog.videgro.net/2015/12/linux-let-your-program-run-as-background-service/
// https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units
// 

#include <ctpl_stl.h>
#include <ipset_t.h>

// ----------------------------------------------------------------------------------;

static void skeleton_daemon() {

    pid_t pid;

    /* Fork off the parent process */
    pid = fork();

    /* An error occurred */
    if (pid < 0)
        exit(EXIT_FAILURE);

    /* Success: Let the parent terminate */
    if (pid > 0)
        exit(EXIT_SUCCESS);

    /* On success: The child process becomes session leader */
    if (setsid() < 0)
        exit(EXIT_FAILURE);

    /* Catch, ignore and handle signals */
    //TODO: Implement a working signal handler */
    signal(SIGCHLD, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    /* Fork off for the second time*/
    pid = fork();

    /* An error occurred */
    if (pid < 0)
        exit(EXIT_FAILURE);

    /* Success: Let the parent terminate */
    if (pid > 0)
        exit(EXIT_SUCCESS);

    /* Set new file permissions */
    umask(0);

    /* Change the working directory to the root directory */
    /* or another appropriated directory */
    chdir("/");

    /* Close all open file descriptors */
    int x;
    for (x = sysconf(_SC_OPEN_MAX); x>=0; x--)
    {
        close (x);
    }

    /* Open the log file */
    openlog ("daemon", LOG_PID, LOG_DAEMON);
}

// ----------------------------------------------------------------------------------;

int ipset_test_alive (int fd) {

    try {
        char send_buf[max_length];

        sprintf (send_buf, "%s", "alive");

        size_t send_len   = strlen(send_buf); 
        size_t bytes_sent = send(fd, send_buf, send_len, 0);

        if (bytes_sent <= 0) {
            perror("nothing sent.\n");
            return -1;
        }
    }
    catch (std::exception& e) {
        std::cerr << "Exception (ipset_test_alive) : " << e.what() << "\n";
    }
    return 0;
}

// ----------------------------------------------------------------------------------;

int get_parser_t (std::string data_t,
                  int         thread_id_t,
                  int         fd) {
    int ret_t = 0;

    try {
        std::string rslt_t;

        std::vector<std::string> outer;
        std::vector<std::string> inner;

        boost::algorithm::split(outer, data_t, boost::is_any_of("|"));

        for (size_t i = 0; i < outer.size(); i++) {

            boost::algorithm::split(inner, outer[i], boost::is_any_of(" "));

            for (size_t i = 0; i < inner.size(); i++) {

                if (inner[i].compare ("add") == 0) {
                    int retval;
                    struct in_addr addrptr;

                    memset(&addrptr, '\0', sizeof(addrptr));

                    retval = inet_aton((inner[i + 2]).c_str(), &addrptr);
                    blocker_init ();
                    ret_t = try_ipset_cmd(IPSET_CMD_ADD, (inner[i + 1]).c_str(), &addrptr, 0);

                    request_conforms = true;

                    BOOST_LOG_TRIVIAL(info) << "[function_name:get_parser_t() : ipset call (add), value = (" << inner[i + 2] << ")]";

                }
                if (inner[i].compare ("del") == 0) {
                    int retval;
                    struct in_addr addrptr;

                    memset(&addrptr, '\0', sizeof(addrptr));

                    retval = inet_aton((inner[i + 2]).c_str(), &addrptr);
                    blocker_init ();
                    ret_t = try_ipset_cmd(IPSET_CMD_DEL, (inner[i + 1]).c_str(), &addrptr, 0);

                    request_conforms = true;

                    BOOST_LOG_TRIVIAL(info) << "[function_name:get_parser_t() : ipset call (del), value = (" << inner[i + 2] << ")]";

                }
                if (inner[i].compare ("conntrack") == 0) {
                    ipset_conntrack (inner[i + 1]);

                    request_conforms = true;
                    BOOST_LOG_TRIVIAL(info) << "[function_name:get_parser_t() : conntrack, value = (" << inner[i + 1] << ")]";
                }

                if (inner[i].compare ("test-alive") == 0) {
                    ipset_test_alive (fd);

                    request_conforms = true;
                    BOOST_LOG_TRIVIAL(info) << "[function_name:get_parser_t() : health check called - ipset_test_alive]";
                }
                if (request_conforms == false) {
                    BOOST_LOG_TRIVIAL(warning) << "[function_name:get_parser_t() : malformed or incomplete request]";
                }
            }
        }
    }
    catch (std::exception& e) {
        std::cerr << "Exception (get_parser_t) : " << e.what() << "\n";
    }
    return ret_t;
}

// ----------------------------------------------------------------------------------;

void thread_ext_t (int        thread_id_t,
                   socket_ptr socket) {
    try {

        timeval tv = { timeout_value, 0 };

        int fd = socket->native_handle();
        setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
        fd_set fds;
        FD_ZERO (&fds);
        FD_SET  (fd, &fds);

        int result = select(fd + 1, &fds, NULL, NULL, &tv);

        if (result == 0) {
            return;
        }
      
        char buf[max_length];

        int bytes = recv(fd, buf, sizeof(buf), MSG_WAITALL);

        if (bytes == -1 && errno == ETIMEDOUT) {
            return;
        }

        std::string s(buf);

      //std::cout << "(" << s << ")" << std::endl;

        BOOST_LOG_TRIVIAL(debug) << "[function_name:thread_ext_t() : network connection established from (" << remote_ip << "), value received = (" << s << ")]";

        get_parser_t (s, thread_id_t, fd);
    }
    catch (std::exception& e) {

        std::cerr << "Exception (thread_ext_t) : " << e.what() << "\n";
    }
}

// ----------------------------------------------------------------------------------;

void performance_counter() {

    sleep(sleep_for);

    if (connection_count > 0) {
        float avg = (float) sleep_for/connection_count;
        std::cout << "(from worker thread : " << connection_count << ")" << std::endl;
        std::cout << "(average : " << avg << ")" << std::endl;

        BOOST_LOG_TRIVIAL(info) << "[function_name:worker_thread() : performance average result = " << avg << "]";

      //std::cout << "(average : " << f << " )" << std::endl;
      //printf ("%f\n", f);
    }
    connection_count = 0;
}

// ----------------------------------------------------------------------------------;

void ipset_t::server_t (boost::asio::io_service& io_service,
                        std::string              ip, 
                        short                    port,
                        int                      num_threads) {

    try {
        /**
            TODO : message queue? - check for timeout?
                   connection blocks - until success?

            - basic_socket_acceptor::accept :

              This function is used to accept a new connection
              from a peer into the given socket. The function
              call will block until a new connection has been 
              accepted successfully or an error occurs.

        **/

        tcp::acceptor acceptor(io_service, tcp::endpoint(boost::asio::ip::address::from_string(ip), port));
        ctpl::thread_pool pool_t(num_threads);

        for (;;) {

            //std::cout << connection_count << std::endl;
            connection_count++;

            socket_ptr sock(new tcp::socket(io_service));
            tcp::acceptor::endpoint_type end_type;

            acceptor.accept(*sock, end_type);
            remote_ip = end_type.address().to_string();

            boost::thread t{performance_counter};

            pool_t.push(thread_ext_t, sock);
        }
    }
    catch (std::exception& e) {
        std::cerr << "Exception (server_t) : " << e.what() << "\n";
    }
}

// ----------------------------------------------------------------------------------;

int main (int   argc, 
          char* argv[]) {

    try {

        load_logging ();
        load_config ();

        ipset_t* t = new ipset_t();
        boost::asio::io_service io_service;

        if (enable_daemon) {
            skeleton_daemon();
        }

        t->server_t (io_service, ip_to_use, port_to_use, num_of_threads);
    }
    catch (std::exception& e) {
        std::cerr << "Exception (main) : " << e.what() << "\n";
    }
    return 0;
}
