// compare.c

#include <stdio.h>
#include <string.h>
#include <glib.h>
#include "compare.h"

// Hash table mapping sounds to UFNodes
static GHashTable *sound_nodes = NULL;

// Find function for Union-Find
static UFNode* uf_find(UFNode *node) {
    if (node->parent != node) {
        node->parent = uf_find(node->parent);
    }
    return node->parent;
}

// Union function for Union-Find
static void uf_union(UFNode *node1, UFNode *node2) {
    UFNode *root1 = uf_find(node1);
    UFNode *root2 = uf_find(node2);
    if (root1 != root2) {
        root2->parent = root1;
    }
}

// Create or get UFNode for a sound
static UFNode* get_or_create_ufnode(const gchar *sound) {
    UFNode *node = g_hash_table_lookup(sound_nodes, sound);
    if (!node) {
        node = g_new(UFNode, 1);
        node->sound = g_strdup(sound);
        node->parent = node;
        g_hash_table_insert(sound_nodes, node->sound, node);
    }
    return node;
}

// Load sound correspondences from file
void load_sound_correspondences(const gchar *filename) {
    if (sound_nodes != NULL) {
        // Free previous data
        free_sound_correspondences();
    }

    sound_nodes = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);

    // Open file
    FILE *file = fopen(filename, "r");
    if (!file) {
        g_warning("Failed to open correspondence file: %s", filename);
        return;
    }

    char line[1024];
    while (fgets(line, sizeof(line), file)) {
        // Skip empty lines or lines starting with '#' (comments)
        if (line[0] == '\n' || line[0] == '#') continue;

        gchar **tokens = g_strsplit(line, ",", -1);
        GPtrArray *nodes_in_line = g_ptr_array_new();

        for (guint i = 0; tokens[i] != NULL; i++) {
            gchar *sound = g_strstrip(tokens[i]);
            if (g_strcmp0(sound, "") != 0) {
                UFNode *node = get_or_create_ufnode(sound);
                g_ptr_array_add(nodes_in_line, node);
            }
        }
        // Union all nodes in this line
        if (nodes_in_line->len > 1) {
            UFNode *first_node = g_ptr_array_index(nodes_in_line, 0);
            for (guint i = 1; i < nodes_in_line->len; i++) {
                UFNode *node = g_ptr_array_index(nodes_in_line, i);
                uf_union(first_node, node);
            }
        }
        g_ptr_array_free(nodes_in_line, TRUE);
        g_strfreev(tokens);
    }

    fclose(file);
}

// Free sound correspondences data
void free_sound_correspondences() {
    if (sound_nodes != NULL) {
        GHashTableIter iter;
        gpointer key, value;
        g_hash_table_iter_init(&iter, sound_nodes);
        while (g_hash_table_iter_next(&iter, &key, &value)) {
            UFNode *node = (UFNode *)value;
            g_free(node->sound);
            g_free(node);
        }
        g_hash_table_destroy(sound_nodes);
        sound_nodes = NULL;
    }
}

// Split word into sounds
GArray* split_word_into_sounds(const gchar *word) {
    GArray *sounds = g_array_new(FALSE, FALSE, sizeof(gchar *));
    const gchar *p = word;

    while (*p) {
        gunichar c = g_utf8_get_char(p);
        int char_len = g_unichar_to_utf8(c, NULL);
        // Handle multi-character sounds if necessary
        // For now, we assume each sound is one Unicode character
        gchar *sound = g_utf8_substring(p, 0, 1);
        g_array_append_val(sounds, sound);
        p = g_utf8_next_char(p);
    }

    return sounds;
}

// Free sounds array
void free_sounds_array(GArray *sounds) {
    for (guint i = 0; i < sounds->len; i++) {
        gchar *sound = g_array_index(sounds, gchar *, i);
        g_free(sound);
    }
    g_array_free(sounds, TRUE);
}

// Find sound correspondences among multiple words
GArray* find_sound_correspondences(GPtrArray *words) {
    GArray **sounds_arrays = g_new0(GArray *, words->len);
    guint min_length = G_MAXUINT;

    // Split each word into sounds
    for (guint i = 0; i < words->len; i++) {
        const gchar *word = (const gchar *)g_ptr_array_index(words, i);
        GArray *sounds = split_word_into_sounds(word);
        sounds_arrays[i] = sounds;
        if (sounds->len < min_length) {
            min_length = sounds->len;
        }
    }

    GArray *result = g_array_new(FALSE, FALSE, sizeof(PositionCorrespondence));

    // For each position
    for (guint pos = 0; pos < min_length; pos++) {
        GPtrArray *sounds_at_pos = g_ptr_array_new_with_free_func(g_free);
        // Collect sounds at this position from all words
        for (guint i = 0; i < words->len; i++) {
            gchar *sound = g_array_index(sounds_arrays[i], gchar *, pos);
            g_ptr_array_add(sounds_at_pos, g_strdup(sound));
        }
        // Add to result
        PositionCorrespondence pc = { pos, sounds_at_pos };
        g_array_append_val(result, pc);
    }

    // Free sounds arrays
    for (guint i = 0; i < words->len; i++) {
        free_sounds_array(sounds_arrays[i]);
    }

    g_free(sounds_arrays);

    return result;
}

// Format correspondences
gchar* format_correspondences(GArray *correspondences, GPtrArray *languages) {
    GString *result = g_string_new("");

    if (correspondences->len == 0) {
        g_string_append(result, "No sound correspondences found.");
    } else {
        for (guint i = 0; i < correspondences->len; i++) {
            PositionCorrespondence pc = g_array_index(correspondences, PositionCorrespondence, i);
            // For each sound at this position, check if they correspond
            gboolean all_correspond = TRUE;
            UFNode *group = NULL;
            GString *sounds_str = g_string_new("");
            for (guint j = 0; j < pc.sounds->len; j++) {
                gchar *sound = (gchar *)g_ptr_array_index(pc.sounds, j);
                UFNode *node = g_hash_table_lookup(sound_nodes, sound);
                if (!node) {
                    // The sound is not in the correspondences
                    all_correspond = FALSE;
                } else {
                    UFNode *root = uf_find(node);
                    if (!group) {
                        group = root;
                    } else if (group != root) {
                        all_correspond = FALSE;
                    }
                }
                // Append sound and language
                gchar *lang = (gchar *)g_ptr_array_index(languages, j);
                g_string_append_printf(sounds_str, "/%s/ (%s)", sound, lang);
                if (j < pc.sounds->len - 1) {
                    g_string_append(sounds_str, ", ");
                }
            }

            if (all_correspond && pc.sounds->len > 1) {
                g_string_append_printf(result, "At position %u, sounds correspond: %s.\n", pc.position + 1, sounds_str->str);
            } else {
                g_string_append_printf(result, "At position %u, sounds do not correspond: %s.\n", pc.position + 1, sounds_str->str);
            }

            g_string_free(sounds_str, TRUE);

            // Free sounds at position
            g_ptr_array_free(pc.sounds, TRUE);
        }
    }

    g_array_free(correspondences, TRUE);

    gchar *final_result = result->str;
    g_string_free(result, FALSE);
    return final_result;
}


CC = gcc
CFLAGS = `pkg-config --cflags gtk+-3.0 glib-2.0`
LIBS = `pkg-config --libs gtk+-3.0 glib-2.0`
CFLAGS += -g -Wall

all: main

main: main.c compare.c compare.h
	$(CC) -o main main.c compare.c $(CFLAGS) $(LIBS)

clean:
	rm -f main


// main.c

#include <gtk/gtk.h>
#include "compare.h"

typedef struct {
    // Lists to store the language and word entry widgets
    GList *language_entries;
    GList *word_entries;
    GtkWidget *grid;
    GtkWidget *button_add;
    GtkWidget *button_compare;
    GtkWidget *textview_results;
    gint current_column;
} AppWidgets;

// Callback to add a new language and word entry
static void on_add_button_clicked(GtkButton *button, gpointer user_data) {
    AppWidgets *widgets = (AppWidgets *)user_data;

    // Create new entries
    GtkWidget *label_lang = gtk_label_new("Language:");
    GtkWidget *entry_lang = gtk_entry_new();
    gtk_entry_set_text(GTK_ENTRY(entry_lang), "Language");

    GtkWidget *label_word = gtk_label_new("Word:");
    GtkWidget *entry_word = gtk_entry_new();

    // Attach to grid
    gtk_grid_attach(GTK_GRID(widgets->grid), label_lang, widgets->current_column, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(widgets->grid), entry_lang, widgets->current_column, 1, 1, 1);
    gtk_grid_attach(GTK_GRID(widgets->grid), label_word, widgets->current_column, 2, 1, 1);
    gtk_grid_attach(GTK_GRID(widgets->grid), entry_word, widgets->current_column, 3, 1, 1);

    // Update the lists
    widgets->language_entries = g_list_append(widgets->language_entries, entry_lang);
    widgets->word_entries = g_list_append(widgets->word_entries, entry_word);

    widgets->current_column++;

    // Show new widgets
    gtk_widget_show(label_lang);
    gtk_widget_show(entry_lang);
    gtk_widget_show(label_word);
    gtk_widget_show(entry_word);

    // Adjust the compare button and text view position
    gtk_grid_attach(GTK_GRID(widgets->grid), widgets->button_compare, 0, 4, widgets->current_column, 1);
    gtk_grid_attach(GTK_GRID(widgets->grid), widgets->textview_results, 0, 5, widgets->current_column, 1);

    gtk_widget_show_all(widgets->grid);
}

// Callback for the Compare button
static void on_compare_button_clicked(GtkButton *button, gpointer user_data) {
    AppWidgets *widgets = (AppWidgets *)user_data;

    GPtrArray *languages = g_ptr_array_new_with_free_func(g_free);
    GPtrArray *words = g_ptr_array_new_with_free_func(g_free);

    // Collect all language and word entries
    GList *lang_iter = widgets->language_entries;
    GList *word_iter = widgets->word_entries;
    gboolean words_entered = FALSE;

    while (lang_iter && word_iter) {
        const gchar *language = gtk_entry_get_text(GTK_ENTRY(lang_iter->data));
        const gchar *word = gtk_entry_get_text(GTK_ENTRY(word_iter->data));

        if (g_strcmp0(word, "") != 0) {
            words_entered = TRUE;
            g_ptr_array_add(languages, g_strdup(language));
            g_ptr_array_add(words, g_strdup(word));
        }

        lang_iter = lang_iter->next;
        word_iter = word_iter->next;
    }

    if (!words_entered) {
        GtkWidget *dialog = gtk_message_dialog_new(NULL,
            GTK_DIALOG_DESTROY_WITH_PARENT,
            GTK_MESSAGE_ERROR,
            GTK_BUTTONS_OK,
            "Please enter at least one word for comparison.");
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
        return;
    }

    GPtrArray *comparisons = compare_all_words(languages, words);

    // Format the results
    GString *result_text = g_string_new("");

    // Keep track of languages that are similar or not
    GHashTable *similar_languages = g_hash_table_new(g_str_hash, g_str_equal);

    for (guint i = 0; i < comparisons->len; i++) {
        WordComparison *comparison = g_ptr_array_index(comparisons, i);

        if (!g_str_has_prefix(comparison->result_text->str, "No similarities")) {
            g_string_append_printf(result_text,
                "Words from %s and %s are similar:\n%s\n",
                comparison->language1, comparison->language2, comparison->result_text->str);

            g_hash_table_add(similar_languages, comparison->language1);
            g_hash_table_add(similar_languages, comparison->language2);
        } else {
            g_string_append_printf(result_text,
                "Words from %s and %s are not similar.\n",
                comparison->language1, comparison->language2);
        }
    }

    // Find languages that are not similar to any others
    for (guint i = 0; i < languages->len; i++) {
        gchar *language = g_ptr_array_index(languages, i);
        if (!g_hash_table_contains(similar_languages, language)) {
            g_string_append_printf(result_text,
                "Word \"%s\" from %s is not similar to other variants.\n",
                (gchar *)g_ptr_array_index(words, i), language);
        }
    }

    GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widgets->textview_results));
    gtk_text_buffer_set_text(buffer, result_text->str, -1);

    g_string_free(result_text, TRUE);
    g_ptr_array_free(languages, TRUE);
    g_ptr_array_free(words, TRUE);
    free_comparisons(comparisons);
    g_hash_table_destroy(similar_languages);
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);

    // Load sound correspondences from file
    load_sound_correspondences("sound_correspondences.csv");

    AppWidgets *widgets = g_new0(AppWidgets, 1);
    widgets->current_column = 0;

    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Word Comparison");
    gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    widgets->grid = gtk_grid_new();
    gtk_container_add(GTK_CONTAINER(window), widgets->grid);

    // Initialize the '+' button
    widgets->button_add = gtk_button_new_with_label("+");
    g_signal_connect(widgets->button_add, "clicked", G_CALLBACK(on_add_button_clicked), widgets);

    // Initialize the Compare button
    widgets->button_compare = gtk_button_new_with_label("Compare");
    g_signal_connect(widgets->button_compare, "clicked", G_CALLBACK(on_compare_button_clicked), widgets);

    // Initialize the text view for results
    widgets->textview_results = gtk_text_view_new();
    gtk_text_view_set_editable(GTK_TEXT_VIEW(widgets->textview_results), FALSE);

    // Add the '+' button to the grid
    gtk_grid_attach(GTK_GRID(widgets->grid), widgets->button_add, widgets->current_column, 0, 1, 1);
    gtk_widget_show(widgets->button_add);

    // Initially add one set of entries
    on_add_button_clicked(GTK_BUTTON(widgets->button_add), widgets);

    // Attach Compare button and text view
    gtk_grid_attach(GTK_GRID(widgets->grid), widgets->button_compare, 0, 4, widgets->current_column, 1);
    gtk_grid_attach(GTK_GRID(widgets->grid), widgets->textview_results, 0, 5, widgets->current_column, 1);

    gtk_widget_show_all(window);

    gtk_main();

    // Free memory
    free_sound_correspondences();

    // Free the widgets structure
    g_list_free(widgets->language_entries);
    g_list_free(widgets->word_entries);
    g_free(widgets);

    return 0;
}

Напиши для файлов main.c и compare.c файл compare.h, который их связывает