// author = @tonybanters
// A Simple Text Editor
// edited by schjmann19 - may 2026

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Text_Editor.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_File_Chooser.H>
#include <fstream>
#include <cstring>
#include <string>
#include <cstdio>
#include <map>

enum Mode { NORMAL, INSERT, COMMAND };

class CustomTextEditor : public Fl_Text_Editor {
public:
    CustomTextEditor(int x, int y, int w, int h) : Fl_Text_Editor(x, y, w, h), mode(NORMAL), editor_window(nullptr) {}
    
    void set_editor_window(class EditorWindow* ew) { editor_window = ew; }
    Mode get_mode() const { return mode; }
    void set_mode(Mode m) { mode = m; }
    
    int handle(int event);
    
private:
    Mode mode;
    class EditorWindow* editor_window;
};

class CustomInput : public Fl_Input {
public:
    CustomInput(int x, int y, int w, int h) : Fl_Input(x, y, w, h), editor_window(nullptr) {}
    
    void set_editor_window(class EditorWindow* ew) { editor_window = ew; }
    int handle(int event);
    
private:
    class EditorWindow* editor_window;
};

class EditorWindow : public Fl_Double_Window {
public:
    EditorWindow(int w, int h, const char* t);
    ~EditorWindow();

private:
    CustomTextEditor* editor;
    CustomInput* command_input;
    Fl_Box* status_bar;
    char filename[256];
    bool quit_requested;

    const char* detect_language(const char* fname);
    void update_status_bar();
    void process_command(const char* cmd);
    void set_mode(Mode m);
    void open_file(const char* fname = nullptr);
    void save_file(const char* fname = nullptr);
    void load_file(const char* fname);
    
    friend class CustomTextEditor;
    friend class CustomInput;
};

int main() {
    EditorWindow window(900, 700, "Simple Text Editor");
    window.show();
    return Fl::run();
}

int CustomTextEditor::handle(int event) {
    if (event == FL_KEYDOWN) {
        int key = Fl::event_key();
        
        if (mode == NORMAL) {
            if (key == ':') {
                editor_window->set_mode(COMMAND);
                return 1;
            }
            if (key == 'i') {
                editor_window->set_mode(INSERT);
                return 1;
            }
        } else if (mode == INSERT) {
            if (key == FL_Escape) {
                editor_window->set_mode(NORMAL);
                return 1;
            }
        }
    }
    
    return Fl_Text_Editor::handle(event);
}

int CustomInput::handle(int event) {
    if (event == FL_KEYDOWN) {
        int key = Fl::event_key();
        
        if (key == FL_Escape) {
            editor_window->set_mode(NORMAL);
            return 1;
        }
    }
    
    return Fl_Input::handle(event);
}

EditorWindow::EditorWindow(int w, int h, const char* t)
    : Fl_Double_Window(w, h, t), quit_requested(false) {
    strcpy(filename, "untitled.txt");

    // Create custom text editor first
    editor = new CustomTextEditor(10, 30, w - 20, h - 90);
    editor->buffer(new Fl_Text_Buffer());
    editor->set_editor_window(this);

    // Create status bar at top
    status_bar = new Fl_Box(10, 5, w - 20, 20);
    status_bar->box(FL_FLAT_BOX);
    status_bar->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
    update_status_bar();

    // Make window resizable
    resizable(editor);

    // Create command input at bottom
    command_input = new CustomInput(10, h - 35, w - 20, 25);
    command_input->set_editor_window(this);
    command_input->callback([](Fl_Widget*, void* v) {
        EditorWindow* ew = (EditorWindow*)v;
        const char* cmd = ew->command_input->value();
        ew->process_command(cmd);
        ew->command_input->value("");
        ew->set_mode(NORMAL);
        if (ew->quit_requested) {
            ew->hide();
        }
    }, this);

    end();
}

EditorWindow::~EditorWindow() {
    // FLTK handles cleanup automatically
}

const char* EditorWindow::detect_language(const char* fname) {
    #include "extension_map.h" // defines a map of extensions to languages
    const char* dot = strrchr(fname, '.');
    if (!dot) return "";

    auto it = extensions.find(dot);
    return it != extensions.end() ? it->second.c_str() : "None";
}

void EditorWindow::update_status_bar() {
    static char status[512];
    const char* mode_str = "NORMAL";
    switch (editor->get_mode()) {
        case NORMAL: mode_str = "NORMAL"; break;
        case INSERT: mode_str = "INSERT"; break;
        case COMMAND: mode_str = "COMMAND"; break;
    }
    snprintf(status, sizeof(status), " [%s] %s | %s", mode_str, filename, detect_language(filename));
    status_bar->label(status);
    redraw();
}

void EditorWindow::set_mode(Mode m) {
    editor->set_mode(m);
    
    if (m == INSERT) {
        editor->take_focus();
        command_input->value("");
    } else if (m == COMMAND) {
        command_input->take_focus();
        command_input->value(":");
        command_input->insert_position(1);
    } else if (m == NORMAL) {
        editor->take_focus();
        command_input->value("");
    }
    
    update_status_bar();
}

void EditorWindow::process_command(const char* cmd) {
    if (!cmd || strlen(cmd) < 2 || cmd[0] != ':') return;

    std::string command(cmd + 1);
    
    if (command == "q") {
        quit_requested = true;
    } else if (command == "wq") {
        save_file();
        quit_requested = true;
    } else if (command.substr(0, 2) == "w ") {
        save_file(command.substr(2).c_str());
    } else if (command == "w") {
        save_file();
    } else if (command.substr(0, 2) == "e ") {
        load_file(command.substr(2).c_str());
    }
}

void EditorWindow::open_file(const char* fname) {
    const char* file = fname ? fname : fl_file_chooser("Open File", "*", NULL);
    if (file) {
        load_file(file);
    }
}

void EditorWindow::save_file(const char* fname) {
    if (fname) strcpy(filename, fname);
    const char* txt = editor->buffer()->text();
    std::ofstream out(filename);
    if (out) {
        out << txt;
        out.close();
        update_status_bar();
    }
}

void EditorWindow::load_file(const char* fname) {
    std::ifstream in(fname);
    if (in) {
        std::string content((std::istreambuf_iterator<char>(in)),
                            std::istreambuf_iterator<char>());
        in.close();

        editor->buffer()->text(content.c_str());
        strcpy(filename, fname);
        update_status_bar();
    }
}