/*
 * Author: Yasunobu Chiba
 *
 * Copyright (C) 2008-2012 NEC Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation.
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <limits.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "chibach.h"
#include "chibach_private.h"
#include "daemon.h"
#include "doubly_linked_list.h"
#include "log.h"
#include "messenger.h"
#include "openflow_switch_interface.h"
#include "stat.h"
#include "timer.h"
#include "utility.h"
#include "wrapper.h"


static bool initialized = false;
static bool chibach_started = false;
static bool run_as_daemon = false;
static char *chibach_log = NULL;
static char *chibach_pid = NULL;
static char *chibach_name = NULL;
static uint64_t datapath_id = 0;

static struct {
  uint32_t ip;
  uint16_t port;
} controller;


static struct option long_options[] = {
  { "datapath_id", required_argument, NULL, 'i' },
  { "controller", required_argument, NULL, 'c' },
  { "port", required_argument, NULL, 'p' },
  { "daemonize", no_argument, NULL, 'd' },
  { "logging_level", required_argument, NULL, 'l' },
  { "help", no_argument, NULL, 'h' },
  { NULL, 0, NULL, 0 },
};

static char short_options[] = "i:c:p:dl:h";


void
usage() {
  printf(
    "Usage: %s [OPTION]...\n"
    "\n"
    "  -i, --datapath_id=DATAPATH_ID set datapath id\n"
    "  -c, --controller=IP_ADDR      set controller host\n"
    "  -p, --port=TCP_PORT           set controller TCP port\n"
    "  -d, --daemonize               run in the background\n"
    "  -l, --logging_level=LEVEL     set logging level\n"
    "  -h, --help                    display this help and exit\n",
    get_chibach_name()
  );
}


static const char *
get_chibach_log() {
  if ( chibach_log == NULL ) {
    char path[ PATH_MAX ];
    sprintf( path, "%s/log", get_chibach_tmp() );
    chibach_log = xstrdup( path );
  }
  return chibach_log;
}


static const char *
get_chibach_pid() {
  if ( chibach_pid == NULL ) {
    char path[ PATH_MAX ];
    sprintf( path, "%s/pid", get_chibach_tmp() );
    chibach_pid = xstrdup( path );
  }
  return chibach_pid;
}


const char *
get_chibach_name() {
  if ( chibach_name == NULL ) {
    return "";
  }

  return chibach_name;
}


static void
maybe_finalize_openflow_switch_interface() {
  if ( openflow_switch_interface_is_initialized() ) {
    finalize_openflow_switch_interface();
  }
}


static void
die_unless_initialized() {
  if ( !initialized ) {
    die( "Chibach is not initialized. Call init_chibach() first." );
  }
}


static void
finalize_chibach() {
  die_unless_initialized();

  debug( "Terminating chibach..." );

  maybe_finalize_openflow_switch_interface();

  finalize_messenger();
  finalize_stat();
  finalize_timer();
  chibach_started = false;
  unlink_pid( get_chibach_pid(), get_chibach_name() );

  unset_chibach_home();
  unset_chibach_tmp();

  xfree( chibach_name );
  chibach_name = NULL;

  xfree( chibach_log );
  chibach_log = NULL;

  initialized = false;
}


static void
check_chibach_tmp() {
  struct stat st;
  int ret = stat( get_chibach_tmp(), &st );
  if ( ( ret != 0 ) && ( errno == ENOENT ) ) {
    die( "Chibach temporary directory does not exist: %s", get_chibach_tmp() );
  }
}


static void
reset_getopt() {
  optind = 0;
  opterr = 1;
}


static void
parse_argv( int *argc, char ***argv ) {
  assert( argc != NULL );
  assert( argv != NULL );

  int argc_tmp = *argc;
  char *new_argv[ *argc ];

  run_as_daemon = false;
  controller.ip = 0x7f000001;
  controller.port = 6633;

  for ( int i = 0; i < *argc; ++i ) {
    new_argv[ i ] = ( *argv )[ i ];
  }

  for ( ;; ) {
    opterr = 0;
    int c = getopt_long( *argc, *argv, short_options, long_options, NULL );

    if ( c == -1 ) {
      break;
    }

    switch ( c ) {
      case 'i':
        if ( optarg != NULL ) {
          string_to_datapath_id( optarg, &datapath_id );
        }
        break;
      case 'c':
        if ( optarg != NULL ) {
          struct in_addr addr;
          inet_aton( optarg, &addr );
          controller.ip = ntohl( addr.s_addr );
        }
        break;
      case 'p':
        if ( optarg != NULL && atoi( optarg ) <= UINT16_MAX ) {
          controller.port = ( uint16_t ) atoi( optarg );
        }
        break;
      case 'd':
        run_as_daemon = true;
        break;
      case 'l':
        set_logging_level( optarg );
        break;
      case 'h':
        usage();
        exit( EXIT_SUCCESS );
        break;
      default:
        continue;
    }

    if ( optarg == NULL || strchr( new_argv[ optind - 1 ], '=' ) != NULL ) {
      argc_tmp -= 1;
      new_argv[ optind - 1 ] = NULL;
    }
    else {
      argc_tmp -= 2;
      new_argv[ optind - 1 ] = NULL;
      new_argv[ optind - 2 ] = NULL;
    }
  }

  for ( int i = 0, j = 0; i < *argc; ++i ) {
    if ( new_argv[ i ] != NULL ) {
      ( *argv )[ j ] = new_argv[ i ];
      j++;
    }
  }
  if ( argc_tmp < *argc ) {
    ( *argv )[ argc_tmp ] = NULL;
  }
  *argc = argc_tmp;

  reset_getopt();
}


static void
ignore_sigpipe() {
  struct sigaction sigpipe_ignore;

  memset( &sigpipe_ignore, 0, sizeof( struct sigaction ) );
  sigpipe_ignore.sa_handler = SIG_IGN;
  sigaction( SIGPIPE, &sigpipe_ignore, NULL );
}


static void
set_exit_handler() {
  struct sigaction signal_exit;

  memset( &signal_exit, 0, sizeof( struct sigaction ) );
  signal_exit.sa_handler = ( void * ) stop_chibach;
  sigaction( SIGINT, &signal_exit, NULL );
  sigaction( SIGTERM, &signal_exit, NULL );
}


static void
set_dump_stats_as_external_callback() {
  set_external_callback( dump_stats );
}


static void
set_usr1_handler() {
  struct sigaction signal_usr1;

  memset( &signal_usr1, 0, sizeof( struct sigaction ) );
  signal_usr1.sa_handler = ( void * ) set_dump_stats_as_external_callback;
  sigaction( SIGUSR1, &signal_usr1, NULL );
}


static void
maybe_daemonize() {
  if ( run_as_daemon ) {
    daemonize( get_chibach_home() );
  }
}


void
init_chibach( int *argc, char ***argv ) {
  assert( argc != NULL );
  assert( argv != NULL );

  chibach_log = NULL;
  initialized = false;
  chibach_started = false;
  run_as_daemon = false;

  chibach_name = xstrdup( basename( *argv[ 0 ] ) );

  parse_argv( argc, argv );
  set_chibach_home();
  set_chibach_tmp();
  check_chibach_tmp();
  if ( run_as_daemon ) {
    init_log( get_chibach_name(), get_chibach_log(), LOGGING_TYPE_FILE );
  }
  else {
    init_log( get_chibach_name(), get_chibach_log(), LOGGING_TYPE_FILE | LOGGING_TYPE_STDOUT );
  }
  ignore_sigpipe();
  set_exit_handler();
  set_usr1_handler();
  init_stat();
  init_timer();
  init_messenger( get_chibach_tmp() );
  if ( datapath_id != 0 ) {
    init_openflow_switch_interface( datapath_id, controller.ip, controller.port );
  }

  initialized = true;
}


static void
start_chibach_up() {
  debug( "Starting chibach ... (CHIBACH_HOME = %s)", get_chibach_home() );

  maybe_daemonize();
  write_pid( get_chibach_pid(), get_chibach_name() );
  chibach_started = true;

  start_messenger();
}


static void
start_chibach_down() {
  finalize_chibach();
}


void
start_chibach() {
  start_chibach_up();
  start_event_handler();
  start_chibach_down();
}


void
stop_chibach() {
  stop_event_handler();
  stop_messenger();
}


uint64_t
get_datapath_id() {
  return datapath_id;
}


/*
 * Local variables:
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * End:
 */