// This file is part of fityk program. Copyright (C) Marcin Wojdyr
// Licence: GNU General Public License version 2
// $Id: main.cpp 322 2007-07-24 00:17:11Z wojdyr $

// CLI-only file 
// in this file: main loop, readline support (command expansion)
// and part of UserInterface implementation (CLI-specific)

            
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <ctype.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
// readline header will be included later, unless NO_READLINE is defined

#include "../common.h"
#include "../ui.h"
#include "../logic.h"
#include "../cmd.h"
#include "../settings.h"
#include "../func.h"
#include "gnuplot.h"

using namespace std;

Ftk* ftk = 0;

//------ UserInterface - implementation of CLI specific methods ------

void cli_show_message (OutputStyle style, const string& s)
{
    if (style == os_warn)
        cout << '\a';
    cout << s << endl;
}

void cli_do_draw_plot (bool /*now*/)
{
    static GnuPlot my_gnuplot;
    my_gnuplot.plot();
}

void cli_wait(float seconds) 
{
    seconds = fabs(seconds);
    timespec ts;
    ts.tv_sec = static_cast<int>(seconds);
    ts.tv_nsec = static_cast<int>((seconds - ts.tv_sec) * 1e9);
    nanosleep(&ts, 0);
}

//----------------------------------------------------------------- 

namespace {

const char* prompt = "=-> ";

/// returns absolute path to config directory
string get_config_dir()
{
    static bool first_run = true;
    static string dir;

    if (!first_run)
        return dir;
    char t[200];
    char *home_dir = getenv("HOME");
    if (!home_dir) {
        getcwd(t, 200);
        home_dir = t;
    }
    // '/' is assumed as path separator
    dir = S(home_dir) + "/" + config_dirname + "/"; 
    if (access(dir.c_str(), X_OK) != 0)
        dir = ""; //gcc 2.95 have no std::string.clear() method
    first_run = false;
    return dir;
}



#ifndef NO_READLINE

    // readline library headers can have old-style typedefs like:
    // typedef int Function ();
    // it would clash with class Function in fityk
    //  anti-Function workaround #1: works with libreadline >= 4.2
#   define _FUNCTION_DEF 
    //  anti-Function workaround #2 (should work always), part 1
#   define Function Function_Bn4MtsgO3fQXM4Ag4z

#   include <readline/readline.h>
#   include <readline/history.h>

#   ifdef Function     
#       undef Function // anti-Function workaround #2, part 2   
#   endif

//TODO support for libedit (MacOs X, etc.)


string set_eq_str;


void read_and_execute_input()
{
    char *line = readline (prompt);
    if (!line)
        throw ExitRequestedException();
    if (line && *line)
        add_history (line);
    string s = line;
    free ((void*) line);
    ftk->get_ui()->exec_and_log(s);
}


char *commands[] = { "info", "plot", "delete", "set", "fit",
        "commands", "dump", "sleep", "reset", "quit", "guess", "define"
        };

char *after_info[] = { "variables", "types", "functions", "datasets",
         "commands", "view", "set", "fit", "fit-history", "errors", "formula",
         "peaks", "guess", "F", "Z", "formula", "dF"
        };

char *command_generator (const char *text, int state)
{
    static unsigned int list_index = 0;
    if (!state) 
        list_index = 0;
    while (list_index < sizeof(commands) / sizeof(char*)) {
        char *name = commands[list_index];
        list_index++;
        if (strncmp (name, text, strlen(text)) == 0)
            return strdup(name);
    }
    //if (!state)
        //return strdup(text); // to prevent file expansion
    return 0;
}

char *type_generator(const char *text, int state)
{
    static unsigned int list_index = 0;
    static vector<string> e;
    if (!state) {
        e.clear();
        vector<string> const tt = Function::get_all_types(); 
        for (vector<string>::const_iterator i = tt.begin(); i != tt.end(); ++i)
            if (!strncmp(i->c_str(), text, strlen(text)))
                e.push_back(*i);
        list_index = 0;
    }
    else
        list_index++;
    if (list_index < e.size())
        return strdup(e[list_index].c_str());
    else
        return 0;
}

char *type_or_guess_generator(const char *text, int state)
{
    static bool give_guess = false;
    const char *guess = "guess";
    if (!state) 
        give_guess = true;
    char *r = type_generator(text, state);
    if (!r && give_guess) {
        give_guess = false;
        if (strncmp (guess, text, strlen(text)) == 0)
            return strdup(guess);
    }
    return r;
}

char *info_generator(const char *text, int state)
{
    static unsigned int list_index = 0;
    static vector<string> e;
    if (!state) {
        e.clear();
        vector<string> const tt = Function::get_all_types(); 
        for (vector<string>::const_iterator i = tt.begin(); i != tt.end(); ++i)
            if (!strncmp(i->c_str(), text, strlen(text)))
                e.push_back(*i);
        for (size_t i = 0; i < sizeof(after_info) / sizeof(char*); ++i) {
            char *name = after_info[i];
            if (strncmp (name, text, strlen(text)) == 0)
                e.push_back(name);
        }
        list_index = 0;
    }
    else
        list_index++;
    if (list_index < e.size())
        return strdup(e[list_index].c_str());
    else
        return 0;
}


char *function_generator(const char *text, int state)
{
    static unsigned int list_index = 0;
    static vector<string> e;
    if (!state) {
        e.clear();
        vector<Function*> const& ff = ftk->get_functions(); 
        for (vector<Function*>::const_iterator i=ff.begin(); i != ff.end(); ++i)
            if (!strncmp ((*i)->xname.c_str(), text, strlen(text)))
                e.push_back((*i)->xname);
        list_index = 0;
    }
    else
        list_index++;
    if (list_index < e.size())
        return strdup(e[list_index].c_str());
    else
        return 0;
}

char *variable_generator(const char *text, int state)
{
    static unsigned int list_index = 0;
    static vector<string> e;
    if (!state) {
        e.clear();
        vector<Variable*> const& vv = ftk->get_variables(); 
        for (vector<Variable*>::const_iterator i=vv.begin(); i != vv.end(); ++i)
            if (!strncmp ((*i)->name.c_str(), text, strlen(text)))
                e.push_back((*i)->name);
        list_index = 0;
    }
    else
        list_index++;
    if (list_index < e.size())
        return strdup(e[list_index].c_str());
    else
        return 0;
}

char *set_generator(const char *text, int state)
{
    static unsigned int list_index = 0;
    static vector<string> e;
    if (!state) {
        e = ftk->get_settings()->expanp(text);
        list_index = 0;
    }
    else
        list_index++;
    if (list_index < e.size())
        return strdup(e[list_index].c_str());
    else
        return 0;
}

char *set_eq_generator (const char *text, int state)
{
    static unsigned int list_index = 0;
    static vector<string> e;
    if (!state) {
        e = ftk->get_settings()->expand_enum(set_eq_str, text);
        list_index = 0;
    }
    else
        list_index++;
    if (list_index < e.size())
        return strdup(e[list_index].c_str());
    else
        return 0;
}

char **my_completion (const char *text, int start, int end)
{
    rl_attempted_completion_over = 1;
    //find start of the command, and skip blanks
    int cmd_start = start;
    while (cmd_start > 0 && rl_line_buffer[cmd_start-1] != ';')
        --cmd_start;
    while (isspace(rl_line_buffer[cmd_start])) 
        ++cmd_start;
    //command
    if (cmd_start == start)
        return rl_completion_matches(text, command_generator);
    char *ptr = rl_line_buffer+cmd_start;
    //check if it is after set command or after with
    if (cmd_start <= start-2 && !strncmp(ptr, "s ", 2)
            || cmd_start <= start-3 && !strncmp(ptr, "se ", 3) 
            || cmd_start <= start-4 && !strncmp(ptr, "set ", 4)
            || cmd_start <= start-2 && !strncmp(ptr, "w ", 2)
            || cmd_start <= start-3 && !strncmp(ptr, "wi ", 3) 
            || cmd_start <= start-4 && !strncmp(ptr, "wit ", 4)
            || cmd_start <= start-5 && !strncmp(ptr, "with ", 5)) {
        while (*ptr && !isspace(*ptr))
            ++ptr;
        ++ptr;
        char *has_eq = 0;
        for (char *i = ptr; i <= rl_line_buffer+end; ++i) {
            if (*i == '=')
                has_eq = i;
            else if (*i == ',') {
                ptr = i+1;
                has_eq = 0;
            }
        }
        if (!has_eq)
            return rl_completion_matches(text, set_generator);
        else {
            set_eq_str = strip_string(string(ptr, has_eq));
            return rl_completion_matches (text, set_eq_generator);
        }
    }
    // FunctionType completion
    if (cmd_start <= start-2 && !strncmp(ptr, "g ", 2)
            || cmd_start <= start-3 && !strncmp(ptr, "gu ", 3) 
            || cmd_start <= start-4 && !strncmp(ptr, "gue ", 4) 
            || cmd_start <= start-5 && !strncmp(ptr, "gues ", 5) 
            || cmd_start <= start-6 && !strncmp(ptr, "guess ", 6)) {
        return rl_completion_matches(text, type_generator);
    }
    // FunctionType or "guess" completion
    if (cmd_start <= start-3 && rl_line_buffer[cmd_start] == '%'
               && strchr(rl_line_buffer+cmd_start, '=')
               && !strchr(rl_line_buffer+cmd_start, '(')) {
        return rl_completion_matches(text, type_or_guess_generator);
    }

    // %function completion
    if (strlen(text) > 0 && text[0] == '%')
        return rl_completion_matches(text, function_generator);
    // $variable completion
    if (start > 0 && rl_line_buffer[start-1] == '$')
        return rl_completion_matches(text, variable_generator);

    // info completion
    if (cmd_start <= start-2 && (!strncmp(ptr, "i ", 2) 
                                 || !strncmp(ptr, "i+", 2))
            || cmd_start <= start-3 && (!strncmp(ptr, "in ", 3) 
                                        || !strncmp(ptr, "in+", 3))
            || cmd_start <= start-4 && (!strncmp(ptr, "inf ", 4) 
                                        || !strncmp(ptr, "inf+", 4))
            || cmd_start <= start-5 && (!strncmp(ptr, "info ", 5)
                                        || !strncmp(ptr, "info+", 5))) {
        return rl_completion_matches(text, info_generator);
    }

    ptr = rl_line_buffer + start - 1;
    while (ptr > rl_line_buffer && isspace(*ptr)) 
        --ptr;
    if (*ptr == '>' || *ptr == '<') { //filename completion
        rl_attempted_completion_over = 0; 
        return 0;
    }

    return 0;
}


/// Reads history (for readline) in ctor and saves it to file in dtor.
/// Proper use: single instance created at the beginning of the program
/// and destroyed at the end.
struct RlHistoryManager
{
    RlHistoryManager();
    ~RlHistoryManager();
    string hist_file;
};

/// read history file
RlHistoryManager::RlHistoryManager()
{
    string fityk_dir = get_config_dir();
    if (!fityk_dir.empty()) {
        hist_file = fityk_dir + "history";
        read_history (hist_file.c_str());
    }
}

/// save history to file
RlHistoryManager::~RlHistoryManager()
{
    //saving command history to file
    if (!hist_file.empty()) {
        int hist_file_size = -1;
        char *hfs = getenv ("HISTFILESIZE");
        if (hfs)
            hist_file_size = atoi (hfs);
        if (hist_file_size <= 0)
            hist_file_size = 500;
        write_history (hist_file.c_str());
        history_truncate_file (hist_file.c_str(), hist_file_size);
    }
}



bool main_loop()
{
    //initialize readline
    rl_readline_name = "fit";
    rl_attempted_completion_function = my_completion;

    RlHistoryManager hm;//it takes care about reading/saving readline history

    //the main loop -- reading input and executing commands
    for (;;)
        read_and_execute_input();

    cout << endl;


    return true;
}


#else //if NO_READLINE 

// the simplest version of user interface -- when readline is not available
bool main_loop()
{
    string s;
    for (;;) {
        cout << prompt;
        if (!getline(cin, s))
            break;
        ftk->get_ui()->exec_and_log(s);
    }
    cout << endl;
    return true;
}

#endif //NO_READLINE 



void interrupt_handler (int /*signum*/)
{
    //set flag for breaking long computations
    user_interrupt = true;
}

} // anonymous namespace

int main (int argc, char **argv)
{
    // setting Ctrl-C handler
    if (signal (SIGINT, interrupt_handler) == SIG_IGN) 
        signal (SIGINT, SIG_IGN);

    // process command-line arguments
    bool exec_init_file = true;
    bool quit = false;
    string script_string;
    for (int i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
            cout << 
              "Usage: cfityk [-h] [-V] [-c <str>] [script or data file...]\n"
              "  -h, --help            show this help message\n"
              "  -V, --version         output version information and exit\n"
              "  -c, --cmd=<str>       script passed in as string\n"
              "  -I, --no-init         don't process $HOME/.fityk/init file\n"
              "  -q, --quit            don't enter interactive shell\n";
            return 0;
        }
        else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) {
            cout << "fityk version " VERSION "\n";
            return 0;
        }
        else if (startswith(argv[i], "-c") || startswith(argv[i], "--cmd")) {
            if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cmd")) {
                argv[i] = 0;
                ++i;
                if (i < argc) {
                    script_string = argv[i];
                    argv[i] = 0;
                }
                else {
                    cerr << "Option " << argv[i] << " requires parameter\n";
                    return 1;
                }
            }
            else if (startswith(argv[i], "-c")) {
                script_string = string(argv[i] + 2);
                argv[i] = 0;
            }
            else if (startswith(argv[i], "--cmd")) {
                if (argv[i][5] != '=') {
                    cerr << "Unknown option: " << argv[i] << "\n";
                    return 1;
                }
                script_string = string(argv[i] + 6);
                argv[i] = 0;
            }
            else
                assert(0);
        }
        else if (!strcmp(argv[i], "-I") || !strcmp(argv[i], "--no-init")) {
            argv[i] = 0;
            exec_init_file = false;
        }
        else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quit")) {
            argv[i] = 0;
            quit = true;
        }
    }

    ftk = new Ftk;

    // set callbacks
    ftk->get_ui()->set_show_message(cli_show_message);
    ftk->get_ui()->set_do_draw_plot(cli_do_draw_plot);
    ftk->get_ui()->set_wait(cli_wait);

    if (exec_init_file) {
        // file with initial commands is executed first (if exists)
        string init_file = get_config_dir() + startup_commands_filename;
        if (access(init_file.c_str(), R_OK) == 0) {
            cerr << " -- reading init file: " << init_file << " --\n";
            ftk->get_ui()->exec_script(init_file);
            cerr << " -- end of init file --" << endl;
        }
    }

    try {
        //then string given with -c is executed
        if (!script_string.empty())
            ftk->get_ui()->exec_and_log(script_string);
        //the rest of parameters/arguments are scripts and/or data files
        for (int i = 1; i < argc; ++i) {
            if (argv[i])
                ftk->get_ui()->process_cmd_line_filename(argv[i]);
        }

        // the version of main_loop() depends on NO_READLINE  
        if (!quit)
            main_loop(); 
    } 
    catch(ExitRequestedException) {
        cerr << "\nbye...\n";
    }

    return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1