/* XPENGUINS - cool little penguins that walk along the tops of your windows
 * -copyright-
#-# 
#-# Copyright (C) 1999-2001  Robin Hogan, 2021-2026 Willem Vermin
#-# 
#-# 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#-# 
*/
/*
 * XPENGUINS is a kind of a cross between lemmings and xsnow - penguins
 * fall from the top of the screen and walk across the tops of windows.
 *
 * Since version 2 the code has been modularised - all the work is
 * done by the xpenguins_*() functions in libxpenguins. This file
 * contains the command-line interface. A GNOME applet interface also
 * exists. The main additional feature since version 2 is the
 * "themeability" (hey, everyone else is doing it so why shouldn't
 * I?) - images are loaded at run-time rather than being compiled into
 * the executable. This makes it relatively easy for artistically
 * inclined non-programmers to replace the penguins with anything they
 * choose. It also means that the images need not be licensed under
 * the GPL.
 * 
 * Robin Hogan <R.J.Hogan@reading.ac.uk>
 * Project started 22 November 1999
 * This file started 7 April 2001
 * See XPENGUINS_DATE for last update
 *
 * Since version 3, gtk3 is used to produce a gui, and to create a 
 * transparent click-through window when possible to draw the toons in.
 * Xpenguins should now run fine in environments like Gnome, KDE, XFCE,
 * but also in environments like fvwm and twm.
 * Some code is borrowed from xsnow, as is evident in some comments.
 *
 * Willem Vermin February 2021
 */

#ifndef VERSION
#ifdef HAVE_CONFIG_H
#include <config.h>
#else
#define VERSION "<unknown version>"
#endif
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gtk/gtk.h>

#include "xpenguins.h"
#include "debug.h"
#include "ui.h"
#include "utils.h"
#include "main.h"
#include "docs.h"
#include "selfrep.h"

#define DEFAULT_THEME "Penguins"

#define XPENGUINS_VERSION VERSION
#define XPENGUINS_AUTHOR "Robin Hogan"
#define XPENGUINS_DATE "1 October 2001"

#define ArgumentIs(short_arg, long_arg) (strcmp(argv[n], short_arg) == 0 \
      || strcmp(argv[n], long_arg) == 0 || strcmp(argv[n], "-" long_arg) == 0)
#define LongArgumentIs(long_arg) \
   (strcmp(argv[n], long_arg) == 0 || strcmp(argv[n], "-" long_arg) == 0)

/* Prototypes */
static void ListThemes();
static void DescribeThemes(char **theme_names);
static float LoadAverage();

static XPenguinsTheme ptheme;

static char *config_dir          = NULL;
static int   load_message        = 0;
static int   load_check_interval = 5000000; /* 5s between load average checks */
static int   frames_active;
static int   interupts           = 0;
static int   cycle               = 0;
static int   load_cycles         = 10; /* number of frames between load average checks */
static float load1               = -1.0; /* Start killing penguins if load reaches this amount */
static float load2               = -1.0; /* All gone by this amount (but can come back!) */
static int   domenu              = 1; // to show or not to show the gui
static int   hidemenu            = 0; // to hide or not to iconify the gui at startup
int          WantXdbe            = 1; // want or want not double buffer

static int  do_penguins_all(void *);
static int  do_test(void *);
static int  do_write_flags(void *);
static int  do_handle_error(void *);
static void penguins_move(void);


int    counter          = 0;
int    FlagsChanged     = 0;
int    npenguins        = -1; /* number of penguins (0 = use default) */
int    time_move        = 0;         /* will be filled in later */
float  SpeedFactor      = 1;
char **ThemeNames       = NULL;  /* names of active themes */


static int   random_theme    = 0;
static int   all_themes      = 0;
static int   ignore_popups   = 0;
static int   describe_theme  = 0;
static int   list_themes     = 0;
static int   nthemes         = 0;
static char *error_message   = NULL;
static char *display_name    = NULL;
static int   npenguins_set   = 0;

static char *ErrorMessage    = NULL;

int main (int argc, char **argv)
{
   int n;
   /* Flags */
   int  rectangular_windows = 0;

   srand48(time(NULL));
   ThemeNames = (char **)malloc(2 * sizeof(char *));
   ThemeNames[0] = strdup(_(DEFAULT_THEME));
   ThemeNames[1] = NULL;
   // Circumvent wayland problems:before starting gtk: make sure that the 
   // gdk-x11 backend is used.

   setenv("GDK_BACKEND","x11",1);

   gtk_init(&argc, &argv);

   // first look if there is the --defaults flag:

   int defaults = 0;
   for (n=1; n < argc; ++n)
   {
      if (LongArgumentIs("-defaults"))
      {
	 defaults = 1;
	 break;
      }
   }

   if (!defaults)
      ReadFlags();

   P("ThemeNames:\n"); // xpenguins_print_list(ThemeNames);
   {
      char **t;
      for(t = ThemeNames; *t; t++)
	 nthemes++;
   }


   /* Handle command-line arguments */
   for (n = 1; n < argc; ++n) {
      if (ArgumentIs("-n", "-penguins")) {
	 if (argc > ++n) {
	    npenguins_set = 1;
	    npenguins = atoi(argv[n]);
	    if (npenguins > PENGUIN_MAX) {
	       npenguins = PENGUIN_MAX;
	       if (xpenguins_verbose) {
		  fprintf(stderr, _("Warning: only %d penguins created\n"), npenguins);
	       }
	    }
	    else if (npenguins < 0) {
	       if (xpenguins_verbose) {
		  fprintf(stderr, _("Warning: using default number of penguins\n"));
	       }
	       npenguins = -1;
	    }
	 }
	 else {
	    fprintf(stderr, _("Error: number of penguins not specified\n"));
	    docs_usage(argv);
	    exit(1);
	 }
      }
      else if (ArgumentIs("-m", "-delay")) {
	 if (argc > ++n) {
	    time_move=atoi(argv[n]);
	 }
	 else {
	    fprintf(stderr, _("Error: delay in milliseconds not specified\n"));
	    docs_usage(argv);
	    exit(1);
	 }
      }
      else if (LongArgumentIs("-speed")) {
	 if (argc > ++n) {
	    SpeedFactor=0.01*atoi(argv[n]);
	 }
	 else {
	    fprintf(stderr, _("Error: speed not specified\n"));
	    docs_usage(argv);
	    exit(1);
	 }
      }
      else if (ArgumentIs("-d", "-display")) {
	 if (argc > ++n) {
	    display_name = argv[n];
	 }
	 else {
	    fprintf(stderr, _("Error: display not specified\n"));
	    docs_usage(argv);
	    exit(1);
	 }
      }
      else if (ArgumentIs("-p", "-ignorepopups")) {
	 ignore_popups = 1;
      }
      else if (ArgumentIs("-r", "-rectwin")) {
	 rectangular_windows = 1;
	 // actually: rectangular windows is not used...
	 (void)rectangular_windows; // no warning from compiler
      }
      else if (ArgumentIs("-h", "-help")) {
	 fprintf(stdout, _("XPenguins %s (%s) by %s\n"),
	       XPENGUINS_VERSION, XPENGUINS_DATE, XPENGUINS_AUTHOR);
	 docs_usage(argv);
	 exit(0);
      }
      else if (ArgumentIs("-v", "-version")) {
	 fprintf(stdout, "XPenguins %s\n", XPENGUINS_VERSION);
	 exit(0);
      }
      else if (ArgumentIs("-q", "-quiet")) {
	 xpenguins_verbose = 0;
      }
      else if (ArgumentIs("-c", "-config-dir")) {
	 if (argc > ++n) {
	    config_dir = argv[n];
	 }
	 else {
	    fprintf(stderr, _("Error: config directory not specified\n"));
	    docs_usage(argv);
	    exit(1);
	 }
      }
      else if (ArgumentIs("-t", "-theme")) 
      {
	 // override all themes found in .xpenguinsrc 
	 if(ThemeFound)
	 {
	    xpenguins_free_list(ThemeNames);
	    ThemeNames = (char **)malloc(2 * sizeof(char *));
	    ThemeNames[0] = strdup(_(DEFAULT_THEME));
	    ThemeNames[1] = NULL;
	    if (!npenguins_set)
	       npenguins  = -1;
	    nthemes    = 0;
	    ThemeFound = 0;
	 }
	 if (argc > ++n) {
	    if (nthemes) {
	       ThemeNames = (char **)realloc(ThemeNames, (nthemes+2) * sizeof(char *));
	       ThemeNames[nthemes] = strdup(argv[n]); /* Add a new theme to the list */
	       ThemeNames[++nthemes] = NULL;          /* Make sure the list is NULL terminated */
	    }
	    else {
	       *ThemeNames = strdup(argv[n]); /* Replace the default theme */
	       ++nthemes;
	    }
	 }
	 else {
	    fprintf(stderr, _("Error: theme not specified\n"));
	    docs_usage(argv);
	    exit(1);
	 }
      }
      else if (ArgumentIs("-l", "-list-themes")) {
	 list_themes = 1;
      }
      else if (ArgumentIs("-i", "-theme-info")) {
	 describe_theme = 1;
      }
      else if (ArgumentIs("-b", "-no-blood")) {
	 xpenguins_set_blood(0);
      }
      else if (ArgumentIs("-a", "-no-angels")) {
	 xpenguins_set_angels(0);
      }
      else if (ArgumentIs("-s", "-squish")) {
	 ToonConfigure(TOON_SQUISH);
      }
      else if (LongArgumentIs("-all")) {
	 all_themes = 1;
      }
      else if (LongArgumentIs("-random-theme")) {
	 random_theme = 1;
      }
      else if (LongArgumentIs("-nomenu")) {
	 domenu = 0;
      }
      else if (LongArgumentIs("-hidemenu")) {
	 hidemenu = 1;
      }
      else if (LongArgumentIs("-nodoublebuffer")) {
	 WantXdbe = 0;
      }
      else if (LongArgumentIs("-defaults")) {
      }
      else if (LongArgumentIs("-lift"))
	 if (argc > ++n) {
	    toon_lift = atoi(argv[n]);
	    P("lift: %d\n",toon_lift);
	 }
	 else {
	    fprintf(stderr, _("Error: --lift expects a number\n"));
	    docs_usage(argv);
	    exit(1);
	 }
      else if (LongArgumentIs("-id")) {
	 if (argc > ++n) {
	    Window id;
	    if (sscanf(argv[n], "%lx", &id)) {
	       P("---------------id %#lx\n",id);
	       toon_root_override = id;
	    }
	    else {
	       fprintf(stderr, _("Warning: could not read window ID\n"));
	    }
	 }
	 else {
	    fprintf(stderr, _("Error: window ID not specified\n"));
	    docs_usage(argv);
	    exit(1);
	 }
      }
      else if (LongArgumentIs("-nice")) {
	 if (argc > ++n) {
	    if ((load1 = atof(argv[n])) < 0.0) {
	       fprintf(stderr, _("Error: load average must be greater than or equal to zero\n"));
	       docs_usage(argv);
	       exit(1);
	    }
	    if (argc > ++n) {
	       if ((load2 = atof(argv[n])) < load1) {
		  fprintf(stderr, _("Error: second load average must be greater than or equal to first\n"));
		  docs_usage(argv);
		  exit(1);
	       }
	    }
	    else {
	       fprintf(stderr, _("Error: second load average not specified\n"));
	       docs_usage(argv);
	       exit(1);
	    }
	 }
	 else {
	    fprintf(stderr, _("Error: no load averages not specified\n"));
	    docs_usage(argv);
	    exit(1);
	 }
      }
      else if(LongArgumentIs("-changelog"))
      {
	 docs_changelog();
	 exit(0);
      }
      else if(LongArgumentIs("-selfrep"))
      {
	 selfrep();
	 exit(0);
      }
      else {
	 if (xpenguins_verbose) {
	    fprintf(stderr, _("Warning: \"%s\" not understood - use \"-h\" for "
		     "a list of options\n"), argv[n]);
	 }
      } 
   }

   main_init();

   if (!ErrorMessage && domenu)
      ui();
   if (hidemenu)
      iconify();

   gtk_main();

   ToonCloseDisplay();
   if (xpenguins_verbose) 
   {
      printf(_("Thank you for using xpenguins.\n"));
   }
   exit(0);
}


void set_default_delay()
{
   time_move = ptheme.delay;
}

char **get_themes(int *n)
{
   return xpenguins_list_themes(n);
}

int do_write_flags(void *dummy)
{
   if (FlagsChanged)
   {
      FlagsChanged = 0;
      WriteFlags();
   }
   return TRUE;
   (void)dummy;
}

int do_test(void *dummy)
{

   return TRUE;
   P("%d do_test toon_root: %#lx\n",counter++,toon_root);
   return TRUE;
   P("do_test toon_lift: %d\n",toon_lift);
   WriteFlags();
   return TRUE;
   P("do_test %d %d %d\n",counter++,toon_lift,toon_display_height);
   toon_lift += 10;
   if (toon_lift > 1200)
      toon_lift = 0;

   XWindowAttributes attributes;
   XGetWindowAttributes(toon_display, toon_root1, &attributes);
   toon_display_height = attributes.height - toon_lift;
   CheckPenguinsY();
   return TRUE;
   ToonPrint();
   return TRUE;
   static int p = 0;
   P("do_test %d %d\n",counter++,p);
   if(p)
      xpenguins_set_number(100);
   else
      xpenguins_set_number(10);
   p = !p;

   return TRUE;
   (void)dummy;
}

int do_penguins_all(void *dummy)
{
   P("do_penguins_all %d\n",counter++);
   penguins_move();
   xpenguins_draw();
   return TRUE;
   (void)dummy;
}

void penguins_move()
{

   if((frames_active = xpenguins_frame()) || !interupts) 
   {
      P("frames_active %d\n",frames_active);
      if (interupts && xpenguins_verbose) 
      {
	 fprintf(stderr, ".");
      }
      if (ToonSignal()) 
      {
	 if (++interupts > 1) 
	 {
	    return;
	 }
	 else if (xpenguins_verbose) 
	 {
	    printf(_("\nInterrupt received: Exiting.\n"));
	 }
	 gtk_main_quit();
	 return;
      }
      else if (!interupts && cycle > load_cycles && load1 >= 0.0) 
      {
	 float load = LoadAverage();
	 int newp;

	 if (load2 > load1) 
	 {
	    newp = ((load2-load)*((float) npenguins)) / (load2-load1);
	    if (newp > npenguins) {
	       newp = npenguins;
	    }
	    else if (newp < 0) {
	       newp = 0;
	    }
	 }
	 else if (load < load1) 
	 {
	    newp = npenguins;
	 }
	 else 
	 {
	    newp = 0;
	 }

	 if (penguin_number != newp) 
	 {
	    xpenguins_set_number(newp);
	    if (xpenguins_verbose && !load_message) {
	       fprintf(stderr, _("Adjusting number according to load\n"));
	       load_message = 1;
	    }
	 }
	 cycle = 0;
      }
      else if (!frames_active) 
      {
	 /* No frames active! Hybernate for 5 seconds... */
	 cycle = load_cycles;
      }
      ++cycle;
   }
   else
      gtk_main_quit();
   return;
}


void ListThemes()
{
   char **theme;
   int n;
   int nthemes = 0;

   theme = xpenguins_list_themes(&nthemes);
   if (theme == NULL || nthemes == 0) {
      if (!config_dir) {
	 config_dir = _(XPENGUINS_SYSTEM_DIRECTORY);
      }
      else if (xpenguins_verbose) {
	 fprintf(stderr, _("No valid themes found (looked in %s%s and %s%s)\n"),
	       config_dir, XPENGUINS_THEME_DIRECTORY, getenv("HOME"),
	       XPENGUINS_USER_DIRECTORY XPENGUINS_THEME_DIRECTORY);
      }
   }
   else {
      for (n = 0; n < nthemes; ++n) {
	 fprintf(stdout, "%s\n", theme[n]);
      }
   }
   exit(0);
}

void DescribeThemes(char **theme_names)
{
   char **info;
   char *location;

   while (*theme_names) {
      location = xpenguins_theme_directory(*theme_names);
      if (!location) {
	 fprintf(stderr, _("Theme called \"%s\" not found\n"),
	       xpenguins_remove_underscores(*theme_names));
	 exit(2);
      }

      info = xpenguins_theme_info(*theme_names);
      if (!info) {
	 /* Assume an informative error message has already been delivered */
	 exit(2);
      }

      fprintf(stdout, _("Theme: %s\n"),      xpenguins_remove_underscores(*theme_names));
      fprintf(stdout, _("Date: %s\n"),       info[PENGUIN_DATE]);
      fprintf(stdout, _("Artist(s): %s\n"),  info[PENGUIN_ARTIST]);
      fprintf(stdout, _("Copyright: %s\n"),  info[PENGUIN_COPYRIGHT]);
      fprintf(stdout, _("License: %s\n"),    info[PENGUIN_LICENSE]);
      fprintf(stdout, _("Maintainer: %s\n"), info[PENGUIN_MAINTAINER]);
      fprintf(stdout, _("Location: %s\n"),   location);
      fprintf(stdout, _("Icon: %s\n"),       info[PENGUIN_ICON]);
      fprintf(stdout, _("Comment: %s\n\n"),  info[PENGUIN_COMMENT]);
      ++theme_names;
   }
   exit(0);
}

/* Get the 1-min averaged system load on linux systems - it's the
 * first number in the /proc/loadavg pseudofile. Return -1 if not
 * found. */
float LoadAverage()
{
   FILE *loadfile = fopen("/proc/loadavg", "r");
   float load = -1;

   if (loadfile) {
      int n = fscanf(loadfile, "%f", &load);
      if (n == 0)
	 load = -1;
      fclose(loadfile);
   }

   return load;
}

void apply_lift(int lift)
{
   toon_lift = lift;

   XWindowAttributes attributes;
   XGetWindowAttributes(toon_display, toon_root1, &attributes);
   toon_display_height = attributes.height - toon_lift;
   CheckPenguinsY();
   FlagsChanged = 1;
}

void main_init()
{
   static int called = 0;
   xpenguins_active  = 0;
   static int one    = 0;
   static int two;
   static int three;

   // clear ptheme
   xpenguins_free_theme(&ptheme);

   interupts  = 0;
   cycle      = 0;

   if(1)
   {
      char **t = ThemeNames;
      while (*t)
      {
	 P("theme: %s\n",*t);
	 t++;
      }
   }

   /* Some basic stuff */

   if (config_dir) {
      xpenguins_set_directory(config_dir);
   }

   if(!called)
   {
      if (all_themes) {
	 char **tmp_list = xpenguins_list_themes(NULL);
	 if (tmp_list) {
	    xpenguins_free_list(ThemeNames);
	    ThemeNames = tmp_list;
	 }
      }

      if (list_themes) {
	 ListThemes();
      }
      else if (describe_theme) {
	 DescribeThemes(ThemeNames);
      }
      else if (random_theme) 
      {
	 char **tmp_list = xpenguins_list_themes(NULL);
	 if (tmp_list) {
	    int nr;

	    /* we need to count how many themes there are */
	    /* but if you did xpenguins_list_themes(&nr,0) ... */
	    for(nr = 0; tmp_list[nr] != NULL; nr++) 
	    {
	    }

	    xpenguins_free_list(ThemeNames);

	    ThemeNames = (char **)malloc(2 * sizeof(char *));
	    nr = drand48()*nr;
	    ThemeNames[0] = strdup(tmp_list[nr]);
	    ThemeNames[1] = NULL;
	    P("Random theme: nr: %d theme: %s\n",nr,ThemeNames[0]);

	    /* we probably should free tmp_list and its contents */
	    /* not sure why but we get a segfault if we free the contents */
	    /* but now we can :-) */

	    xpenguins_free_list(tmp_list);

	    if (xpenguins_verbose) {
	       fprintf(stderr, _("Selected random theme - '%s'\n"),
		     ThemeNames[0]);
	    }
	 }
      }

      if (ignore_popups) {
	 xpenguins_ignorepopups(1);
      }

      if (npenguins >= 0) {
	 xpenguins_set_number(npenguins);
      }
   }


   P("ThemeNames:\n"); //xpenguins_print_list(ThemeNames);
   /* Load theme */
   error_message = xpenguins_load_themes(&ThemeNames, &ptheme);
   if (error_message)
   {
      // maybe user deleted themes from .xpenguins/themes, so we try again with the default
      xpenguins_free_list(ThemeNames);
      ThemeNames = (char **)malloc(2 * sizeof(char *));
      ThemeNames[0] = strdup(_(DEFAULT_THEME));
      ThemeNames[1] = NULL;

      error_message = xpenguins_load_themes(&ThemeNames, &ptheme);
      if (error_message) {
	 if (ThemeNames[1]) 
	 {
	    fprintf(stderr, _("Error loading themes: %s\n"), error_message);
	    ErrorMessage = (char*) malloc(512*sizeof(char));
	    ErrorMessage = strdup("Sorry, there is a problem with theme loading.");
	 }
	 else 
	 {
	    fprintf(stderr, _("Error loading theme %s: %s\n"), *ThemeNames, error_message);
	    ErrorMessage = (char*) malloc(512*sizeof(char));
	    snprintf(ErrorMessage, 500, "Sorry, there is a problem with theme '%s'",error_message);
	 }
	 //exit(2);
      }
   }

   if(0)
   {
      printf("*** total: %d ngenera: %d delay: %d _nallocated: %d\n",
	    ptheme.total,ptheme.ngenera,ptheme.delay,ptheme._nallocated);
      int i;
      if(0)
	 for (i=0; i<ptheme.ngenera; i++)
	 {
	    printf("+++ genus %d name: %s number: %d\n",i,ptheme.name[i],ptheme.number[i]);
	    int j;
	    for (j=0; j<PENGUIN_NTYPES; j++)
	    {
	       printf("--- type: %d %s\n",j,NumToType[j]);
	       ToonDataPrint(ptheme.data[i]+j);
	    }
	 }
      i=1; //int j=1;
      P("terminal_velocity of %d %d: %f\n",i,j,penguin_data[i][j].terminal_velocity);
   }

   if (!time_move) {
      time_move = ptheme.delay;
   }

   if (load1 >= 0.0) {
      float load = LoadAverage();
      if (load < 0.0) {
	 fprintf(stderr, _("Warning: cannot detect load averages on this system\n"));
	 load1 = load2 = -1.0;
      }
      else {
	 load_cycles = load_check_interval / (time_move*1000);
      }
   }

   /* Send pixmaps to X server */
   error_message = xpenguins_start(display_name);
   if (error_message) {
      fprintf(stderr, _("Error starting xpenguins: %s\n"),
	    error_message);
      exit(2);
   }

   /* We want npenguins to represent the full complement of penguins;
    * penguin_number may change if the load gets too high */
   if (npenguins <= 0) {
      npenguins = penguin_number;
   }

   ToonConfigure(TOON_CATCHSIGNALS);

   if(one)
   {
      P("remove timeouts\n");
      g_source_remove(one);
      g_source_remove(two);
      g_source_remove(three);
   }
   ClearScreen();
   {

      if (ErrorMessage)
	 g_timeout_add_full        (G_PRIORITY_DEFAULT, 10,   do_handle_error ,NULL,NULL);
      else
      {
	 one =   g_timeout_add_full(G_PRIORITY_DEFAULT, 40,   do_penguins_all ,NULL,NULL);
	 two =   g_timeout_add_full(G_PRIORITY_DEFAULT, 200,  do_test         ,NULL,NULL);
	 three = g_timeout_add_full(G_PRIORITY_DEFAULT, 500,  do_write_flags  ,NULL,NULL);
      }
   }
   called = 1;
}

int do_handle_error(void *dummy)
{
   show_dialog(1, "%s", ErrorMessage);
   return False;
   (void)dummy;
}
