/*
 * Copyright (C) 2002 Stefan Holst
 * Copyright (C) 2003-2017 the xine project team
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 *
 * gxine unix domain socket remote control interface
 */

#include "config.h"

#include <stddef.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#include <pthread.h>
#include <signal.h>

#include "globals.h"
#include "engine.h"
#include "server.h"
#include "utils.h"

/*
#define LOG
*/

#define SOCKET_LEAFNAME	"socket"
#define BUF_SIZE        1024

static int       gxsocket;
static pthread_t server_thread;

/* create an unix domain socket */
static int make_socket (char *path)
{
  union {
    struct sockaddr_un u;
    struct sockaddr s;
  } adr = { .u = {0} };
  int sock = socket (PF_UNIX, SOCK_STREAM, 0);
  if (sock < 0)
  {
    perror ("socket");
    return -1;
  }

  unlink (path);

  /* fill socket address structures */

  adr.u.sun_family = AF_UNIX;
  strncpy (adr.u.sun_path, path, sizeof (adr.u.sun_path)-1);

  /* bind the name to the descriptor */
  if (bind (sock, &adr.s, sizeof (adr)) < 0)
  {
    close(sock);
    perror ("bind");
    return -1;
  }

  logprintf ("server: socket '%s' created \n", path);

  return sock;
}

static void sock_write (int fd, char *msg)
{
  int len = strlen (msg);
  int pos = 0;

  while (pos < len)
  {
    int n = write (fd, msg+pos, len-pos);

    if (n <= 0)
      switch (errno)
      {
      case EAGAIN:
	continue;
      default:
	perror ("server: write error");
	return;
      }
    else
      pos += n;
  }
}

static void __attribute__ ((format (printf, 2, 3)))
error_report (void *s, const char *msg, ...)
{
  int fd = *(int *)s;
  char *amsg;
  va_list ap;
  va_start (ap, msg);
  amsg = g_strdup_vprintf (msg, ap);
  va_end (ap);

  sock_write (fd, amsg);
  sock_write (fd, "\n\n");
  free (amsg);
}

static int running;

static __attribute__ ((noreturn)) void *socket_listener (void *data)
{
  listen (gxsocket, 1);
  while (running)
  {
    struct sockaddr client;
    socklen_t size = sizeof client;
    char buf[BUF_SIZE + 1];
    int s = accept (gxsocket, &client, &size);

    if (s == -1)
    {
      perror ("accept");
      sleep (1);
      continue;
    }

    logprintf ("server: connected.\n");
    sock_write(s, _("This is gxine\n\nJavascript interface. Type 'help();' for help.\n"));

    int buf_len = 0;
    int listening = 1;

    while (listening)
    {
      int n = read (s, buf + buf_len, 1);
      if (n <= 0)
      {
	switch (errno)
	{
	case EAGAIN:
	  continue;
	default:
	  if (errno)
	    g_printerr (_("server: read error %d\n"), errno);
	  else
	case ESPIPE:
	case EPIPE:
	case ECONNRESET:
	    g_print (_("server: client disconnected\n"));
	  listening = 0;
	  break;
	}
      }
      else if (buf[buf_len] == '\n' || buf[buf_len] == '\r')
      {
	buf[buf_len]=0;
	gdk_threads_enter();
	engine_exec_ext (buf, error_report, &s, NULL, NULL);
	gdk_threads_leave();
	buf_len = 0;
      }
      else if (buf_len < BUF_SIZE)
	buf_len += n;
    }
    logprintf ("server: closing client connection.\n");
    close(s);
  }
  logprintf ("server: listener thread exit\n");
  pthread_exit(NULL);
}

void server_setup (void)
{
  char *filename = get_config_filename (SOCKET_LEAFNAME);
  if (strlen (filename) >= sizeof (((struct sockaddr_un *)0)->sun_path))
  {
    fputs (_("server: name too long - gxine_client will not be able to connect\n"),
	   stderr);
    gxsocket = -1;
  }
  else
    gxsocket = make_socket (filename);
  free (filename);
}

void server_start (void)
{
  if (gxsocket >= 0)
  {
    int err;
    pthread_attr_t attr;
    running = 1;
    pthread_attr_init (&attr);
    pthread_attr_setstacksize (&attr, 64 * 1024); /* should be fine... */
    err = pthread_create (&server_thread, &attr, socket_listener, NULL);
    pthread_attr_destroy (&attr);
    if (err) {
      fprintf (stderr, "server: can't create a thread: %d\n", err);
      running = 0;
      close(gxsocket);
    }
  }
}

void server_stop (void)
{
  if (running)
  {
    void *p;
    running = 0;
    sleep (1);
    pthread_cancel (server_thread);
    pthread_join (server_thread, &p);
    /* shutdown (gxsocket, SHUT_RDWR);*/
    close(gxsocket);
  }
}


/*
 * functions to connect to already running gxine
 */

static int   client_fd = -1;
static char  tstr[80];

static void sigpipe_handler (int sn)
{
  logprintf ("server: SIGPIPE received.\n");
  signal (SIGPIPE, sigpipe_handler);
}

int server_client_connect (void)
{
  union {
    struct sockaddr_un u;
    struct sockaddr s;
  } cli_adr = { .u = {0} }, serv_adr = { .u = {0} };
  int                length = sizeof(struct sockaddr_un);

  signal (SIGPIPE, sigpipe_handler);

  /* server filename */
  char *filename = get_config_filename (SOCKET_LEAFNAME);
  if (strlen (filename) >= sizeof (cli_adr.u.sun_path))
    goto nothing; /* no point in printing anything... */

  logprintf ("server: trying to connect to already running instance of gxine (%s)...\n",
	    filename);

  client_fd = socket (AF_UNIX, SOCK_STREAM, 0);
  if (client_fd < 0)
  {
    perror ("socket");
    goto nothing;
  }

  /* initialize the client address structure */
  cli_adr.u.sun_family = AF_UNIX;

  snprintf (tstr, sizeof(tstr), "/tmp/gxine_%d", getpid ());
  strcpy (cli_adr.u.sun_path, tstr);
  unlink (tstr);

  /* bind the socket to the client's address */
  if (bind (client_fd, &cli_adr.s, length) < 0)
  {
    perror ("bind");
    goto nothing;
  }

  /* initialize the server address structure */
  serv_adr.u.sun_family = AF_UNIX;
  strcpy (serv_adr.u.sun_path, filename);

  if (connect (client_fd, &serv_adr.s, sizeof (serv_adr)) < 0)
  {
    logperror ("connect");
    server_client_stop ();
    goto nothing;
  }

  g_print (_("server: connected to existing gxine instance.\n"));
  atexit (server_client_stop);

  free (filename);
  return 1;

nothing:
  free (filename);
  return 0;
}

int server_client_send (const char *cmd)
{
  return write (client_fd, cmd, strlen (cmd));
}

void server_client_stop (void)
{
  if (client_fd < 0)
    return;

  close(client_fd);
  client_fd = -1;
  unlink (tstr);
}
