/*******************************************************************************

  Eurecom OpenAirInterface
  Copyright(c) 1999 - 2013 Eurecom

  This program is free software; you can redistribute it and/or modify it
  under the terms and conditions of the GNU General Public License,
  version 2, as published by the Free Software Foundation.

  This program is distributed in the hope 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.,
  51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.

  The full GNU General Public License is included in this distribution in
  the file called "COPYING".

  Contact Information
  Openair Admin: openair_admin@eurecom.fr
  Openair Tech : openair_tech@eurecom.fr
  Forums       : http://forums.eurecom.fr/openairinterface
  Address      : EURECOM, Campus SophiaTech, 450 Route des Chappes
                 06410 Biot FRANCE

*******************************************************************************/

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include <arpa/inet.h> /* To provide inet_addr */

#include "mme_config.h"
#include "intertask_interface_conf.h"

mme_config_t mme_config;

static
void config_init(mme_config_t *mme_config_p)
{
    memset(mme_config_p, 0, sizeof(mme_config_t));

    pthread_rwlock_init(&mme_config_p->rw_lock, NULL);

    mme_config_p->verbosity_level            = 0;
    mme_config_p->config_file                = NULL;
    mme_config_p->max_eNBs                   = MAX_NUMBER_OF_ENB;
    mme_config_p->max_ues                    = MAX_NUMBER_OF_UE;

    mme_config_p->emergency_attach_supported     = 0;
    mme_config_p->unauthenticated_imsi_supported = 0;

    /* Timer configuration */
    mme_config_p->gtpv1u_config.port_number = GTPV1_U_PORT_NUMBER;
    mme_config_p->s1ap_config.port_number   = S1AP_PORT_NUMBER;
    /* IP configuration */
    mme_config_p->ipv4.sgw_interface_name_for_S1u_S12_S4_up = DEFAULT_SGW_INTERFACE_NAME_FOR_S1U_S12_S4_UP;
    mme_config_p->ipv4.sgw_ip_address_for_S1u_S12_S4_up     = inet_addr(DEFAULT_SGW_IP_ADDRESS_FOR_S1U_S12_S4_UP);
    mme_config_p->ipv4.sgw_ip_netmask_for_S1u_S12_S4_up     = DEFAULT_SGW_IP_NETMASK_FOR_S1U_S12_S4_UP;

    mme_config_p->ipv4.sgw_interface_name_for_S5_S8_up      = DEFAULT_SGW_INTERFACE_NAME_FOR_S5_S8_UP;
    mme_config_p->ipv4.sgw_ip_address_for_S5_S8_up          = inet_addr(DEFAULT_SGW_IP_ADDRESS_FOR_S5_S8_UP);
    mme_config_p->ipv4.sgw_ip_netmask_for_S5_S8_up          = DEFAULT_SGW_IP_NETMASK_FOR_S5_S8_UP;

    mme_config_p->ipv4.pgw_interface_name_for_SGI           = DEFAULT_PGW_INTERFACE_NAME_FOR_S5_S8;
    mme_config_p->ipv4.pgw_ip_addr_for_SGI                  = inet_addr(DEFAULT_PGW_IP_ADDRESS_FOR_S5_S8);
    mme_config_p->ipv4.pgw_ip_netmask_for_SGI               = DEFAULT_PGW_IP_NETMASK_FOR_S5_S8;

    mme_config_p->ipv4.mme_interface_name_for_S1_MME        = DEFAULT_MME_INTERFACE_NAME_FOR_S1_MME;
    mme_config_p->ipv4.mme_ip_address_for_S1_MME            = inet_addr(DEFAULT_MME_IP_ADDRESS_FOR_S1_MME);
    mme_config_p->ipv4.mme_ip_netmask_for_S1_MME            = DEFAULT_MME_IP_NETMASK_FOR_S1_MME;

    mme_config_p->ipv4.mme_interface_name_for_S11           = DEFAULT_MME_INTERFACE_NAME_FOR_S11;
    mme_config_p->ipv4.mme_ip_address_for_S11               = inet_addr(DEFAULT_MME_IP_ADDRESS_FOR_S11);
    mme_config_p->ipv4.mme_ip_netmask_for_S11               = DEFAULT_MME_IP_NETMASK_FOR_S11;

    mme_config_p->ipv4.sgw_interface_name_for_S11           = DEFAULT_SGW_INTERFACE_NAME_FOR_S11;
    mme_config_p->ipv4.sgw_ip_address_for_S11               = inet_addr(DEFAULT_SGW_IP_ADDRESS_FOR_S11);
    mme_config_p->ipv4.sgw_ip_netmask_for_S11               = DEFAULT_SGW_IP_NETMASK_FOR_S11;

    mme_config_p->s6a_config.conf_file    = S6A_CONF_FILE;

    mme_config_p->itti_config.queue_size  = ITTI_QUEUE_MAX_ELEMENTS;
    mme_config_p->itti_config.log_file    = NULL;

    mme_config_p->sctp_config.in_streams  = SCTP_IN_STREAMS;
    mme_config_p->sctp_config.out_streams = SCTP_OUT_STREAMS;

    mme_config_p->relative_capacity = RELATIVE_CAPACITY;

    mme_config_p->mme_statistic_timer = MME_STATISTIC_TIMER_S;

    mme_config_p->gummei.nb_mme_gid = 1;
    mme_config_p->gummei.mme_gid    = calloc(1, sizeof(*mme_config_p->gummei.mme_gid));
    mme_config_p->gummei.mme_gid[0] = MMEGID;
    mme_config_p->gummei.nb_mmec    = 1;
    mme_config_p->gummei.mmec       = calloc(1, sizeof(*mme_config_p->gummei.mmec));
    mme_config_p->gummei.mmec[0]    = MMEC;

    /* Set the TAI */
    mme_config_p->gummei.nb_plmns   = 1;
    mme_config_p->gummei.plmn_mcc   = calloc(1, sizeof(*mme_config_p->gummei.plmn_mcc));
    mme_config_p->gummei.plmn_mnc   = calloc(1, sizeof(*mme_config_p->gummei.plmn_mnc));
    mme_config_p->gummei.plmn_tac   = calloc(1, sizeof(*mme_config_p->gummei.plmn_tac));

    mme_config_p->gummei.plmn_mcc[0] = PLMN_MCC;
    mme_config_p->gummei.plmn_mnc[0] = PLMN_MNC;
    mme_config_p->gummei.plmn_tac[0] = PLMN_TAC;

    mme_config_p->s1ap_config.outcome_drop_timer_sec = S1AP_OUTCOME_TIMER_DEFAULT;
}

static int config_parse_file(mme_config_t *mme_config_p)
{
    extern FILE *yyin;
    int ret = -1;

    if (mme_config_p == NULL)
        return ret;
    if (mme_config_p->config_file == NULL) {
        fprintf(stderr, "No Configuration file given... Attempting default values\n");
        return 0;
    }

    yyin = fopen(mme_config_p->config_file, "r");
    if (!yyin) {
        /* We failed to open the file */
        fprintf(stderr, "Unable to open the configuration file: %s (%d:%s)\n",
                mme_config_p->config_file, errno, strerror(errno));
        return errno;
    }

    /* Call the yacc parser */
    ret = yyparse(mme_config_p);

    /* Close the file descriptor */
    if (fclose(yyin) != 0) {
        fprintf(stderr, "Unable to close the configuration file: %s (%d:%s)\n",
                mme_config_p->config_file, errno, strerror(errno));
        return errno;
    }
    return ret;
}

#define DISPLAY_ARRAY(size, format, args...)                        \
do {                                                                \
    int i;                                                          \
    for (i = 0; i < size; i++) {                                    \
        fprintf(stdout, format, args);                              \
        if ((i != (size - 1)) && ((i + 1) % 10 == 0))               \
        {                                                           \
            fprintf(stdout, "\n        ");                          \
        }                                                           \
    }                                                               \
    if (i > 0)                                                      \
        fprintf(stdout, "\n");                                      \
} while(0)

static void config_display(mme_config_t *mme_config_p)
{
    fprintf(stdout, "==== EURECOM %s v%s ====\n", PACKAGE_NAME, PACKAGE_VERSION);
    fprintf(stdout, "Configuration:\n");
    fprintf(stdout, "- File ...............: %s\n", mme_config_p->config_file);
    fprintf(stdout, "- Verbosity level ....: %d\n", mme_config_p->verbosity_level);
    fprintf(stdout, "- Realm ..............: %s\n", mme_config_p->realm);
    fprintf(stdout, "- Max eNBs ...........: %u\n", mme_config_p->max_eNBs);
    fprintf(stdout, "- Max UEs ............: %u\n", mme_config_p->max_ues);
    fprintf(stdout, "- Emergency support ..: %s\n", mme_config_p->emergency_attach_supported == 0 ? "FALSE" : "TRUE");
    fprintf(stdout, "- Unauth IMSI support : %s\n", mme_config_p->unauthenticated_imsi_supported == 0 ? "FALSE" : "TRUE");
    fprintf(stdout, "- Max UEs ............: %u\n", mme_config_p->max_ues);
    fprintf(stdout, "- Relative capa ......: %u\n\n", mme_config_p->relative_capacity);
    fprintf(stdout, "- Statistics timer ...: %u (seconds)\n\n", mme_config_p->mme_statistic_timer);
    fprintf(stdout, "- S1-U:\n");
    fprintf(stdout, "    port number ......: %d\n", mme_config_p->gtpv1u_config.port_number);
    fprintf(stdout, "- S1-MME:\n");
    fprintf(stdout, "    port number ......: %d\n", mme_config_p->s1ap_config.port_number);
    fprintf(stdout, "- IP:\n");
    fprintf(stdout, "    s1-u iface .......: %s\n", mme_config_p->ipv4.sgw_interface_name_for_S1u_S12_S4_up);
    fprintf(stdout, "    s1-u ip ..........: %s/%d\n",
            inet_ntoa(*((struct in_addr *)&mme_config_p->ipv4.sgw_ip_address_for_S1u_S12_S4_up)),
            mme_config_p->ipv4.sgw_ip_netmask_for_S1u_S12_S4_up);
    fprintf(stdout, "    sgi iface ........: %s\n", mme_config_p->ipv4.pgw_interface_name_for_SGI);
    fprintf(stdout, "    sgi ip ...........: %s/%d\n",
            inet_ntoa(*((struct in_addr *)&mme_config_p->ipv4.pgw_ip_addr_for_SGI)),
            mme_config_p->ipv4.pgw_ip_netmask_for_SGI);
    fprintf(stdout, "    s1-MME iface .....: %s\n", mme_config_p->ipv4.mme_interface_name_for_S1_MME);
    fprintf(stdout, "    s1-MME ip ........: %s/%d\n",
            inet_ntoa(*((struct in_addr *)&mme_config_p->ipv4.mme_ip_address_for_S1_MME)),
            mme_config_p->ipv4.mme_ip_netmask_for_S1_MME);
    fprintf(stdout, "    s11 S-GW iface ...: %s\n", mme_config_p->ipv4.sgw_interface_name_for_S11);
    fprintf(stdout, "    s11 S-GW ip ......: %s/%d\n",
            inet_ntoa(*((struct in_addr *)&mme_config_p->ipv4.sgw_ip_address_for_S11)),
            mme_config_p->ipv4.sgw_ip_netmask_for_S11);
    fprintf(stdout, "    s11 MME iface ....: %s\n", mme_config_p->ipv4.mme_interface_name_for_S11);
    fprintf(stdout, "    s11 S-GW ip ......: %s/%d\n",
            inet_ntoa(*((struct in_addr *)&mme_config_p->ipv4.mme_ip_address_for_S11)),
            mme_config_p->ipv4.mme_ip_netmask_for_S11);
    fprintf(stdout, "- ITTI:\n");
    fprintf(stdout, "    queue size .......: %u (bytes)\n", mme_config_p->itti_config.queue_size);
    fprintf(stdout, "    log file .........: %s\n", mme_config_p->itti_config.log_file);
    fprintf(stdout, "- SCTP:\n");
    fprintf(stdout, "    in streams .......: %u\n", mme_config_p->sctp_config.in_streams);
    fprintf(stdout, "    out streams ......: %u\n", mme_config_p->sctp_config.out_streams);
    fprintf(stdout, "- GUMMEI:\n");
    fprintf(stdout, "    mme group ids ....:\n        ");
    DISPLAY_ARRAY(mme_config_p->gummei.nb_mme_gid, "| %u ", mme_config_p->gummei.mme_gid[i]);
    fprintf(stdout, "    mme codes ........:\n        ");
    DISPLAY_ARRAY(mme_config_p->gummei.nb_mmec, "| %u ", mme_config_p->gummei.mmec[i]);
    fprintf(stdout, "    plmns ............: (mcc.mnc:tac)\n        ");
    DISPLAY_ARRAY(mme_config_p->gummei.nb_plmns, "| %3u.%3u:%u ",
                  mme_config_p->gummei.plmn_mcc[i], mme_config_p->gummei.plmn_mnc[i],
                  mme_config_p->gummei.plmn_tac[i]);
    fprintf(stdout, "- S6A:\n");
    fprintf(stdout, "    conf file ........: %s\n", mme_config_p->s6a_config.conf_file);
}

static void usage(void)
{
    fprintf(stdout, "==== EURECOM %s v%s ====\n", PACKAGE_NAME, PACKAGE_VERSION);
    fprintf(stdout, "Please report any bug to: %s\n\n", PACKAGE_BUGREPORT);
    fprintf(stdout, "Usage: oaisim_mme [options]\n\n");
    fprintf(stdout, "Available options:\n");
    fprintf(stdout, "-h      Print this help and return\n");
    fprintf(stdout, "-i<interface name>\n");
    fprintf(stdout, "        Set the network card to use for IPv4 forwarding\n");
    fprintf(stdout, "-c<path>\n");
    fprintf(stdout, "        Set the configuration file for mme\n");
    fprintf(stdout, "        See template in UTILS/CONF\n");
    fprintf(stdout, "-K<file>\n");
    fprintf(stdout, "        Output intertask messages to provided file\n");
    fprintf(stdout, "-V      Print %s version and return\n", PACKAGE_NAME);
    fprintf(stdout, "-v[1-2] Debug level:\n");
    fprintf(stdout, "            1 -> ASN1 XER printf on and ASN1 debug off\n");
    fprintf(stdout, "            2 -> ASN1 XER printf on and ASN1 debug on\n");
}

extern void
nwGtpv1uDisplayBanner(void);

int config_parse_opt_line(int argc, char *argv[], mme_config_t *mme_config_p)
{
    int c;
    config_init(mme_config_p);
    /* Parsing command line */
    while ((c = getopt (argc, argv, "c:hi:K:v:V")) != -1) {
        switch (c) {
            case 'c': {
                /* Store the given configuration file. If no file is given,
                 * then the default values will be used.
                 */
                int config_file_len = 0;
                config_file_len = strlen(optarg);
                mme_config_p->config_file = malloc(sizeof(char) * (config_file_len + 1));
                memcpy(mme_config_p->config_file, optarg, config_file_len);
                mme_config_p->config_file[config_file_len] = '\0';
            } break;
            case 'i': {
                int interface_len = 0;

                /* Copying provided interface name to use for ipv4 forwarding */
                interface_len = strlen(optarg);
                mme_config_p->ipv4.sgw_interface_name_for_S1u_S12_S4_up = calloc(interface_len + 1, sizeof(char));
                memcpy(mme_config_p->ipv4.sgw_interface_name_for_S1u_S12_S4_up, optarg, interface_len);
                mme_config_p->ipv4.sgw_interface_name_for_S1u_S12_S4_up[interface_len] = '\0';
            } break;
            case 'v': {
                mme_config_p->verbosity_level = atoi(optarg);
            } break;
            case 'V': {
                fprintf(stdout, "==== EURECOM %s v%s ====\n"
                        "Please report any bug to: %s\n", PACKAGE_NAME, PACKAGE_VERSION,
                        PACKAGE_BUGREPORT);
                exit(0);
                nwGtpv1uDisplayBanner();
            } break;
            case 'K':
                mme_config_p->itti_config.log_file = strdup(optarg);
                break;
            case 'h': /* Fall through */
            default:
                usage();
                exit(0);
        }
    }
    /* Parse the configuration file using bison */
    if (config_parse_file(mme_config_p) != 0) {
        return -1;
    }
    /* Display yhe configuration */
    config_display(mme_config_p);
    return 0;
}