/* fileSeq.c
 * A file handling plugin for Gimp 2.x
 * Lets you work on a sequence of images, one at a time
 * Copyright (C) 2004 David Hodson
 *
 * 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.
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#define PLUGIN_NAME "plug_in_fileSeq"
#define PLUGIN_TITLE "File Sequencer"

static void query (void);
static void run(const char*, int, const GimpParam*, int*, GimpParam**);
GimpPlugInInfo PLUG_IN_INFO = { NULL, NULL, query, run };

MAIN()

static void fileSeqDialog();

static void
query (void) {
  static GimpParamDef args[] = {
    { GIMP_PDB_INT32,     "run_mode",     "Interactive" }, };
  static GimpParamDef *return_vals = NULL;
  static guint nargs = 1;
  static guint nreturn_vals = 0;
  gimp_install_procedure (
    PLUGIN_NAME,
    "File Sequencer",
    "Allows the user to step through a sequence of images",
    "David Hodson",
    "David Hodson",
    "2004",
    "<Toolbox>/Xtns/File Sequencer...",
    NULL, GIMP_EXTENSION,
    nargs, nreturn_vals, args, return_vals );
}

static void
run(
  const char *name,
  int n_params, 
  const GimpParam * param, 
  int *nreturn_vals, 
  GimpParam** return_vals) {

  static GimpParam values[1];
  GimpRunMode run_mode;

  nreturn_vals[0] = 1;
  return_vals[0] = values;
  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = GIMP_PDB_SUCCESS;

  run_mode = param[0].data.d_int32;
  switch (run_mode) {

  case GIMP_RUN_INTERACTIVE:
  case GIMP_RUN_WITH_LAST_VALS:
    fileSeqDialog();
    break;

  case GIMP_RUN_NONINTERACTIVE:
  default:
    values[0].data.d_status = GIMP_PDB_CALLING_ERROR;
    break;
  }
}

typedef struct {
  int imageNum;
  int inc;
  GtkWidget* imageName;
  GtkWidget* nextBtn;
  GtkWidget* backBtn;
} SeqData;
static SeqData seq;

static void
displayName() {
  gchar* name = gimp_image_get_filename(seq.imageNum);
  gchar* tail = strrchr(name, '/');
  if (tail) {
    ++tail;
  }
  if ((tail == 0) || (*tail == 0)) {
    tail = name;
  }
  gtk_label_set_text(GTK_LABEL(seq.imageName), tail);
}

static gchar*
bumpFileName(const gchar* oldName, int inc) {
  // generate new file name...
  // ansii assumptions here!
  // find the last string of digits in the oldName
  const char* digits = "0123456789";
  const char* numStart = 0;
  size_t numLength = 0;
  // searchPoint always points to digits or end of string
  const char* searchPoint = oldName + strcspn(oldName, digits);
  while (*searchPoint) {
    numStart = searchPoint;
    numLength = strspn(searchPoint, digits);
    searchPoint += numLength;
    searchPoint += strcspn(searchPoint, digits);
  }
  if (numStart == 0) {
    return 0;
  }
  char* newName = malloc(strlen(oldName) + 1);
  if (newName == 0) {
    return 0;
  }
  int newNum = atoi(numStart) + inc;
  if (newNum < 0) {
    newNum = 0;
  }
  sprintf(newName, "%.*s%0*d%s",
    numStart - oldName, oldName,
    numLength, newNum,
    numStart + numLength);
  return newName;
}

static void
setButtons() {
  gchar* oldName = gimp_image_get_filename(seq.imageNum);
  if (oldName == 0) {
    gtk_widget_set_sensitive(seq.nextBtn, FALSE);
    gtk_widget_set_sensitive(seq.backBtn, FALSE);
    return;
  }
  FILE* foo = 0;

  gchar* nextName = bumpFileName(oldName, seq.inc);
  if (nextName == 0) {
    gtk_widget_set_sensitive(seq.nextBtn, FALSE);
  }
  foo = fopen(nextName, "r");
  if (foo) {
    fclose(foo);
    foo = 0;
    gtk_widget_set_sensitive(seq.nextBtn, TRUE);
  } else {
    gtk_widget_set_sensitive(seq.nextBtn, FALSE);
  }
  if (nextName) {
    free(nextName);
  }

  gchar* backName = bumpFileName(oldName, -seq.inc);
  if (backName == 0) {
    gtk_widget_set_sensitive(seq.backBtn, FALSE);
  }
  foo = fopen(backName, "r");
  if (foo) {
    fclose(foo);
    foo = 0;
    gtk_widget_set_sensitive(seq.backBtn, TRUE);
  } else {
    gtk_widget_set_sensitive(seq.backBtn, FALSE);
  }
  if (backName) {
    free(backName);
  }
}

static void
selectImage() {
  // get the next image (or first one)
  int index = -1;
  int i;
  gint numImages = 0;
  gint* image = gimp_image_list(&numImages);
  if (numImages == 0) {
    seq.imageNum = -1;
    gtk_label_set_text(GTK_LABEL(seq.imageName), "<no xxximage>");
    setButtons();
    free(image);
    return;
  }
  for (i = 0; i < numImages; ++i) {
    if (image[i] == seq.imageNum) {
      index = i;
    }
  }
  seq.imageNum = image[(index + 1) % numImages];
  displayName();
  setButtons();
  free(image);
}

static void
changeImageFile(int inc) {
  gboolean ok = TRUE;

  gchar* oldName = gimp_image_get_filename(seq.imageNum);
  if (oldName == 0) {
    return;
  }

  gint drawNum = gimp_image_get_active_drawable(seq.imageNum);
  if (gimp_image_is_dirty(seq.imageNum)) {
    ok = gimp_file_save(
      GIMP_RUN_NONINTERACTIVE, seq.imageNum, drawNum, oldName, oldName);
    if (!ok) {
      gimp_message("Couldn't save old file, giving up.");
      free(oldName);
      return;
    }
  }

  gchar* newName = bumpFileName(oldName, inc);
  if (newName == 0) {
    gimp_message("Couldn't create next file name.");
    free(oldName);
    return;
  }

  free(oldName);
  gint newImage = gimp_file_load(GIMP_RUN_NONINTERACTIVE, newName, newName);
  ok = gimp_displays_reconnect(seq.imageNum, newImage);
  if (ok) {
    // newImage now has two references - won't go away!??
    gimp_image_clean_all(newImage);
    gimp_image_delete(seq.imageNum);
    gimp_image_delete(newImage);
    seq.imageNum = newImage;
    displayName();
    //gtk_label_set_text(GTK_LABEL(seq.imageName), newName);
  } else {
    gimp_image_delete(newImage);
  }
  setButtons();
  free(newName);
}

// button functions
static void nextCB(GtkButton* b, gpointer p) { changeImageFile(seq.inc); }
static void backCB(GtkButton* b, gpointer p) { changeImageFile(-seq.inc); }
static void selectCB(GtkButton* b, gpointer p) { selectImage(); }

static void
fileSeqDialog() {

  GtkWidget* dialog;
  GtkWidget* vbox;
  GtkWidget* hbox;

  int usesPreview = FALSE;
  GtkDialogFlags flags = (GtkDialogFlags)0;

  gimp_ui_init(PLUGIN_NAME, usesPreview);

  dialog = gimp_dialog_new(
    PLUGIN_TITLE,
    PLUGIN_NAME,
    0, flags, 0, 0,
    GTK_STOCK_OK, 0,
    0);

  vbox = GTK_DIALOG(dialog)->vbox;

  hbox = gtk_hbox_new(FALSE, 0);
  seq.imageName = gtk_label_new("");

  //GtkWidget* select = gtk_button_new_from_stock(GTK_STOCK_GO_FORWARD);
  GtkWidget* select = gtk_button_new_with_label("->");
  g_signal_connect(select, "clicked", G_CALLBACK(selectCB), NULL);
  gtk_box_pack_start(GTK_BOX(hbox), seq.imageName, TRUE, TRUE, 0);
  gtk_box_pack_end(GTK_BOX(hbox), select, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

  seq.nextBtn = gtk_button_new_with_label("Next");
  g_signal_connect(seq.nextBtn, "clicked", G_CALLBACK(nextCB), NULL);
  gtk_box_pack_start(GTK_BOX(vbox), seq.nextBtn, TRUE, TRUE, 0);

  seq.backBtn = gtk_button_new_with_label("Back");
  g_signal_connect(seq.backBtn, "clicked", G_CALLBACK(backCB), NULL);
  gtk_box_pack_start(GTK_BOX(vbox), seq.backBtn, TRUE, TRUE, 0);

  gtk_widget_show_all(dialog);

  seq.imageNum = -1;
  seq.inc = 1;
  selectImage();

  gimp_dialog_run(GIMP_DIALOG(dialog));
}
