/*  $Id: gtail.c,v 1.18 1999/06/05 20:31:01 colin Exp $ */

/*  ----------------------------------------------------------------------

    gtail -- A "tail -f" for GTK+.
    Copyright (c) 1999 Colin Marquardt <colin.marquardt@gmx.de>

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

    ---------------------------------------------------------------------- */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <gtk/gtk.h>
#include <sys/stat.h>
#include "about.h"
#include "main_window.h"
#include "filesel.h"
#include "binfile.h"
#include "gtail.h"

/* ================================================================================ */
/* from /opt/gnome/gnome-admin-1.0.1/logview/logrtns.c                   */
/* ----------------------------------------------------------------------
   NAME:	was_modified
   DESCRIPTION:	Returns true if modified flag in log changed. It also
                changes the modified flag.
   ---------------------------------------------------------------------- */
gboolean
was_modified (Log *log) {
  struct stat filestatus;

  stat(log->name, &filestatus);
  if (filestatus.st_mtime != log->mtime) {
    log->mtime = filestatus.st_mtime;
    /*  g_print("Modified!\n"); */
    return TRUE;
  }
  return FALSE;
}

/* ================================================================================ */

gchar*
get_buffer (Log *log) {
  
  guint FileDescr;
  guint Idx=0;
  guint chars_read;
  glong FilePos = 1000;
  static gchar buffer[10001];
  gchar TempBuf[1];
  guint BytesToRead = 4096;

  memset(buffer, '\0', sizeof(buffer));

  /* using fdopen might be a better solution   *
   * see mod_tail.c from tick-a-stat           *
   * http://gqview.netpedia.net/rest-tick.html */
  FileDescr=open(log->name, O_RDONLY);
  
  if (bin_file(FileDescr)) {
    g_error("Binary file!");
  }
  
  if (FileDescr != 0) {
    
    /* position pointer to EOF minus FilePos */
    FilePos=lseek(FileDescr, 0, SEEK_END);
    
    if (FilePos > 0) {
      /* if the file is really short... */
      if (FilePos < BytesToRead) {
	/* ... just read from the beginning of the file */
	FilePos=0;
      }
      else { /* file is not short... */
	/* ...so calculate the new position
	   (FilePos is now BytesToRead bytes from EOF away) */
	FilePos-=BytesToRead;
      }
      /* bring us to the just calculated position*/
      FilePos=lseek(FileDescr, FilePos, SEEK_SET);
      
      /* ------------------------------------------------------------ */
      
      /* read away chars before the first newline         *
       * so that we always start display with a new line: */
      do {
	read(FileDescr, TempBuf, 1);
	Idx++;
      }
      while ((*TempBuf != '\n') && (*TempBuf != EOF));
      
      /* calculate the new position dependent on the chars read before a newline; *
       * but if we found an EOF without a newline just show what is there:        */
      if (!*TempBuf == EOF) {
	BytesToRead = BytesToRead-Idx;
	FilePos+=Idx;
      }
      
      /* bring us to the just calculated position*/
      FilePos=lseek(FileDescr, FilePos, SEEK_SET);
      
      /* ------------------------------------------------------------ */
      
      /* read BytesToRead from the file descriptor FileDescr into buffer */
      chars_read = read(FileDescr, buffer, BytesToRead);
      //      g_print("BytesToRead = %i, chars_read = %i\n", BytesToRead, chars_read);
      
      buffer[chars_read+1] = (guint) "\0";
    }
    else {
      g_warning ("File has zero length!");
    }
    /* now close the file */
    close(FileDescr);
    
    /* check that something was read */
    if (!chars_read > 0) {
      g_warning("Something went wrong: nothing read!\n");
    }
  }
  else {
    g_warning ("File descriptor was 0!");
  }
  return buffer;
}

/* ================================================================================ */
gboolean
update_log (Log *log) {

  GtkAdjustment *adj;
  GdkFont *fixed_font;

  guint NoOfDelChars;
  guint TextLen;

  gchar* buffer;
  gboolean snap;
  gfloat ViewPoint;

  /* Load a fixed font */
  fixed_font = gdk_font_load ("-misc-fixed-medium-r-*-*-*-100-*-*-*-*-*-*");
   
  if (was_modified(log)) {
    buffer = get_buffer(log);

  /* gtk_timeout_add() is stopped if we return FALSE */
  //  if (!buffer) return FALSE;
  
    /* ------------------------------------------------------------------------  */
    /* thanks to Noonian Soong <noonian@dreamhaven.net> for the adjustment stuff */

#ifdef DEBUG
    g_print ("log->page_no = %i\n", log->page_no);
    g_print ("log->slot_no = %i\n", log->slot_no);
    g_print ("log->name    = %s\n", log->name);
#endif

    /* the text widget creates its own adjustment */
    adj = GTK_TEXT (TextWidget[log->slot_no])->vadj;

    /* test if the scrollbar is at the bottom of the widget: */
    /* upper: the maximum value the text widget can take *
     * page_size: how much we are currently seeing       *
     * value: the topmost point we are currently seeing  */
    if ((adj->upper - adj->page_size) == adj->value)
      snap = TRUE;

    ViewPoint = adj->value;
    
    /* Freeze the text widget, ready for multiple updates without redisplaying */
    gtk_text_freeze (GTK_TEXT (TextWidget[log->slot_no]));

    NoOfDelChars = gtk_text_backward_delete (GTK_TEXT(TextWidget[log->slot_no]),
					     strlen(buffer));

#ifdef DEBUG
    g_print ("NoOfDelChars = %i\n", NoOfDelChars);
#endif
    
    gtk_text_insert (GTK_TEXT (TextWidget[log->slot_no]),
		     fixed_font, NULL, NULL, buffer, -1);

    /* Thaw the text widget, allowing the updates to become visible */
    gtk_text_thaw (GTK_TEXT (TextWidget[log->slot_no]));

    /* if the scrollbar was at the bottom of the widget: */
    if (snap)
      /* set value to page_size away from the end of the widget: */
      gtk_adjustment_set_value (GTK_ADJUSTMENT (adj),
			        adj->upper - adj->lower - adj->page_size);
    else {
      /* otherwise set the scrollbar to where it was: */
      gtk_adjustment_set_value (GTK_ADJUSTMENT (adj),
			        ViewPoint);
    }

    TextLen = gtk_text_get_length(GTK_TEXT (TextWidget[log->slot_no]));
#ifdef DEBUG
    g_print("TextLen = %i\n", TextLen);
#endif

  }    

  /* As this is a timeout function, return TRUE so that it continues to get called */
  return TRUE;
}

/* ================================================================================ */
/* This is an internally used function to set notebook tab widgets. */
/* generated by Glade 0.3.9 */

void
set_notebook_tab (GtkWidget *notebook, guint  page_num, GtkWidget *widget)
{
  GtkNotebookPage *page;
  GtkWidget *notebook_page;

  page = (GtkNotebookPage*) g_list_nth (GTK_NOTEBOOK (notebook)->children, page_num)->data;
  notebook_page = page->child;

  gtk_widget_ref (notebook_page);
  gtk_notebook_remove_page (GTK_NOTEBOOK (notebook), page_num);
  gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), notebook_page,
                            widget, page_num);

  gtk_widget_unref (notebook_page);
  /* added by Colin: */
  gtk_notebook_set_page (GTK_NOTEBOOK (notebook), page_num);
}

/* ================================================================================ */
void
on_add_activate (GtkMenuItem *menuitem, gpointer user_data) {

  if (OpenFiles < FILES_MAX) {
    GString *File = g_string_new(GetFilename("Select File"));

    if (File->str) {
#ifdef DEBUG
      g_print("File is %s\n", File->str);
#endif
      add_file(File);
      OpenFiles++;
      if (OpenFiles == FILES_MAX) gtk_widget_set_sensitive(menuItem_Add, FALSE);
    }
  }
}

/* ================================================================================ */
void
on_remove_activate (GtkMenuItem *menuitem, gpointer user_data) {

  remove_file();
  if (OpenFiles == 0) gtk_widget_set_sensitive(menuItem_Remove, FALSE);
}

/* ================================================================================ */
void
close_application (GtkWidget *widget, gpointer data) {
  guint Idx;

  for (Idx=0; Idx < OpenFiles; Idx++) {
    gtk_timeout_remove (tag[Idx]);
  }
  gtk_main_quit();
}

/* ================================================================================ */
void
on_about_activate (GtkMenuItem *menuitem, gpointer user_data) {
  GtkWidget *aboutdialog;
  
  aboutdialog = CreateAboutDialog ();
  gtk_widget_show (aboutdialog);
}

/* ================================================================================ */
GtkWidget *
draw_notebook (guint FileNo, guint PageNo, GString *FileName, GtkWidget *Notebook) {

  GtkWidget *Table[FILES_MAX];
  GtkWidget *VScrollbar[FILES_MAX];
  GtkWidget *NotebookLabel[FILES_MAX];

  Table[FileNo] = gtk_table_new (2, 2, FALSE);
  gtk_table_set_row_spacing (GTK_TABLE (Table[FileNo]), 0, 2);
  gtk_table_set_col_spacing (GTK_TABLE (Table[FileNo]), 0, 2);
  gtk_container_add (GTK_CONTAINER (Notebook), Table[FileNo]);
  gtk_widget_show (Table[FileNo]);
  
  TextWidget[FileNo] = gtk_text_new (NULL, NULL);
  gtk_widget_show (TextWidget[FileNo]);
  gtk_table_attach (GTK_TABLE (Table[FileNo]), TextWidget[FileNo], 0, 1, 0, 1,
		    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		    GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
  
  VScrollbar[FileNo] = gtk_vscrollbar_new (GTK_TEXT (TextWidget[FileNo])->vadj);
  gtk_table_attach (GTK_TABLE (Table[FileNo]), VScrollbar[FileNo], 1, 2, 0, 1,
		    GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
  gtk_widget_show (VScrollbar[FileNo]);
  /* gtk_range_set_update_policy (GTK_RANGE (VScrollbar[FileNo]), GTK_UPDATE_DELAYED); */
  
  NotebookLabel[PageNo] = gtk_label_new (FileName->str);
  gtk_widget_show (NotebookLabel[PageNo]);
  set_notebook_tab (Notebook, PageNo, NotebookLabel[PageNo]);

  return Notebook;
}

/* ================================================================================ */
gint
find_slot_no (guint PageNo) {

  guint Idx;
  gint SlotNo = -1;
  
  /* loop over all file slots than can exist: */
  for (Idx=0; Idx < FILES_MAX; Idx++) {
    /* donīt check freed slots: */ 
    if (!FileSlotIsFree[Idx]) {
      if (FileLog[Idx]->page_no == PageNo) {
	SlotNo=FileLog[Idx]->slot_no; break;
      }
    }
  }
  if (SlotNo == -1) g_error ("Found no slot number for PageNo %i\n", PageNo);
  return SlotNo;
}

/* ================================================================================ */
void 
add_file (GString *FileName) {

  guint Idx;
  gint FreeSlot;
  guint CurPageNo;
 
  FreeSlot = -1;
  for (Idx=0; Idx < FILES_MAX; Idx++) if (FileSlotIsFree[Idx] == TRUE) { FreeSlot=Idx; break; }
  if (FreeSlot >= 0) {
#ifdef DEBUG
    g_print("Found free slot = %i\n", FreeSlot);
#endif
  }
  else {
    g_error("No free slot found.");
  }
  
  /* allocate memory for the log structs */
  FileLog[FreeSlot] = g_malloc (sizeof (Log));
  if (FileLog[FreeSlot] == NULL) {
    g_error("Not enough memory!\n");
  }
  
  FileLog[FreeSlot]->slot_no = FreeSlot;

  /* always add the page at the end: */
  FileLog[FreeSlot]->page_no = OpenFiles;

  /* draw the notebook: */
  draw_notebook (FreeSlot, OpenFiles, FileName, Notebook);

  gtk_notebook_set_page (GTK_NOTEBOOK (Notebook), OpenFiles);
  CurPageNo = gtk_notebook_current_page(GTK_NOTEBOOK (Notebook));
  
  /* fill the log structure with file name: */
  /* FIXME: better use g_string_assign (); */
  strcpy (FileLog[FreeSlot]->name, FileName->str);

#ifdef DEBUG
  g_print ("*** FileLog[FreeSlot]->page_no = %i\n", FileLog[FreeSlot]->page_no);
  g_print ("*** FileLog[FreeSlot]->slot_no = %i\n", FileLog[FreeSlot]->slot_no);
  g_print ("*** FileLog[FreeSlot]->name    = %s\n", FileLog[FreeSlot]->name);
#endif

  /* setup timer to check log */
  tag[FreeSlot] = gtk_timeout_add (1000, (GtkFunction) update_log, FileLog[FreeSlot]);

  /* set the Remove menu item sensitive again: */
  gtk_widget_set_sensitive (menuItem_Remove, TRUE);

#ifdef DEBUG
  g_print("Using slot %i\n", FreeSlot);
#endif
  /* this slot is not free anymore: */
  FileSlotIsFree[FreeSlot] = FALSE;

#ifdef DEBUG
  g_print ("----- Add summary: --------\n");
  for (Idx=0; Idx < FILES_MAX; Idx++) {
    if (!FileSlotIsFree[Idx]) {
      g_print ("* FileLog[%i]->page_no = %i\n", Idx, FileLog[Idx]->page_no);
      g_print ("* FileLog[%i]->slot_no = %i\n", Idx, FileLog[Idx]->slot_no);
      g_print ("* FileLog[%i]->name    = %s\n", Idx, FileLog[Idx]->name);
    }
  }
  g_print ("---------------------------\n");
#endif
}

/* ================================================================================ */
void 
remove_file (void) {

  guint CurFileSlot;
  guint FileSlot;
  guint Idx;
  guint CurPageNo;

  /* we get the page that is to be removed: */
  CurPageNo = gtk_notebook_current_page(GTK_NOTEBOOK (Notebook));

  CurFileSlot = find_slot_no (CurPageNo);
#ifdef DEBUG
  g_print("** Will free: %i --> (%i)\n", CurPageNo, CurFileSlot);
  g_print("** OpenFiles = %i\n", OpenFiles);
  g_print("** CurPageNo = %i\n", CurPageNo);
#endif
  for (Idx=CurPageNo+1; Idx < OpenFiles; Idx++) {
#ifdef DEBUG
    g_print("** Idx = %i\n", Idx);
#endif
    FileSlot = find_slot_no (Idx);
#ifdef DEBUG
    g_print ("** Old FileLog[%i]->page_no = %i\n", FileSlot, FileLog[FileSlot]->page_no);
#endif
    FileLog[FileSlot]->page_no--;
#ifdef DEBUG
    g_print ("** New FileLog[%i]->page_no = %i\n", FileSlot, FileLog[FileSlot]->page_no);
#endif
  }
  gtk_notebook_remove_page (GTK_NOTEBOOK (Notebook), CurPageNo);
  FileSlotIsFree[CurFileSlot] = TRUE;
  g_free(FileLog[CurFileSlot]);
  gtk_timeout_remove (tag[CurFileSlot]);
  OpenFiles--;

  /* make Add menu item sensitive again: */
  gtk_widget_set_sensitive(menuItem_Add, TRUE);

#ifdef DEBUG
  g_print ("----- Remove summary: -----\n");
  for (Idx=0; Idx < FILES_MAX; Idx++) {
    if (!FileSlotIsFree[Idx]) {
      g_print ("* FileLog[%i]->page_no = %i\n", Idx, FileLog[Idx]->page_no);
      g_print
	("* FileLog[%i]->slot_no = %i\n", Idx, FileLog[Idx]->slot_no);
      g_print ("* FileLog[%i]->name    = %s\n", Idx, FileLog[Idx]->name);
    }
  }
  g_print ("---------------------------\n");
#endif
}


/* ================================================================================ */
/* ================================================================================ */
int
main (int argc, char *argv[]) {

  guint Idx;
  GtkWidget *MainWindow;
  GString *File;
  
  gtk_init (&argc, &argv);

  MainWindow = create_main_window();
  /* make MainWindow visible */
  gtk_widget_show (MainWindow);
  gtk_widget_set_sensitive(menuItem_Remove, FALSE);

  /* initialize all FileSlots: */
  for (Idx=0; Idx < FILES_MAX; Idx++) FileSlotIsFree[Idx] = TRUE;

  while (--argc > 0)
    {
      File = g_string_new(argv[argc]);
      add_file(File);
      OpenFiles++;
    }

  gtk_main ();
  
  return 0;
}
