/*
 * Ouroboros - Copyright (C) 2016
 *
 * The API to instruct IPCPs
 *
 *    Sander Vrijders <sander.vrijders@intec.ugent.be>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define OUROBOROS_PREFIX "lib-ipcp"

#include <ouroboros/config.h>
#include <ouroboros/ipcp.h>
#include <ouroboros/common.h>
#include <ouroboros/logs.h>
#include <ouroboros/utils.h>
#include <ouroboros/sockets.h>

#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

static ipcp_msg_t * send_recv_ipcp_msg(pid_t pid,
                                       ipcp_msg_t * msg)
{
       int sockfd = 0;
       buffer_t buf;
       char * sock_path = NULL;
       ssize_t count = 0;
       ipcp_msg_t * recv_msg = NULL;

       sock_path = ipcp_sock_path(pid);
       if (sock_path == NULL)
               return NULL;

       sockfd = client_socket_open(sock_path);
       if (sockfd < 0) {
               free(sock_path);
               return NULL;
       }

       buf.size = ipcp_msg__get_packed_size(msg);
       if (buf.size == 0) {
               close(sockfd);
               free(sock_path);
               return NULL;
       }

       buf.data = malloc(IPCP_MSG_BUF_SIZE);
       if (buf.data == NULL) {
               close(sockfd);
               free(sock_path);
               return NULL;
       }

       ipcp_msg__pack(msg, buf.data);

       if (write(sockfd, buf.data, buf.size) == -1) {
               free(sock_path);
               free(buf.data);
               close(sockfd);
               return NULL;
       }

       count = read(sockfd, buf.data, IPCP_MSG_BUF_SIZE);
       if (count <= 0) {
               free(sock_path);
               free(buf.data);
               close(sockfd);
               return NULL;
       }

       recv_msg = ipcp_msg__unpack(NULL, count, buf.data);
       if (recv_msg == NULL) {
               free(sock_path);
               free(buf.data);
               close(sockfd);
               return NULL;
       }

       free(buf.data);
       free(sock_path);
       close(sockfd);
       return recv_msg;
}

pid_t ipcp_create(char *         ipcp_name,
                  enum ipcp_type ipcp_type)
{
        pid_t pid = 0;
        char irmd_pid[10];
        size_t len = 0;
        char * ipcp_dir = "bin";
        char * full_name = NULL;
        char * exec_name = NULL;

        sprintf (irmd_pid, "%u", getpid());

        pid = fork();
        if (pid == -1) {
                LOG_ERR("Failed to fork");
                return pid;
        }

        if (pid != 0) {
                return pid;
        }

        if (ipcp_type == IPCP_NORMAL)
                exec_name = IPCP_NORMAL_EXEC;
        else if (ipcp_type == IPCP_SHIM_UDP)
                exec_name = IPCP_SHIM_UDP_EXEC;
        else if (ipcp_type == IPCP_SHIM_ETH_LLC)
                exec_name = IPCP_SHIM_ETH_LLC_EXEC;
        else if (ipcp_type == IPCP_LOCAL)
                exec_name = IPCP_LOCAL_EXEC;
        else
                exit(EXIT_FAILURE);

        len += strlen(INSTALL_DIR);
        len += strlen(ipcp_dir);
        len += strlen(exec_name);
        len += 3;

        full_name = malloc(len + 1);
        if (full_name == NULL) {
                LOG_ERR("Failed to malloc");
                exit(EXIT_FAILURE);
        }

        strcpy(full_name, INSTALL_DIR);
        strcat(full_name, "/");
        strcat(full_name, ipcp_dir);
        strcat(full_name, "/");
        strcat(full_name, exec_name);
        full_name[len] = '\0';

        char * argv[] = {full_name,
                         irmd_pid,
                         ipcp_name,
                         0};

        char * envp[] = {0};

        execve(argv[0], &argv[0], envp);

        LOG_DBG("%s", strerror(errno));
        LOG_ERR("Failed to load IPCP daemon");
        LOG_ERR("Make sure to run the installed version");
        free(full_name);
        exit(EXIT_FAILURE);
}

int ipcp_destroy(pid_t pid)
{
        int status;

        if (kill(pid, SIGTERM)) {
                LOG_ERR("Failed to destroy IPCP");
                return -1;
        }

        if (waitpid(pid, &status, 0) < 0) {
                LOG_ERR("Failed to destroy IPCP");
                return -1;
        }

        return 0;
}

int ipcp_reg(pid_t   pid,
             char ** dif_names,
             size_t  len)
{
        ipcp_msg_t msg = IPCP_MSG__INIT;
        ipcp_msg_t * recv_msg = NULL;
        int ret = -1;

        if (dif_names == NULL ||
            len == 0 ||
            dif_names[0] == NULL)
                return -EINVAL;

        msg.code       = IPCP_MSG_CODE__IPCP_REG;
        msg.dif_names  = dif_names;
        msg.len        = len;

        recv_msg = send_recv_ipcp_msg(pid, &msg);
        if (recv_msg == NULL)
                return -1;

        if (recv_msg->has_result == false) {
                ipcp_msg__free_unpacked(recv_msg, NULL);
                return -1;
        }

        ret = recv_msg->result;
        ipcp_msg__free_unpacked(recv_msg, NULL);

        return ret;
}

int ipcp_unreg(pid_t pid,
               char ** dif_names,
               size_t len)
{
        ipcp_msg_t msg = IPCP_MSG__INIT;
        ipcp_msg_t * recv_msg = NULL;
        int ret = -1;

        if (dif_names == NULL ||
            len == 0 ||
            dif_names[0] == NULL)
                return -EINVAL;

        msg.code       = IPCP_MSG_CODE__IPCP_UNREG;
        msg.dif_names  = dif_names;
        msg.len        = len;

        recv_msg = send_recv_ipcp_msg(pid, &msg);
        if (recv_msg == NULL)
                return -1;

        if (recv_msg->has_result == false) {
                ipcp_msg__free_unpacked(recv_msg, NULL);
                return -1;
        }

        ret = recv_msg->result;
        ipcp_msg__free_unpacked(recv_msg, NULL);

        return ret;
}


int ipcp_bootstrap(pid_t pid,
                   dif_config_msg_t * conf)
{
        ipcp_msg_t msg = IPCP_MSG__INIT;
        ipcp_msg_t * recv_msg = NULL;
        int ret = -1;

        if (conf == NULL)
                return -EINVAL;

        msg.code = IPCP_MSG_CODE__IPCP_BOOTSTRAP;
        msg.conf = conf;

        recv_msg = send_recv_ipcp_msg(pid, &msg);
        if (recv_msg == NULL)
                return -1;

        if (recv_msg->has_result == false) {
                ipcp_msg__free_unpacked(recv_msg, NULL);
                return -1;
        }

        ret = recv_msg->result;
        ipcp_msg__free_unpacked(recv_msg, NULL);

        return ret;
}

int ipcp_enroll(pid_t pid,
                char * member_name,
                char * n_1_dif)
{
        ipcp_msg_t msg = IPCP_MSG__INIT;
        ipcp_msg_t * recv_msg = NULL;
        int ret = -1;

        if (n_1_dif == NULL || member_name == NULL)
                return -EINVAL;

        msg.code        = IPCP_MSG_CODE__IPCP_ENROLL;
        msg.member_name = member_name;
        msg.n_1_dif     = n_1_dif;

        recv_msg = send_recv_ipcp_msg(pid, &msg);
        if (recv_msg == NULL) {
                free(msg.member_name);
                return -1;
        }

        if (recv_msg->has_result == false) {
                ipcp_msg__free_unpacked(recv_msg, NULL);
                free(msg.member_name);
                return -1;
        }

        ret = recv_msg->result;
        ipcp_msg__free_unpacked(recv_msg, NULL);
        free(msg.member_name);

        return ret;
}

int ipcp_name_reg(pid_t    pid,
                  char *   name)
{
        ipcp_msg_t msg = IPCP_MSG__INIT;
        ipcp_msg_t * recv_msg = NULL;
        int ret = -1;

        if (name == NULL)
                return -1;

        msg.code = IPCP_MSG_CODE__IPCP_NAME_REG;
        msg.name = name;

        recv_msg = send_recv_ipcp_msg(pid, &msg);
        if (recv_msg == NULL)
                return -1;

        if (recv_msg->has_result == false) {
                ipcp_msg__free_unpacked(recv_msg, NULL);
                return -1;
        }

        ret = recv_msg->result;
        ipcp_msg__free_unpacked(recv_msg, NULL);

        return ret;
}

int ipcp_name_unreg(pid_t  pid,
                    char * name)
{
        ipcp_msg_t msg = IPCP_MSG__INIT;
        ipcp_msg_t * recv_msg = NULL;
        int ret = -1;

        msg.code = IPCP_MSG_CODE__IPCP_NAME_UNREG;
        msg.name = name;

        recv_msg = send_recv_ipcp_msg(pid, &msg);
        if (recv_msg == NULL)
                return -1;

        if (recv_msg->has_result == false) {
                ipcp_msg__free_unpacked(recv_msg, NULL);
                return -1;
        }

        ret = recv_msg->result;
        ipcp_msg__free_unpacked(recv_msg, NULL);

        return ret;
}

int ipcp_flow_alloc(pid_t         pid,
                    int           port_id,
                    pid_t         n_pid,
                    char *        dst_name,
                    char *        src_ae_name,
                    enum qos_cube qos)
{
        ipcp_msg_t msg = IPCP_MSG__INIT;
        ipcp_msg_t * recv_msg = NULL;
        int ret = -1;

        if (dst_name == NULL || src_ae_name == NULL)
                return -EINVAL;

        msg.code         = IPCP_MSG_CODE__IPCP_FLOW_ALLOC;
        msg.has_port_id  = true;
        msg.port_id      = port_id;
        msg.has_pid      = true;
        msg.pid          = n_pid;
        msg.src_ae_name  = src_ae_name;
        msg.dst_name     = dst_name;
        msg.has_qos_cube = true;
        msg.qos_cube     = qos;

        recv_msg = send_recv_ipcp_msg(pid, &msg);
        if (recv_msg == NULL)
                return -1;

        if (!recv_msg->has_result) {
                ipcp_msg__free_unpacked(recv_msg, NULL);
                return -1;
        }

        ret = recv_msg->result;
        ipcp_msg__free_unpacked(recv_msg, NULL);

        return ret;
}

int ipcp_flow_alloc_resp(pid_t pid,
                         int   port_id,
                         pid_t n_pid,
                         int   response)
{
        ipcp_msg_t msg = IPCP_MSG__INIT;
        ipcp_msg_t * recv_msg = NULL;
        int ret = -1;

        msg.code         = IPCP_MSG_CODE__IPCP_FLOW_ALLOC_RESP;
        msg.has_port_id  = true;
        msg.port_id      = port_id;
        msg.has_pid      = true;
        msg.pid          = n_pid;
        msg.has_response = true;
        msg.response     = response;

        recv_msg = send_recv_ipcp_msg(pid, &msg);
        if (recv_msg == NULL)
                return -1;

        if (recv_msg->has_result == false) {
                ipcp_msg__free_unpacked(recv_msg, NULL);
                return -1;
        }

        ret = recv_msg->result;
        ipcp_msg__free_unpacked(recv_msg, NULL);

        return ret;
}

int ipcp_flow_req_arr(pid_t  pid,
                      char * dst_name,
                      char * src_ae_name)
{
        irm_msg_t msg = IRM_MSG__INIT;
        irm_msg_t * recv_msg = NULL;
        int port_id = -1;

        if (dst_name == NULL || src_ae_name == NULL)
                return -EINVAL;

        msg.code          = IRM_MSG_CODE__IPCP_FLOW_REQ_ARR;
        msg.has_pid       = true;
        msg.pid           = pid;
        msg.dst_name      = dst_name;
        msg.ae_name       = src_ae_name;

        recv_msg = send_recv_irm_msg(&msg);
        if (recv_msg == NULL)
                return -1;

        if (!recv_msg->has_port_id) {
                irm_msg__free_unpacked(recv_msg, NULL);
                return -1;
        }

        port_id = recv_msg->port_id;
        irm_msg__free_unpacked(recv_msg, NULL);

        return port_id;
}

int ipcp_flow_alloc_reply(pid_t pid,
                          int   port_id,
                          int   response)
{
        irm_msg_t msg = IRM_MSG__INIT;
        irm_msg_t * recv_msg = NULL;
        int ret = -1;

        msg.code         = IRM_MSG_CODE__IPCP_FLOW_ALLOC_REPLY;
        msg.port_id      = port_id;
        msg.has_port_id  = true;
        msg.response     = response;
        msg.has_response = true;

        recv_msg = send_recv_irm_msg(&msg);
        if (recv_msg == NULL)
                return -1;

        if (recv_msg->has_result == false) {
                irm_msg__free_unpacked(recv_msg, NULL);
                return -1;
        }

        ret = recv_msg->result;
        irm_msg__free_unpacked(recv_msg, NULL);

        return ret;
}


int ipcp_flow_dealloc(pid_t pid,
                      int   port_id)
{
        if (pid != 0) {
                ipcp_msg_t msg = IPCP_MSG__INIT;
                ipcp_msg_t * recv_msg = NULL;
                int ret = -1;

                msg.code        = IPCP_MSG_CODE__IPCP_FLOW_DEALLOC;
                msg.has_port_id = true;
                msg.port_id     = port_id;

                recv_msg = send_recv_ipcp_msg(pid, &msg);
                if (recv_msg == NULL)
                        return 0;

                if (recv_msg->has_result == false) {
                        ipcp_msg__free_unpacked(recv_msg, NULL);
                        return 0;
                }

                ret = recv_msg->result;
                ipcp_msg__free_unpacked(recv_msg, NULL);

                return ret;
        } else {
                irm_msg_t msg = IRM_MSG__INIT;
                irm_msg_t * recv_msg = NULL;
                int ret = -1;

                msg.code        = IRM_MSG_CODE__IPCP_FLOW_DEALLOC;
                msg.has_port_id = true;
                msg.port_id     = port_id;

                recv_msg = send_recv_irm_msg(&msg);
                if (recv_msg == NULL)
                        return 0;

                if (recv_msg->has_result == false) {
                        irm_msg__free_unpacked(recv_msg, NULL);
                        return 0;
                }

                ret = recv_msg->result;
                irm_msg__free_unpacked(recv_msg, NULL);

                return ret;
        }
}