/*****************************************************************
 * gmerlin - a general purpose multimedia framework and applications
 *
 * Copyright (c) 2001 - 2012 Members of the Gmerlin project
 * gmerlin-general@lists.sourceforge.net
 * http://gmerlin.sourceforge.net
 *
 * 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, see <http://www.gnu.org/licenses/>.
 * *****************************************************************/

#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gavl/metatags.h>

#include <config.h>

#include <gmerlin/log.h>
#include <gmerlin/utils.h>
#include <gmerlin/state.h>

#include <gui_gtk/logwindow.h>
#include <gui_gtk/gtkutils.h>


/* Delay between update calls in Milliseconds */
#define DELAY_TIME 50

/* State variables */

static const bg_state_var_desc_t state_vars[] =
  {
    { GAVL_META_X,              GAVL_TYPE_INT, GAVL_VALUE_INIT_INT(10)  },
    { GAVL_META_Y,              GAVL_TYPE_INT, GAVL_VALUE_INIT_INT(10)  },
    { GAVL_META_WIDTH,          GAVL_TYPE_INT, GAVL_VALUE_INIT_INT(100) },
    { GAVL_META_HEIGHT,         GAVL_TYPE_INT, GAVL_VALUE_INIT_INT(100) },
    { BG_GTK_LOGWINDOW_VISIBLE, GAVL_TYPE_INT, GAVL_VALUE_INIT_INT(0)   },
    { /* End */ },
  };

struct bg_gtk_log_window_s
  {
  GtkWidget * window;
  GtkWidget * textview;
  GtkTextBuffer * buffer;
  GtkWidget * scrolledwindow;
  
  bg_control_t ctrl;
  
  // bg_msg_sink_t * sink;

  GtkTextTagTable * tag_table;
  GtkTextTag      * info_tag;
  GtkTextTag      * debug_tag;
  GtkTextTag      * error_tag;
  GtkTextTag      * warning_tag;
  
  int visible;

  int num_messages;
  int max_messages;
  
  int x, y, width, height;

  char * last_error;
  
  //  gavl_dictionary_t state;
  int have_state;

  bg_msg_sink_t * log_sink;
  };

static gboolean delete_callback(GtkWidget * w, GdkEventAny * event,
                                gpointer data)
  {
  gavl_value_t val;
  bg_gtk_log_window_t * win = data;
  
  gavl_value_init(&val);
  gavl_value_set_int(&val, 0);
  
  bg_state_set(NULL, 1, BG_GTK_LOGWINDOW_STATE_CTX, BG_GTK_LOGWINDOW_VISIBLE,
               &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  
  gtk_widget_hide(win->window);
  win->visible = 0;
  return TRUE;
  }

static void delete_first_line(bg_gtk_log_window_t * win)
  {
  GtkTextIter start_iter, end_iter;

  gtk_text_buffer_get_iter_at_line(win->buffer, &start_iter, 0);
  gtk_text_buffer_get_iter_at_line(win->buffer, &end_iter, 1);
  gtk_text_buffer_delete(win->buffer, &start_iter, &end_iter);
  win->num_messages--;
  }

static void changed_callback(gpointer data)
  {
  bg_gtk_log_window_t * w;
  GtkTextIter iter;
  GtkTextMark * mark = NULL;
  w = data;
 
  gtk_text_buffer_get_end_iter(w->buffer, &iter);

  mark = gtk_text_buffer_create_mark(w->buffer, NULL, &iter, FALSE);
  gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(w->textview), mark);
  gtk_text_buffer_delete_mark(w->buffer, mark);
  }

static int handle_log_message(void * data, gavl_msg_t * msg)
  {
  bg_gtk_log_window_t * w = data;

  
  switch(msg->NS)
    {
    case GAVL_MSG_NS_LOG:
      {
      gavl_log_level_t level;
      const char * domain = NULL;
      const char * message = NULL;
      GtkTextTag * tag = NULL;
      char * str;
      GtkTextIter iter;
      int i;
      char ** lines;
      int got_message = 0;

      while(w->num_messages > w->max_messages - 1)
        delete_first_line(w);

      if(!gavl_log_msg_get(msg, &level,
                          &domain, &message))
        return 1;
      
      
      switch(level)
        {
        case GAVL_LOG_DEBUG:
          tag = w->debug_tag;
          break;
        case GAVL_LOG_WARNING:
          tag = w->warning_tag;
          break;
        case GAVL_LOG_ERROR:
          tag = w->error_tag;
          w->last_error = gavl_strrep(w->last_error, message);
          break;
        case GAVL_LOG_INFO:
          tag = w->info_tag;
          break;
        }

      gtk_text_buffer_get_end_iter(w->buffer, &iter);

      if(*message == '\0') /* Empty string */
        {
        str = bg_sprintf("[%s]\n", domain);
        gtk_text_buffer_insert_with_tags(w->buffer,
                                         &iter,
                                         str, -1, tag, NULL);
        }
      else
        {
        lines = bg_strbreak(message, '\n');
        i = 0;
        while(lines[i])
          {
          str = bg_sprintf("[%s]: %s\n", domain, lines[i]);
          gtk_text_buffer_insert_with_tags(w->buffer,
                                           &iter,
                                           str, -1, tag, NULL);
          free(str);
          i++;
          }
        bg_strbreak_free(lines);
        }
      w->num_messages++;
      got_message = 1;
      
      if(got_message)
        changed_callback(w);
      }
      break;
    }

  return 1;
  }

static int handle_message(void * data, gavl_msg_t * msg)
  {
  bg_gtk_log_window_t * w = data;

  switch(msg->NS)
    {
    case BG_MSG_NS_STATE:
      switch(msg->ID)
        {
        case BG_MSG_STATE_CHANGED:
          if(w->have_state)
            return 1;
          
          if(!strcmp(gavl_msg_get_arg_string_c(msg, 1), BG_GTK_LOGWINDOW_STATE_CTX))
            {
            int last = 0;
            const char * var = NULL;
            gavl_value_t val;
            
            gavl_value_init(&val);
            bg_msg_get_state(msg, &last, NULL, &var, &val, NULL);

            //             fprintf(stderr, "Logwindow state changed %d %s ", last, var);
            //            gavl_value_dump(&val, 0);
            //            fprintf(stderr, "\n");
            
            if(!strcmp(var, GAVL_META_X))
              gavl_value_get_int(&val, &w->x);
            else if(!strcmp(var, GAVL_META_Y))
              gavl_value_get_int(&val, &w->y);
            else if(!strcmp(var, GAVL_META_WIDTH))
              gavl_value_get_int(&val, &w->width);
            else if(!strcmp(var, GAVL_META_HEIGHT))
              gavl_value_get_int(&val, &w->height);
            else if(!strcmp(var, BG_GTK_LOGWINDOW_VISIBLE))
              gavl_value_get_int(&val, &w->visible);
            
            if(last)
              {
              if(w->visible)
                bg_gtk_log_window_show(w);
              else
                bg_gtk_log_window_hide(w);

              w->have_state = 1;
              }
            }
          break;
        }
      break;
    }
  
  return 1;
  }

static gboolean idle_callback(gpointer data)
  {
  bg_gtk_log_window_t * w = data;
  bg_msg_sink_iteration(w->log_sink);
  bg_msg_sink_iteration(w->ctrl.evt_sink);
  return TRUE;
  }

static gboolean configure_callback(GtkWidget * w, GdkEventConfigure *event,
                                   gpointer data)
  {
  gavl_value_t val;
  bg_gtk_log_window_t * win = data;

  gavl_value_init(&val);
  
  win->x = event->x;
  win->y = event->y;
  win->width = event->width;
  win->height = event->height;
  gdk_window_get_root_origin(gtk_widget_get_window(win->window), &win->x, &win->y);

  gavl_value_set_int(&val, win->x);
  bg_state_set(NULL, 0, BG_GTK_LOGWINDOW_STATE_CTX, GAVL_META_X, &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  gavl_value_set_int(&val, win->y);
  bg_state_set(NULL, 0, BG_GTK_LOGWINDOW_STATE_CTX, GAVL_META_Y, &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  gavl_value_set_int(&val, win->width);
  bg_state_set(NULL, 0, BG_GTK_LOGWINDOW_STATE_CTX, GAVL_META_WIDTH, &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  gavl_value_set_int(&val, win->height);
  bg_state_set(NULL, 1, BG_GTK_LOGWINDOW_STATE_CTX, GAVL_META_HEIGHT, &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  
  return FALSE;
  }

bg_gtk_log_window_t * bg_gtk_log_window_create(const char * app_name)
  {
  char * tmp_string;
  bg_gtk_log_window_t * ret;
  ret = calloc(1, sizeof(*ret));

  /* Create window */
  ret->window = bg_gtk_window_new(GTK_WINDOW_TOPLEVEL);

  tmp_string = bg_sprintf(TR("%s messages"), app_name);
  gtk_window_set_title(GTK_WINDOW(ret->window), tmp_string);
  free(tmp_string);
  
  g_signal_connect(G_OBJECT(ret->window), "delete_event",
                   G_CALLBACK(delete_callback), (gpointer)ret);
  g_signal_connect(G_OBJECT(ret->window), "configure-event",
                   G_CALLBACK(configure_callback), (gpointer)ret);
  
  /* Create and connect message queue */
  bg_control_init(&ret->ctrl, bg_msg_sink_create(handle_message, ret, 0));

  ret->log_sink = bg_msg_sink_create(handle_log_message, ret, 0);
  bg_log_add_dest(ret->log_sink);
  
  /* Create tag table */
  ret->tag_table   = gtk_text_tag_table_new();
  ret->info_tag    = gtk_text_tag_new(NULL);
  ret->debug_tag   = gtk_text_tag_new(NULL);
  ret->error_tag   = gtk_text_tag_new(NULL);
  ret->warning_tag = gtk_text_tag_new(NULL);

  gtk_text_tag_table_add(ret->tag_table, ret->info_tag);
  gtk_text_tag_table_add(ret->tag_table, ret->debug_tag);
  gtk_text_tag_table_add(ret->tag_table, ret->error_tag);
  gtk_text_tag_table_add(ret->tag_table, ret->warning_tag);

  /* Create textbuffer */

  ret->buffer = gtk_text_buffer_new(ret->tag_table);
  
  /* Create textview */  
  
  ret->textview = gtk_text_view_new_with_buffer(ret->buffer);
  gtk_text_view_set_editable(GTK_TEXT_VIEW(ret->textview), FALSE);
  gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ret->textview), FALSE);
  gtk_widget_set_size_request(ret->textview, 300, 100);
  
  gtk_widget_show(ret->textview);

  /* Create scrolledwindow */
  ret->scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ret->scrolledwindow),
                                 GTK_POLICY_ALWAYS, GTK_POLICY_ALWAYS);
  
  gtk_container_add(GTK_CONTAINER(ret->scrolledwindow), ret->textview);
  gtk_widget_show(ret->scrolledwindow);
  gtk_container_add(GTK_CONTAINER(ret->window), ret->scrolledwindow);
  
  
  /* Add idle callback */
  g_timeout_add(DELAY_TIME, idle_callback, (gpointer)ret);
  
  return ret;
  }

void bg_gtk_log_window_set_ctrl(bg_gtk_log_window_t * ret, bg_controllable_t * ctrl)
  {
  bg_controllable_connect(ctrl, &ret->ctrl);
  }

void bg_gtk_log_window_destroy(bg_gtk_log_window_t * win)
  {
  if(win->last_error)
    free(win->last_error);
  gtk_widget_destroy(win->window);

  bg_log_remove_dest(win->log_sink);
  bg_msg_sink_destroy(win->log_sink);
  
  bg_control_cleanup(&win->ctrl);
  free(win);
  }

void
bg_gtk_log_window_show(bg_gtk_log_window_t * w)
  {
  gavl_value_t val;

  if(!w->width || !w->height)
    {
    gtk_window_set_position(GTK_WINDOW(w->window), GTK_WIN_POS_CENTER_ON_PARENT);
    }
  gtk_widget_show(w->window);

  if(w->width && w->height)
    bg_gtk_decorated_window_move_resize_window(w->window,
                                               w->x, w->y, w->width, w->height);
  else
    gtk_window_resize(GTK_WINDOW(w->window), 600, 300);
  
  gavl_value_init(&val);
  gavl_value_set_int(&val, 1);
  
  bg_state_set(NULL, 1, BG_GTK_LOGWINDOW_STATE_CTX, BG_GTK_LOGWINDOW_VISIBLE,
               &val, w->ctrl.cmd_sink, BG_CMD_SET_STATE);
  
  w->visible = 1;
  }

void bg_gtk_log_window_hide(bg_gtk_log_window_t * win)
  {
  gavl_value_t val;

  gavl_value_init(&val);
  gavl_value_set_int(&val, 0);
  
  bg_state_set(NULL, 1, BG_GTK_LOGWINDOW_STATE_CTX, BG_GTK_LOGWINDOW_VISIBLE,
               &val, win->ctrl.cmd_sink, BG_CMD_SET_STATE);
  
  gtk_widget_hide(win->window);
  win->visible = 0;
  }

/* Configuration stuff */

static const bg_parameter_info_t parameters[] =
  {
    {
      .name =        "max_messages",
      .long_name =   TRS("Number of messages"),
      .type =        BG_PARAMETER_INT,
      .val_default = GAVL_VALUE_INIT_INT(20),
      .help_string = TRS("Maximum number of messages hold in the window")
    },
    {
      .name =        "info_color",
      .long_name =   TRS("Info foreground"),
      .type =        BG_PARAMETER_COLOR_RGB,
      .val_default = GAVL_VALUE_INIT_COLOR_RGB(0.0, 0.0, 0.0),
      .help_string = TRS("Color for info messages"),
    },
    {
      .name =        "warning_color",
      .long_name =   TRS("Warning foreground"),
      .type =        BG_PARAMETER_COLOR_RGB,
      .val_default = GAVL_VALUE_INIT_COLOR_RGB(1.0, 0.5, 0.0),
      .help_string = TRS("Color for warning messages"),
    },
    {
      .name =        "error_color",
      .long_name =   TRS("Error foreground"),
      .type =        BG_PARAMETER_COLOR_RGB,
      .val_default = GAVL_VALUE_INIT_COLOR_RGB(1.0, 0.0, 0.0),
      .help_string = TRS("Color for error messages"),
    },
    {
      .name =        "debug_color",
      .long_name =   TRS("Debug foreground"),
      .type =        BG_PARAMETER_COLOR_RGB,
      .val_default = GAVL_VALUE_INIT_COLOR_RGB(0.0, 0.0, 1.0),
      .help_string = TRS("Color for debug messages"),
    },
    {
      .name = "x",
      .long_name = "X",
      .flags = BG_PARAMETER_HIDE_DIALOG,
      .type = BG_PARAMETER_INT,
      .val_default = GAVL_VALUE_INIT_INT(100)
    },
    {
      .name = "y",
      .long_name = "Y",
      .flags = BG_PARAMETER_HIDE_DIALOG,
      .type = BG_PARAMETER_INT,
      .val_default = GAVL_VALUE_INIT_INT(100)
    },
    {
      .name = "width",
      .long_name = "Width",
      .flags = BG_PARAMETER_HIDE_DIALOG,
      .type = BG_PARAMETER_INT,
      .val_default = GAVL_VALUE_INIT_INT(0)
    },
    {
      .name = "height",
      .long_name = "Height",
      .flags = BG_PARAMETER_HIDE_DIALOG,
      .type = BG_PARAMETER_INT,
      .val_default = GAVL_VALUE_INIT_INT(0)
    },
    { /* */ }
  };

const bg_parameter_info_t *
bg_gtk_log_window_get_parameters(bg_gtk_log_window_t * w)
  {
  return parameters;
  }

void bg_gtk_log_window_set_parameter(void * data, const char * name,
                                     const gavl_value_t * v)
  {
  GdkColor color;
  bg_gtk_log_window_t * win;
  if(!name)
    return;

  win = (bg_gtk_log_window_t *)data;

  if(!strcmp(name, "max_messages"))
    {
    win->max_messages = v->v.i;
    while(win->num_messages > win->max_messages)
      delete_first_line(win);
    }
  
  else if(!strcmp(name, "info_color"))
    {
    color.red   = (guint16)(v->v.color[0] * 65535.0);
    color.green = (guint16)(v->v.color[1] * 65535.0);
    color.blue  = (guint16)(v->v.color[2] * 65535.0);
    g_object_set(win->info_tag, "foreground-gdk", &color, NULL);
    }
  else if(!strcmp(name, "warning_color"))
    {
    color.red   = (guint16)(v->v.color[0] * 65535.0);
    color.green = (guint16)(v->v.color[1] * 65535.0);
    color.blue  = (guint16)(v->v.color[2] * 65535.0);
    g_object_set(win->warning_tag, "foreground-gdk", &color, NULL);
    }
  else if(!strcmp(name, "error_color"))
    {
    color.red   = (guint16)(v->v.color[0] * 65535.0);
    color.green = (guint16)(v->v.color[1] * 65535.0);
    color.blue  = (guint16)(v->v.color[2] * 65535.0);
    g_object_set(win->error_tag, "foreground-gdk", &color, NULL);
    }
  else if(!strcmp(name, "debug_color"))
    {
    color.red   = (guint16)(v->v.color[0] * 65535.0);
    color.green = (guint16)(v->v.color[1] * 65535.0);
    color.blue  = (guint16)(v->v.color[2] * 65535.0);
    g_object_set(win->debug_tag, "foreground-gdk", &color, NULL);
    }
  }

void bg_gtk_log_window_flush(bg_gtk_log_window_t * win)
  {
  idle_callback(win);
  }

const char * bg_gtk_log_window_last_error(bg_gtk_log_window_t * win)
  {
  return win->last_error;
  }

void bg_gtk_log_window_init_state(gavl_dictionary_t * dict)
  {
  bg_state_init_ctx(dict, BG_GTK_LOGWINDOW_STATE_CTX, state_vars);
  }
  
