// SPDX-License-Identifier: GPL-2.0-only
/*
 * multiskkserv-ctl.c -- multiskkserv control utility
 * (C)Copyright 2001-2024 by Hiroshi Takekawa
 * This file is part of multiskkserv.
 *
 * Last Modified: Sat Feb 24 15:54:22 2024.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <pthread.h>

#include <sys/param.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define REQUIRE_STRING_H
#include "compat.h"
#include "common.h"

#ifndef HAVE_GETADDRINFO
#  include "getaddrinfo.h"
#endif

#include "dlist.h"
#include "libstring.h"

#include "multiskkserv.h"
#include "getopt-support.h"

#define BIND_ERROR 1
#define ACCEPT_ERROR 2
#define NO_DICTIONARY_ERROR 3
#define MEMORY_ERROR 4
#define INVALID_PORT_ERROR 5
#define INVALID_NUMBER_ERROR 6
#define INVALID_FAMILY_ERROR 7

static Option options[] = {
  { "help",     'h', _NO_ARGUMENT,       "Show help message." },
  { "server",   's', _REQUIRED_ARGUMENT, "Specify which ip to listen." },
  { "port",     'p', _REQUIRED_ARGUMENT, "Specify which port to listen." },
  { "family",   'f', _REQUIRED_ARGUMENT, "Specify address family: INET or INET6 or UNSPEC." },
  { "yomi",     'y', _REQUIRED_ARGUMENT, "Specify yomi for translation test." },
  { NULL }
};

static int
socket_connect(char *remote, char **sstr, int port, char *service __attribute__((unused)), int family)
{
  String *s;
  struct addrinfo hints, *res0, *res;
  int sock = 0;
  int opt, gaierr;
  int try_default_portnum;
  char sbuf[NI_MAXSERV];
  char ipbuf[INET6_ADDRSTRLEN];
  char selfname[MAXHOSTNAMELEN + 1];

  s = string_create();

  gethostname(selfname, MAXHOSTNAMELEN);
  selfname[MAXHOSTNAMELEN] = '\0';
  string_cat(s, selfname);
  string_cat(s, ":");
  string_cat(s, remote);
  string_cat(s, ":");

  if (port > -1) {
    snprintf(sbuf, sizeof(sbuf), "%d", port);
    sbuf[sizeof(sbuf) - 1] = '\0';
    try_default_portnum = 0;
  } else {
    strncpy(sbuf, SKKSERV_SERVICE, sizeof(sbuf));
    try_default_portnum = 1;
  }

  for (;;) {
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = family;
    hints.ai_socktype = SOCK_STREAM;
    res0 = NULL;
    if ((gaierr = getaddrinfo(remote, sbuf, &hints, &res0))) {
      if (gaierr == EAI_SERVICE && try_default_portnum) {
	snprintf(sbuf, sizeof(sbuf), "%d", SKKSERV_PORT);
	sbuf[sizeof(sbuf) - 1] = '\0';
	try_default_portnum = 0;
	continue;
      }
#ifdef HAVE_GETADDRINFO
      err_message_fnc("getaddrinfo(): %s(gaierr = %d)\n", gai_strerror(gaierr), gaierr);
#endif
      return -2;
    }
    for (res = res0; res; res = res->ai_next) {
      if (res->ai_family != AF_INET && res->ai_family != AF_INET6)
	continue;
      if ((sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0)
	continue;
      ipbuf[0] = '\0';
#ifdef HAVE_GETNAMEINFO
      if ((gaierr = getnameinfo(res->ai_addr, res->ai_addrlen, ipbuf, sizeof(ipbuf),
				NULL, 0, NI_NUMERICHOST))) {
	err_message_fnc("getnameinfo(): %s\n", gai_strerror(gaierr));
	if (res0)
	  freeaddrinfo(res0);
	close(sock);
	return -3;
      }
#else
      {
	struct sockaddr_in *sp_v4 = (struct sockaddr_in *)&res->ai_addr;
	strncpy(ipbuf, inet_ntoa(sp_v4->sin_addr), sizeof(ipbuf));
      }
#endif
      ipbuf[sizeof(ipbuf) - 1] = '\0';
      opt = 1;
      setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      if (connect(sock, res->ai_addr, res->ai_addrlen)) {
	perror(PROGNAME "-ctl: connect");
	close(sock);
	sock = -1;
	continue;
      }
      string_cat(s, ipbuf);
      string_cat(s, ":");
      break;
    }
    if (res0)
      freeaddrinfo(res0);
    break;
  }

  string_cat(s, " ");
  *sstr = strdup(string_get(s));
  string_destroy(s);

  return sock;
}

static void
show_stat(char *remote, int port, int family)
{
  int sock;
  char *sstr;
  char rbuf[SKKSERV_REQUEST_SIZE];

  if ((sock = socket_connect(remote, &sstr, port, (char *)SKKSERV_SERVICE, family)) < 0) {
    err_message_fnc("Cannot make a connection.\n");
    return;
  }

  snprintf(rbuf, sizeof(rbuf), "S\n");
  write(sock, rbuf, strlen(rbuf));
  if (read(sock, rbuf, sizeof(rbuf)) > 0) {
    int nconns, nactives;

    sscanf(rbuf, "S%d:%d", &nconns, &nactives);
    printf("%d total connections, %d connections active.\n", nconns, nactives);
  }

  free(sstr);
  close(sock);
}

static void
translate(char *remote, int port, int family, char *yomi)
{
  int sock;
  char *sstr;
  char rbuf[SKKSERV_REQUEST_SIZE];
  int read_size;

  if ((sock = socket_connect(remote, &sstr, port, (char *)SKKSERV_SERVICE, family)) < 0) {
    err_message_fnc("Cannot make a connection.\n");
    return;
  }

  snprintf(rbuf, sizeof(rbuf), "1%s ", yomi);
  write(sock, rbuf, strlen(rbuf));
  if ((read_size = read(sock, rbuf, sizeof(rbuf))) > 0) {
    rbuf[read_size] = '\0';
    printf("%s", rbuf);
  }

  free(sstr);
  close(sock);
}

static void
usage(void)
{
  printf(PROGNAME "-ctl version " VERSION "\n");
  printf("(C)Copyright 2001-2024 by Hiroshi Takekawa\n\n");
  printf("usage: multiskkserv-ctl [options] ['stat']\n");

  printf("Options:\n");
  print_option_usage(options);
}

int
main(int argc, char **argv)
{
  extern char *optarg;
  extern int optind;
  char *optstr;
  char *servername = NULL;
  char *yomi = NULL;
  int ch;
  int port = -1;
  int family = AF_INET;

  optstr = gen_optstring(options);
  while ((ch = getopt(argc, argv, optstr)) != -1) {
    switch (ch) {
    case 'h':
      usage();
      return 0;
    case 's':
      servername = strdup(optarg);
      break;
    case 'p':
      port = atoi(optarg);
      if (port < 0 || port > 65535) {
	err_message_fnc("Invalid port number(%d).\n", port);
	return INVALID_PORT_ERROR;
      }
      break;
    case 'f':
      if (strcasecmp("INET", optarg) == 0)
	family = AF_INET;
      else if (strcasecmp("INET6", optarg) == 0)
	family = AF_INET6;
      else if (strcasecmp("UNSPEC", optarg) == 0)
	family = AF_UNSPEC;
      else if (strcasecmp("4", optarg) == 0)
	family = AF_INET;
      else if (strcasecmp("6", optarg) == 0)
	family = AF_INET6;
      else if (strcasecmp("IPv4", optarg) == 0)
	family = AF_INET;
      else if (strcasecmp("IPv6", optarg) == 0)
	family = AF_INET6;
      else {
	err_message_fnc("Invalid family(%s).\n", optarg);
	return INVALID_FAMILY_ERROR;
      }
      break;
    case 'y':
      yomi = strdup(optarg);
      break;
    default:
      usage();
      return 0;
    }
  }
  free(optstr);

  if (optind == argc) {
    usage();
    return 0;
  }

  if (strcasecmp(argv[optind], "help") == 0) {
    usage();
  } else if (strcasecmp(argv[optind], "stat") == 0) {
    if (!servername)
      servername = strdup("localhost");
    show_stat(servername, port, family);
    free(servername);
  } else if (strcasecmp(argv[optind], "tran") == 0 && yomi) {
    if (!servername)
      servername = strdup("localhost");
    translate(servername, port, family, yomi);
    free(servername);
  } else {
    usage();
    return 1;
  }

  return 0;
}
