/*
 * Copyright (C) 2006 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
 *
 * Permissive console output transliteration
 */

#include "config.h"
#include "i18n.h"
#include "console_output.h"

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

#include <glib.h>

#ifdef GXINE_CLIENT
/* defines & functions for gxine_client */

# define STDOUT stdout
# define STDERR stderr

static inline void log_console_text (const char *s, int e) {}
static inline void set_prev_line (const char *s) {}
static void console_push (const char *s, uint8_t e) {}

#define console_lock() do {} while (0)
#define console_unlock() do {} while (0)

#else
/* defines & functions for gxine */

# include <pthread.h>
# include <string.h>
# include "log_window.h"

# define STDOUT fd[0].file
# define STDERR fd[1].file

static struct {
  FILE *file;
  int fd[2];
} fd[2];

typedef struct {
  uint8_t err;
  char text[0];
} console_log_item_t;

static pthread_mutex_t write_lock = PTHREAD_MUTEX_INITIALIZER;
static char *prev_line = NULL;
static GAsyncQueue *console_log_queue = NULL;

#define console_lock() pthread_mutex_lock (&write_lock)
#define console_unlock() pthread_mutex_unlock (&write_lock)

static inline void set_prev_line (const char *s)
{
  free (prev_line);
  prev_line = s ? strdup (s) : NULL;
}

static void console_push (const char *text, uint8_t err)
{
  console_log_item_t *item = malloc (sizeof (console_log_item_t) + strlen (text) + 1);
  item->err = err;
  strcpy (item->text, text);
  g_async_queue_push (console_log_queue, item);
}

#endif /* defines & functions */

static GIConv xlate;

static void
xlate_write (const char *text, FILE *stream)
{
  gsize read = 0, written = 0;
  while (*text)
  {
    GError *error = NULL;
    text += read;
    char *buf = g_convert_with_iconv (text, -1, xlate, &read, &written, &error);
    if (buf && *buf)
      fputs (buf, stream);
    else if (error)
    {
      g_error_free (error);
      error = NULL;
      buf = g_convert_with_iconv (text, read, xlate, &read, &written, &error);
      if (buf && *buf)
	fputs (buf, stream);
      else
      {
	if (error)
	  g_error_free (error);
	fputc ('?', stream);
	if (!read && *text)
	  read = 1;
      }
    }
    g_free (buf);
    if (!read)
      break;
  }
}

static void
xlate_g_print (const char *text)
{
  console_lock ();
  xlate_write (text, STDOUT);
  console_push (text, 0);
  set_prev_line (text);
  console_unlock ();
}

static void
xlate_g_printerr (const char *text)
{
  console_lock ();
  xlate_write (text, STDERR);
  console_push (text, 1);
  set_prev_line (text);
  console_unlock ();
}

#ifndef GXINE_CLIENT
static void * __attribute__ ((noreturn))
console_log_captured (void *data)
{
  for (;;)
  {
    console_log_item_t *item = g_async_queue_pop (console_log_queue);
    log_console_text (item->text, item->err);
    free (item);
  }
  return NULL; /* shut up, gcc */
}

static void *
console_capture (void *data)
{
  uint8_t id = (uint8_t)(intptr_t)data;
  char *line = NULL;
  size_t size;
  FILE *in = fdopen (fd[id].fd[0], "r");

  setlinebuf (in);
  while (getline (&line, &size, in) != -1)
  {
    console_lock ();
    if (!prev_line || strcmp (line, prev_line))
    {
      fputs (line, fd[id].file);
      console_push (line, id);
      set_prev_line (line);
    }
    else
      set_prev_line (NULL);
    console_unlock ();
  }

  free (line);
  return NULL;
}
#endif

void
console_output_init (void)
{
  const char *cset;

  g_get_charset (&cset);
  char *dest = g_strconcat (cset, "//TRANSLIT", NULL);
  xlate = g_iconv_open (dest, "utf-8");
  g_free (dest);
  g_set_print_handler (xlate_g_print);
  g_set_printerr_handler (xlate_g_printerr);

#ifndef GXINE_CLIENT
  int i;
  FILE *newfd[2];
  pthread_t thread;
  pthread_attr_t attr;
  int origfd;

  console_log_queue = g_async_queue_new ();

  fd[0].file = stdout;
  fd[1].file = stderr;

  for (i = 0; i < 2; ++i)
  {
    setlinebuf (fd[i].file);
    if (pipe (fd[i].fd) || !(newfd[i] = fdopen (fd[i].fd[1], "a")))
      goto fail;
    setlinebuf (newfd[i]);
    /* preserve old stdout/stderr in fd[i].file */
    if ((origfd = dup(1 + i)) == -1 ||
        (fd[i].file = fdopen(origfd, "a")) == NULL) goto fail;
    /* redirect */
    close(1 + i);
    if (dup2(fd[i].fd[1], 1 + i) == -1) goto fail;
  }

  pthread_attr_init (&attr);
  pthread_attr_setstacksize (&attr, 128 * 1024);

  if (pthread_create (&thread, &attr, console_log_captured, NULL) ||
      pthread_create (&thread, &attr, console_capture, (void *)0) ||
      pthread_create (&thread, &attr, console_capture, (void *)1))
    goto fail;

  return;

fail:
  perror ("gxine: stdout/stderr redirection failed");
  exit (2);
#endif
}
