/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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 3 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 <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "libpika/pika.h"

#include "script-fu-console-history.h"

static gint            console_history_tail_position (CommandHistory *self);
static GStrv           console_history_to_strv       (CommandHistory *self);


/* CommandHistory
 *
 * Not a true class, just a struct with methods.
 * Does not inherit GObject.
 *
 * Is a model that affects a view of the "console history."
 * The model is a sequence of ExecutedCommands.
 * The sequence is a time-ordered queue.
 * You can only append to the end, called the tail.
 * An ExecutedCommand does not contain the result of interpretation,
 * only the string that was interpreted.
 *
 * The sequence is finite.
 * When you append to the tail,
 * commands might be discarded from the head.
 *
 * Has a cursor.
 * You can only get the command at the cursor.
 * The user scrolling through the history moves the cursor.
 * This scrolling is arrow keys in the editor pane,
 * (not scroll bars in the history view pane.)
 * See the main console logic:
 * when user hits arrow keys in the editor,
 * move cursor in the history, get the command at the cursor,
 * and display it in the editor, ready to execute.
 *
 * A CommandHistory is a model,
 * but there is also a distinct TotalHistory model for a scrolling view of the history
 * (e.g. a GtkTextBuffer model for a GtkTextView.)
 *
 * CommandHistory <-supersets- TotalHistory <-views- ConsoleView
 *
 * TotalHistory contains more than the commands in CommandHistory.
 * TotalHistory contains e.g. splash, prompts, and interpretation results.
 *
 * !!! Self does not currently write TotalHistory;
 * The main console logic writes TotalHistory,
 *
 * CommandHistory is persistent across sessions of the ScriptFu Console,
 * and across sessions of Pika.
 * When the SFConsole starts, the TotalHistory,
 * is just the CommandHistory, without results of eval.
 * Old results are not meaningful since the environment changed.
 * Specifically, a new session of SFConsole has a new initialized interpreter.
 * Similarly, when the user closes the console,
 * only the CommandHistory is saved as settings.
 */

void
console_history_init (CommandHistory *self)
{
  self->model     = g_list_append (self->model, NULL);
  self->model_len = 1;
  self->model_max = 100;
}



/* Store the command in tail of CommandHistory.
 * The tail is the most recent added element, which was created prior by new_tail.
 *
 * @commmand transfer full
 *
 * !!! The caller is executing the command.
 * The caller updates TotalHistory, with a prompt string and the command string.
 * Self does not update TotalHistory, the model of the view.
 */
void
console_history_set_tail (CommandHistory *self,
                          const gchar    *command)
{
  GList *list;

  list = g_list_nth (self->model,
                     console_history_tail_position (self));

  if (list->data)
    g_free (list->data);

  /* Discarding const. */
  list->data = (gpointer) command;
}

/* Remove the head of the history and free its string.
 *
 * GList doesn't have such a direct method.
 * Search web to find this solution.
 * !!! g_list_remove does not free the data of the removed element.
 *
 * Remove the element whose data (a string)
 * matches the data of the first element.
 * Then free the data of the first element.
 */
static void
console_history_remove_head (CommandHistory *self)
{
  gpointer * data;

  g_return_if_fail (self->model != NULL);

  data = self->model->data;
  self->model = g_list_remove (self->model, data);
  g_free (data);
}

/* Append NULL string at tail of CommandHistory.
 * Prune head when max exceeded, freeing the string.
 * Position the cursor at last element.
 */
void
console_history_new_tail (CommandHistory *self)
{
  self->model = g_list_append (self->model, NULL);

  if (self->model_len == self->model_max)
    {
      console_history_remove_head (self);
    }
  else
    {
      self->model_len++;
    }

  self->model_cursor = console_history_tail_position (self);
}

void
console_history_cursor_to_tail (CommandHistory *self)
{
  self->model_cursor = console_history_tail_position (self);
}

gboolean
console_history_is_cursor_at_tail (CommandHistory *self)
{
  return self->model_cursor == console_history_tail_position (self);
}

void
console_history_move_cursor (CommandHistory *self,
                             gint            direction)
{
  self->model_cursor += direction;

  /* Clamp cursor in range [0, model_len-1] */
  if (self->model_cursor < 0)
    self->model_cursor = 0;

  if (self->model_cursor >= self->model_len)
    self->model_cursor = self->model_len - 1;
}

const gchar *
console_history_get_at_cursor (CommandHistory *self)
{
  return g_list_nth (self->model, self->model_cursor)->data;
}


/* Methods for persisting history as a setting. */

/* Return a GStrv of the history from settings.
 * The Console knows how to put GStrv to both models!
 *
 * !!! Handle attack on settings file.
 * The returned cardinality of the set of strings
 * may be zero or very many.
 * Elsewhere ensure we don't overflow models.
 */
GStrv
console_history_from_settings (CommandHistory       *self,
                               PikaProcedureConfig  *config)
{
  GStrv           in_history;

  /* Get aux arg from property of config. */
  g_object_get (config,
                "history", &in_history,
                NULL);

  return in_history;
}

void
console_history_to_settings (CommandHistory       *self,
                             PikaProcedureConfig  *config)
{
  GStrv           out_history;

  out_history = console_history_to_strv (self);

  /* set an aux arg in config. */
  g_object_set (config,
                "history", out_history,
                NULL);
}

/* Return history model as GStrv.
 * Converts from interal list into a string array.
 *
 * !!! The exported history may have a tail
 * which is user's edits to the command line,
 * that the user never evaluated.
 * Exported history does not have an empty tail.
 *
 * Caller must g_strfreev the returned GStrv.
 */
static GStrv
console_history_to_strv (CommandHistory *self)
{
  GStrv         history_strv;
  GStrvBuilder *builder;

  builder = g_strv_builder_new ();
  /* Order is earliest first. */
  for (GList *l = self->model; l != NULL; l = l->next)
    {
      /* Don't write an empty pre-allocated tail. */
      if (l->data != NULL)
        g_strv_builder_add (builder, l->data);
    }
  history_strv = g_strv_builder_end (builder);
  g_strv_builder_unref (builder);
  return history_strv;
}

static gint
console_history_tail_position (CommandHistory *self)
{
  return g_list_length (self->model) - 1;
}