/*
   Editor macros engine

   Copyright (C) 2001-2025
   Free Software Foundation, Inc.

   This file is part of the Midnight Commander.

   The Midnight Commander 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.

   The Midnight Commander 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 <config.h>

#include <stdlib.h>

#include "lib/global.h"
#include "lib/mcconfig.h"
#include "lib/tty/key.h"        /* tty_keyname_to_keycode*() */
#include "lib/keybind.h"        /* keybind_lookup_actionname() */
#include "lib/fileloc.h"

#include "src/setup.h"          /* macro_action_t */
#include "src/history.h"        /* MC_HISTORY_EDIT_REPEAT */

#include "editwidget.h"

#include "editmacros.h"

/*** global variables ****************************************************************************/

/*** file scope macro definitions ****************************************************************/

/*** file scope type declarations ****************************************************************/

/*** forward declarations (file scope functions) *************************************************/

/*** file scope variables ************************************************************************/

/* --------------------------------------------------------------------------------------------- */
/*** file scope functions ************************************************************************/
/* --------------------------------------------------------------------------------------------- */

static int
edit_macro_comparator (gconstpointer *macro1, gconstpointer *macro2)
{
    const macros_t *m1 = (const macros_t *) macro1;
    const macros_t *m2 = (const macros_t *) macro2;

    return m1->hotkey - m2->hotkey;
}

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

static void
edit_macro_sort_by_hotkey (void)
{
    if (macros_list != NULL && macros_list->len != 0)
        g_array_sort (macros_list, (GCompareFunc) edit_macro_comparator);
}

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

static int
edit_get_macro (WEdit *edit, int hotkey)
{
    macros_t *array_start;
    macros_t *result;
#if defined(__WATCOMC__) //WIN32/c11
    macros_t search_macro = { 0 };
    search_macro.hotkey = hotkey;
#else
    macros_t search_macro = {
        .hotkey = hotkey
    };
#endif

    (void) edit;

    result = bsearch (&search_macro, macros_list->data, macros_list->len,
                      sizeof (macros_t), (GCompareFunc) edit_macro_comparator);

    if (result == NULL || result->macro == NULL)
        return (-1);

    array_start = &g_array_index (macros_list, struct macros_t, 0);

    return (int) (result - array_start);
}

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

/** returns FALSE on error */
static gboolean
edit_delete_macro (WEdit *edit, int hotkey)
{
    mc_config_t *macros_config = NULL;
    const char *section_name = "editor";
    gchar *macros_fname;
    int indx;
    char *skeyname;

    /* clear array of actions for current hotkey */
    while ((indx = edit_get_macro (edit, hotkey)) != -1)
    {
        macros_t *macros;

        macros = &g_array_index (macros_list, struct macros_t, indx);
        g_array_free (macros->macro, TRUE);
        g_array_remove_index (macros_list, indx);
    }

    macros_fname = mc_config_get_full_path (MC_MACRO_FILE);
    macros_config = mc_config_init (macros_fname, FALSE);
    g_free (macros_fname);

    if (macros_config == NULL)
        return FALSE;

    skeyname = tty_keycode_to_keyname (hotkey);
    while (mc_config_del_key (macros_config, section_name, skeyname))
        ;
    g_free (skeyname);
    mc_config_save_file (macros_config, NULL);
    mc_config_deinit (macros_config);
    return TRUE;
}

/* --------------------------------------------------------------------------------------------- */
/*** public functions ****************************************************************************/
/* --------------------------------------------------------------------------------------------- */

/** returns FALSE on error */
gboolean
edit_store_macro_cmd (WEdit *edit)
{
    int i;
    int hotkey;
    GString *macros_string = NULL;
    const char *section_name = "editor";
    gchar *macros_fname;
    GArray *macros = NULL;
    int tmp_act;
    mc_config_t *macros_config;
    char *skeyname;

    hotkey =
        editcmd_dialog_raw_key_query (_("Save macro"), _("Press the macro's new hotkey:"), TRUE);
    if (hotkey == ESC_CHAR)
        return FALSE;

    tmp_act = keybind_lookup_keymap_command (WIDGET (edit)->keymap, hotkey);
    /* return FALSE if try assign macro into restricted hotkeys */
    if (tmp_act == CK_MacroStartRecord
        || tmp_act == CK_MacroStopRecord || tmp_act == CK_MacroStartStopRecord)
        return FALSE;

    edit_delete_macro (edit, hotkey);

    macros_fname = mc_config_get_full_path (MC_MACRO_FILE);
    macros_config = mc_config_init (macros_fname, FALSE);
    g_free (macros_fname);

    if (macros_config == NULL)
        return FALSE;

    edit_push_undo_action (edit, KEY_PRESS + edit->start_display);

    skeyname = tty_keycode_to_keyname (hotkey);

    for (i = 0; i < macro_index; i++)
    {
        macro_action_t m_act;
        const char *action_name;

        action_name = keybind_lookup_actionname (record_macro_buf[i].action);
        if (action_name == NULL)
            break;

        if (macros == NULL)
        {
            macros = g_array_new (TRUE, FALSE, sizeof (macro_action_t));
            macros_string = g_string_sized_new (250);
        }

        m_act.action = record_macro_buf[i].action;
        m_act.ch = record_macro_buf[i].ch;
        g_array_append_val (macros, m_act);
        g_string_append_printf (macros_string, "%s:%i;", action_name, (int) record_macro_buf[i].ch);
    }

    if (macros == NULL)
        mc_config_del_key (macros_config, section_name, skeyname);
    else
    {
        macros_t macro;

        macro.hotkey = hotkey;
        macro.macro = macros;
        g_array_append_val (macros_list, macro);
        mc_config_set_string (macros_config, section_name, skeyname, macros_string->str);
    }

    g_free (skeyname);

    edit_macro_sort_by_hotkey ();

    if (macros_string != NULL)
        g_string_free (macros_string, TRUE);
    mc_config_save_file (macros_config, NULL);
    mc_config_deinit (macros_config);

    return TRUE;
}

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

/** return FALSE on error */

gboolean
edit_load_macro_cmd (WEdit *edit)
{
    mc_config_t *macros_config = NULL;
    gchar **profile_keys, **keys;
    gchar **values, **curr_values;
    const char *section_name = "editor";
    gchar *macros_fname;

    (void) edit;

    if (macros_list == NULL || macros_list->len != 0)
        return FALSE;

    macros_fname = mc_config_get_full_path (MC_MACRO_FILE);
    macros_config = mc_config_init (macros_fname, TRUE);
    g_free (macros_fname);

    if (macros_config == NULL)
        return FALSE;

    keys = mc_config_get_keys (macros_config, section_name, NULL);

    for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
    {
        int hotkey;
        GArray *macros = NULL;

        values = mc_config_get_string_list (macros_config, section_name, *profile_keys, NULL);
        hotkey = tty_keyname_to_keycode (*profile_keys, NULL);

        for (curr_values = values; *curr_values != NULL && *curr_values[0] != '\0'; curr_values++)
        {
            char **macro_pair;

            macro_pair = g_strsplit (*curr_values, ":", 2);

            if (macro_pair != NULL)
            {
#if defined(__WATCOMC__) //WIN32/c11
                macro_action_t m_act = { 0 };
                m_act.ch = -1;
#else
                macro_action_t m_act = {
                    .action = 0,
                    .ch = -1
                };
#endif

                if (macro_pair[0] != NULL && macro_pair[0][0] != '\0')
                    m_act.action = keybind_lookup_action (macro_pair[0]);

                if (macro_pair[1] != NULL && macro_pair[1][0] != '\0')
                    m_act.ch = strtol (macro_pair[1], NULL, 0);

                if (m_act.action != 0)
                {
                    /* a shell command */
                    if ((m_act.action / CK_PipeBlock (0)) == 1)
                    {
                        m_act.action = CK_PipeBlock (0);
                        if (m_act.ch > 0)
                            m_act.action += m_act.ch;
                        m_act.ch = -1;
                    }

                    if (macros == NULL)
                        macros = g_array_new (TRUE, FALSE, sizeof (m_act));

                    g_array_append_val (macros, m_act);
                }

                g_strfreev (macro_pair);
            }
        }

        if (macros != NULL)
        {
#if defined(__WATCOMC__) //WIN32/c11
            macros_t macro = {0};
            macro.hotkey = hotkey;
            macro.macro = macros;
#else
            macros_t macro = {
                .hotkey = hotkey,
                .macro = macros
            };
#endif

            g_array_append_val (macros_list, macro);
        }

        g_strfreev (values);
    }

    g_strfreev (keys);
    mc_config_deinit (macros_config);
    edit_macro_sort_by_hotkey ();

    return TRUE;
}

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

void
edit_delete_macro_cmd (WEdit *edit)
{
    int hotkey;

    hotkey = editcmd_dialog_raw_key_query (_("Delete macro"), _("Press macro hotkey:"), TRUE);

    if (hotkey != 0 && !edit_delete_macro (edit, hotkey))
        message (D_ERROR, _("Delete macro"), _("Macro not deleted"));
}

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

gboolean
edit_repeat_macro_cmd (WEdit *edit)
{
    gboolean ok;
    char *f;
    long count_repeat = 0;

    f = input_dialog (_("Repeat last commands"), _("Repeat times:"), MC_HISTORY_EDIT_REPEAT, NULL,
                      INPUT_COMPLETE_NONE);
    ok = (f != NULL && *f != '\0');

    if (ok)
    {
        char *error = NULL;

        count_repeat = strtol (f, &error, 0);

        ok = (*error == '\0');
    }

    g_free (f);

    if (ok)
    {
        int i, j;

        edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
        edit->force |= REDRAW_PAGE;

        for (j = 0; j < count_repeat; j++)
            for (i = 0; i < macro_index; i++)
                edit_execute_cmd (edit, record_macro_buf[i].action, record_macro_buf[i].ch);

        edit_update_screen (edit);
    }

    return ok;
}

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

/** returns FALSE on error */
gboolean
edit_execute_macro (WEdit *edit, int hotkey)
{
    gboolean res = FALSE;

    if (hotkey != 0)
    {
        int indx;

        indx = edit_get_macro (edit, hotkey);
        if (indx != -1)
        {
            const macros_t *macros;

            macros = &g_array_index (macros_list, struct macros_t, indx);
            if (macros->macro->len != 0)
            {
                guint i;

                edit->force |= REDRAW_PAGE;

                for (i = 0; i < macros->macro->len; i++)
                {
                    const macro_action_t *m_act;

                    m_act = &g_array_index (macros->macro, struct macro_action_t, i);
                    edit_execute_cmd (edit, m_act->action, m_act->ch);
                    res = TRUE;
                }
            }
        }
    }

    return res;
}

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

void
edit_begin_end_macro_cmd (WEdit *edit)
{
    /* edit is a pointer to the widget */
    if (edit != NULL)
    {
        long command = macro_index < 0 ? CK_MacroStartRecord : CK_MacroStopRecord;

        edit_execute_key_command (edit, command, -1);
    }
}

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

void
edit_begin_end_repeat_cmd (WEdit *edit)
{
    /* edit is a pointer to the widget */
    if (edit != NULL)
    {
        long command = macro_index < 0 ? CK_RepeatStartRecord : CK_RepeatStopRecord;

        edit_execute_key_command (edit, command, -1);
    }
}

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