/*
 * Copyright (C) 2001-2017 the xine-project
 *
 * 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
 *
 * lirc (infrared control) code, originally based on oxine
 */

#include "globals.h"
#include "defs.h"

#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>

#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <glib.h>
#include <gtk/gtk.h>

#ifdef HAVE_LIRC
#include <lirc/lirc_client.h>
#endif

#include "lirc.h"
#include "engine.h"

#ifdef HAVE_LIRC
static pthread_t             gxine_lirc_thread;
static int                   gxine_lirc_thread_created = 0;
static pthread_mutex_t	     lirc_init_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t	     lirc_init_cond = PTHREAD_COND_INITIALIZER;
static struct lirc_config   *lircconfig;
static int                   gxine_lirc_fd;

/* lirc thread
  primary config file for lirc is ~/.gxine/lircrc
  secondary is ~/.config/lircrc
  legacy is ~/.lircrc
  prog name = gxine
  possible config = lots :-)
*/

static int gxine_lirc_tryload (const char *file)
{
  struct stat st;
  char *fname = g_build_filename (g_get_home_dir(), file, NULL);

  if (stat (fname, &st))
  {
    if (errno == ENOENT)
    {
      free (fname);
      return 1;
    }
    g_printerr (_("lirc: problem with ~/%s: %s\n"), file, strerror (errno));
    free (fname);
    return 1;
  }

  if (lirc_readconfig (fname, &lircconfig, NULL) != 0)
  {
    g_printerr(_("lirc: lirc_readconfig of %s failed\n"), fname);
    free (fname);
    return 1;
  }

  free (fname);
  return 0;
}

static void gxine_lirc_cleanup (void *data)
{
  lirc_deinit ();
}

static __attribute__ ((noreturn)) void *gxine_lirc_run (void *data)
{
  char *config;
  gboolean first = TRUE;

  for (;;)
  {
    pthread_mutex_lock (&lirc_init_mutex);

    gxine_lirc_fd = -1;
    while (gxine_lirc_fd == -1)
    {
      if ((gxine_lirc_fd = lirc_init ("gxine", 0)) != -1)
      {
        if (!first) {
	  logprintf ("lirc: reconnected\n");
        }
	break;
      }

      if (!first)
      {
	pthread_cond_signal (&lirc_init_cond);
	pthread_mutex_unlock (&lirc_init_mutex);
	pthread_cleanup_push (gxine_lirc_cleanup, NULL);
	sleep (1);
	pthread_testcancel();
	pthread_cleanup_pop (0);
	pthread_mutex_lock (&lirc_init_mutex);
	continue;
      }

      g_printerr (_("lirc: cannot initialise - disabling remote control\n"
		 "lirc: maybe lircd isn't running or you can't connect to the socket?\n"));
      failed:
      gxine_lirc_thread_created = 0;
      pthread_cond_signal (&lirc_init_cond);
      pthread_mutex_unlock (&lirc_init_mutex);
      pthread_exit(NULL);
    }

    first = FALSE;
    pthread_cleanup_push (gxine_lirc_cleanup, NULL);

    /* read ~/.gxine/lircrc then ~/.lircrc - definitions are not overridden */
    if ((gxine_lirc_tryload (".gxine/lircrc")
	 && gxine_lirc_tryload (".config/lircrc")
	 && gxine_lirc_tryload (".lircrc"))
	|| !lircconfig || !lircconfig->first)
    {
      /* if neither file has been read successfully or there's nothing for
       * gxine, exit now */
      g_printerr (_("lirc: no configuration found - disabling remote control\n"));
      goto failed;
    }

    pthread_cond_signal (&lirc_init_cond);
    pthread_mutex_unlock (&lirc_init_mutex);

    for (;;)
    {
      char *code;
      pthread_testcancel();

      if (lirc_nextcode (&code) == 0)
      {
	pthread_testcancel();

	if (code != NULL)
	{
	  int ret;
	  while ((ret = lirc_code2char (lircconfig, code, &config) == 0)
		 && config != NULL)
	  {
	    gchar *src = g_strdup_printf (_("LIRC binding %s.%s"),
					 lircconfig->current_mode, code);
	    engine_queue_push (config, NULL, NULL, NULL, NULL, src);
	    g_free (src);
	  }

	  free(code);
	}
      }
      else
      {
	/* lircd has exited? try reinitialising in case it's been restarted */
	logprintf ("lirc: socket read error\n");
	pthread_testcancel();
	break;
      }
    }

    pthread_cleanup_pop (1);
  }
}

static void gxine_lirc_start (void)
{
  pthread_attr_t attr;
  gxine_lirc_thread_created = 0;
  pthread_mutex_lock (&lirc_init_mutex);
  pthread_attr_init (&attr);
  pthread_attr_setstacksize (&attr, 256 * 1024); /* probably overkill */
  if (!pthread_create (&gxine_lirc_thread, &attr, gxine_lirc_run, NULL)) {
    gxine_lirc_thread_created = 1;
    pthread_cond_wait (&lirc_init_cond, &lirc_init_mutex);
  }
  pthread_attr_destroy (&attr);
  pthread_mutex_unlock (&lirc_init_mutex);
}

#else
#define gxine_lirc_thread_created (0)
#endif

void gxine_lirc_quit (void)
{
#ifdef HAVE_LIRC
  if (gxine_lirc_thread_created)
  {
    pthread_cancel (gxine_lirc_thread);
    lirc_freeconfig (lircconfig);
  }
#endif
}

static JSBool js_reload_lirc (JSContext *cx, uintN argc, jsval *vp)
{
  se_log_fncall ("reload_lirc");
  se_argc_check_max (0, "reload_lirc");
#ifdef HAVE_LIRC
  gxine_lirc_quit ();
  if (gxine_lirc_thread_created)
    pthread_join (gxine_lirc_thread, NULL);
  gxine_lirc_start ();
#endif
  JS_SET_RVAL (cx, vp, gxine_lirc_thread_created ? JSVAL_TRUE : JSVAL_FALSE);
  return JS_TRUE;
}

void gxine_lirc_init (void)
{
  se_defun (gse, NULL, "reload_lirc", js_reload_lirc, 0, JSFUN_FAST_NATIVE,
	    SE_GROUP_HIDDEN, NULL, NULL);
#ifdef HAVE_LIRC
  gxine_lirc_start ();
#endif
}
