// This file is part of fityk program. Copyright (C) Marcin Wojdyr
// Licence: GNU General Public License version 2
// $Id: cmd2.cpp 297 2007-05-25 02:33:19Z wojdyr $

/// big grammars in Spirit take a lot of time and memory to compile
/// so they must be splitted into separate compilation units
/// that's the only reason why this file is not a part of cmd.cpp

#include <boost/spirit/core.hpp>
#include <boost/spirit/actor/assign_actor.hpp>
#include <boost/spirit/actor/push_back_actor.hpp>
#include <boost/spirit/actor/clear_actor.hpp>
#include <boost/spirit/utility/chset.hpp>
#include <boost/spirit/utility/chset_operators.hpp>
#include <math.h>

#include "cmd2.h"
#include "optional_suffix.h"
#include "logic.h"
#include "data.h"
#include "sum.h"
#include "ui.h"
#include "fit.h"
#include "settings.h"
#include "datatrans.h"
#include "guess.h"
#include "var.h"
#include "func.h"
#include "ast.h"

#ifndef CONFIGURE_BUILD
# define CONFIGURE_BUILD "UNKNOWN"
#endif
#ifndef CONFIGURE_ARGS
# define CONFIGURE_ARGS "UNKNOWN"
#endif

using namespace std;

namespace cmdgram {

bool with_plus, deep_cp;
string t, t2, t3, output_redir;
string prepared_info;
bool info_append;
int tmp_int, tmp_int2, ds_pref;
double tmp_real, tmp_real2;
vector<string> vt, vr;
vector<int> vn, vds;
const int new_dataset = -1;
const int all_datasets = -2;
bool outdated_plot = false;
bool no_info_output = false;


vector<DataWithSum*> get_datasets_from_indata()
{
    vector<DataWithSum*> result;
    if (vds.empty()) {
        if (AL->get_ds_count() == 1)
            result.push_back(AL->get_ds(0));
        else
            throw ExecuteError("Dataset must be specified (eg. 'in @0').");
    }
    else if (vds.size() == 1 && vds[0] == all_datasets)
        for (int i = 0; i < AL->get_ds_count(); ++i)
            result.push_back(AL->get_ds(i));
    else
        for (vector<int>::const_iterator i = vds.begin(); i != vds.end(); ++i)
            result.push_back(AL->get_ds(*i));
    return result;
}

IntRangeGrammar  IntRangeG;

} //namespace cmdgram

using namespace cmdgram;

namespace {

void do_import_dataset(char const*, char const*)
{
    AL->import_dataset(tmp_int, t, t2, vn);
    outdated_plot=true;  
}

void do_export_dataset(char const*, char const*)
{
    vector<string> const& ff_names = AL->get_sum(tmp_int)->get_ff_names();
    AL->get_data(tmp_int)->export_to_file(t, vt, ff_names); 
}

void do_append_data(char const*, char const*)
{
    int n = AL->append_ds();
    AL->activate_ds(n);
    outdated_plot=true;  
}

void do_load_data_sum(char const*, char const*)
{
    vector<Data const*> dd;
    for (vector<int>::const_iterator i = vn.begin(); i != vn.end(); ++i)
        dd.push_back(AL->get_data(*i));
    if (tmp_int == new_dataset) 
        tmp_int = AL->append_ds();
    AL->get_data(tmp_int)->load_data_sum(dd, t);
    AL->activate_ds(tmp_int);
    outdated_plot=true;  
}

void do_plot(char const*, char const*)
{
    if (vds.size() == 1 && vds[0] >= 0)
        AL->activate_ds(vds[0]);
    bool need_ds = false;
    for (vector<string>::const_iterator i = vr.begin(); i != vr.end(); ++i)
        if (i->empty())
            need_ds = true;
    if (need_ds) {
        vector<DataWithSum*> dsds = get_datasets_from_indata();
        //move active ds to the front
        DataWithSum* ads = AL->get_ds(AL->get_active_ds_position());
        vector<DataWithSum*>::iterator pos = find(dsds.begin(),dsds.end(), ads);
        if (pos != dsds.end() && pos != dsds.begin()) {
            *pos = dsds[0];
            dsds[0] = ads;
        }
        AL->view.set_datasets(dsds);
    }
    AL->view.parse_and_set(vr);
    AL->get_ui()->draw_plot(1, true);
    outdated_plot = false;
}

void do_output_info(char const*, char const*)
{
    prepared_info = strip_string(prepared_info);
    if (no_info_output)
        return;
    if (output_redir.empty())
        AL->rmsg(prepared_info);
    else {
        ofstream os(output_redir.c_str(), 
                    ios::out | (info_append ? ios::app : ios::trunc));
        if (!os) 
            throw ExecuteError("Can't open file: " + output_redir);
        os << prepared_info << endl;
    }
}

void do_print_info(char const* a, char const* b)
{
    string s = string(a,b);
    string m;
    vector<Variable*> const &variables = AL->get_variables(); 
    vector<Function*> const &functions = AL->get_functions(); 
    if (s.empty())
        m = "info about what?";
    else if (s == "version") {
        if (with_plus) {
            m = "Version: " VERSION 
                "\nBuild system type: " CONFIGURE_BUILD
                "\nConfigured with: " CONFIGURE_ARGS
#ifdef __GNUC__
                "\nCompiler: GCC"
#endif
#ifdef __VERSION__
                "\nCompiler version: " __VERSION__ 
#endif
                "\nCompilation date: " __DATE__ 
                "\nBoost.Spirit version: " + S(SPIRIT_VERSION / 0x1000) 
                                + "." + S(SPIRIT_VERSION % 0x1000 / 0x0100)
                                + "." + S(SPIRIT_VERSION % 0x0100);
 
        }
        else
            m = VERSION;
    }
    else if (s == "variables") {
        if (variables.empty())
            m = "No variables found.";
        else {
            m = "Defined variables: ";
            for (vector<Variable*>::const_iterator i = variables.begin(); 
                    i != variables.end(); ++i)
                if (with_plus)
                    m += "\n" + (*i)->get_info(AL->get_parameters(), false);
                else
                    if ((*i)->is_visible())
                        m += (*i)->xname + " ";
        }
    }
    else if (s[0] == '$') {
        const Variable* v = AL->find_variable(string(s, 1));
        m = v->get_info(AL->get_parameters(), with_plus);
        if (with_plus) {
            vector<string> refs = AL->get_variable_references(string(s, 1));
            if (!refs.empty())
                m += "\nreferenced by: " + join_vector(refs, ", ");
        }
    }
    else if (s == "functions") {
        if (functions.empty())
            m = "No functions found.";
        else {
            m = "Defined functions: ";
            for (vector<Function*>::const_iterator i = functions.begin(); 
                    i != functions.end(); ++i)
                if (with_plus)
                    m += "\n" + (*i)->get_info(variables, AL->get_parameters());
                else
                    m += (*i)->xname + " ";
        }
    }
    else if (s == "types") {
        m = "Defined function types: ";
        vector<string> const& types = Function::get_all_types();
        for (vector<string>::const_iterator i = types.begin(); 
                i != types.end(); ++i)
            if (with_plus)
                m += "\n" + Function::get_formula(*i);
            else
                m += *i + " ";
    }
    else if (s[0] == '%') {
        const Function* f = AL->find_function(string(s, 1));
        m = f->get_info(variables, AL->get_parameters(), with_plus);
    }
    else if (s == "datasets") {
        m = S(AL->get_ds_count()) + " datasets.";
        if (with_plus)
            for (int i = 0; i < AL->get_ds_count(); ++i)
                m += "\n@" + S(i) + ": " + AL->get_data(i)->get_title();
    }
    else if (s[0] == '@') {
        Data const* data = AL->get_data(tmp_int);
        if (s.find(".title") != string::npos)
            m = data->get_title();
        else if (s.find(".filename") != string::npos)
            m = data->get_filename();
        else
            m = data->get_info();
    }
    else if (s == "view") {
        m = AL->view.str();
    }
    else if (s == "set")
        m = AL->get_settings()->print_usage();
    else if (startswith(s, "fit-history"))
        m = AL->get_fit_container()->param_history_info();
    else if (startswith(s, "fit"))
        m = AL->get_fit()->get_info(get_datasets_from_indata());
    else if (startswith(s, "errors"))
        m = AL->get_fit()->getErrorInfo(get_datasets_from_indata(), with_plus);
    else if (startswith(s, "peaks")) {
        vector<fp> errors;
        vector<DataWithSum*> v = get_datasets_from_indata();
        if (with_plus) 
            errors = AL->get_fit()->get_symmetric_errors(v);
        for (vector<DataWithSum*>::const_iterator i=v.begin(); i!=v.end(); ++i){
            m += "# " + (*i)->get_data()->get_title() + "\n";
            m += (*i)->get_sum()->get_peak_parameters(errors) + "\n";
        }
    }
    else if (startswith(s, "formula")) {
        vector<DataWithSum*> v = get_datasets_from_indata();
        for (vector<DataWithSum*>::const_iterator i=v.begin(); i!=v.end(); ++i){
            m += "# " + (*i)->get_data()->get_title() + "\n";
            bool gnuplot = AL->get_settings()->get_e("formula-export-style")==1;
            m += (*i)->get_sum()->get_formula(!with_plus, gnuplot) + "\n";
        }
    }
    else if (s == "commands")
        m = AL->get_ui()->get_commands().get_info(with_plus);
    else if (startswith(s, "guess")) {
        vector<DataWithSum*> v = get_datasets_from_indata();
        for (vector<DataWithSum*>::const_iterator i = v.begin(); 
                                                           i != v.end(); ++i)
            m += get_guess_info(*i, vr);
    }
    prepared_info += "\n" + m;
}

void do_print_func(char const*, char const*)
{
    vector<string> const &names = AL->get_sum(ds_pref)->get_names(t2[0]);
    if (tmp_int < 0)
        tmp_int += names.size();
    if (is_index(tmp_int, names)) 
        prepared_info += "\n" + AL->find_function(names[tmp_int])
            ->get_info(AL->get_variables(), AL->get_parameters(), with_plus);
    else
        prepared_info += "\nNot found.";
}

void do_print_sum_info(char const*, char const*)
{
    string m = t2 + ": "; 
    vector<int> const &idx = AL->get_sum(ds_pref)->get_indices(t2[0]);
    for (vector<int>::const_iterator i = idx.begin(); i != idx.end(); ++i) {
        Function const* f = AL->get_function(*i);
        if (with_plus)
            m += "\n" + f->get_info(AL->get_variables(), AL->get_parameters());
        else
            m += f->xname + " ";
    }
    prepared_info += "\n" + m;
}

void do_print_sum_derivatives_info(char const*, char const*)
{
    fp x = get_transform_expression_value(t2, AL->get_data(ds_pref));
    Sum const* sum = AL->get_sum(ds_pref);
    vector<fp> symb = sum->get_symbolic_derivatives(x);
    vector<fp> num = sum->get_numeric_derivatives(x, 1e-4);
    assert (symb.size() == num.size());
    string m = "F(" + S(x) + ")=" + S(sum->value(x));
    for (int i = 0; i < size(num); ++i) {
        if (is_neq(symb[i], 0) || is_neq(num[i], 0))
            m += "\ndF / d " + AL->find_variable_handling_param(i)->xname 
                + " = (symb.) " + S(symb[i]) + " = (num.) " + S(num[i]);
    }
    prepared_info += "\n" + m;
}

void do_print_deriv(char const* a, char const* b)
{
    string s = string(a, b);
    prepared_info += "\n" + get_derivatives_str(s);
}

void do_print_debug_info(char const*, char const*)  
{ 
    string m;
    if (t == "idx") {   // show varnames and var_idx from VariableUser
        for (int i = 0; i < size(AL->get_functions()); ++i) 
            m += S(i) + ": " + AL->get_function(i)->get_debug_idx_info() +"\n";
        for (int i = 0; i < size(AL->get_variables()); ++i) 
            m += S(i) + ": " + AL->get_variable(i)->get_debug_idx_info() +"\n";
    }
    else if (t == "rd") { // show values of derivatives for all variables
        for (int i = 0; i < size(AL->get_variables()); ++i) {
            Variable const* var = AL->get_variable(i);
            m += var->xname + ": ";
            vector<Variable::ParMult> const& rd 
                                        = var->get_recursive_derivatives();
            for (vector<Variable::ParMult>::const_iterator i = rd.begin(); 
                                                           i != rd.end(); ++i)
                m += S(i->p) 
                    + "/" + AL->find_variable_handling_param(i->p)->xname 
                    + "/" + S(i->mult) + "  ";
            m += "\n";
        }
    }
    else if (t.size() > 0 && t[0] == '%') { // show bytecode
        Function const* f = AL->find_function(t);
        m = f->get_bytecode();
    }
    AL->rmsg(m);
}


void do_print_data_expr(char const*, char const*)
{
    string s;
    if (vds.empty() && !is_data_dependent_expression(t2)) //no data
        s = S(get_transform_expression_value(t2, 0));
    else {
        vector<DataWithSum*> v = get_datasets_from_indata();
        if (v.size() == 1)
            s = S(get_transform_expression_value(t2, v[0]->get_data()));
        else {
            map<DataWithSum const*, int> m;
            for (int i = 0; i < AL->get_ds_count(); ++i)
                m[AL->get_ds(i)] = i;
            for (vector<DataWithSum*>::const_iterator i = v.begin(); 
                    i != v.end(); ++i) {
                fp k = get_transform_expression_value(t2, (*i)->get_data());
                if (i != v.begin())
                    s += "\n";
                s += "in @" + S(m[*i]); 
                if (with_plus)
                    s += " " + (*i)->get_data()->get_title();
                s += ": " + S(k);
            }
        }
    }
    prepared_info += "\n" + s;
}

void do_print_func_type(char const* a, char const* b)
{
    string s = string(a,b);
    string m = Function::get_formula(s);
    if (m.empty())
        m = "Undefined function type: " + s;
    prepared_info += "\n" + m;
}

void do_guess(char const*, char const*)
{
    vector<DataWithSum*> v = get_datasets_from_indata();
    for (vector<DataWithSum*>::const_iterator i = v.begin(); i != v.end(); ++i)
        guess_and_add(*i, t, t2, vr, vt);
    outdated_plot=true;  
}

void set_data_title(char const*, char const*)  { 
    AL->get_data(ds_pref)->title = t; 
}

void do_list_commands(char const*, char const*)
{
    vector<string> cc 
      = AL->get_ui()->get_commands().get_commands(tmp_int, tmp_int2, with_plus);
    prepared_info += "\n" + join_vector(cc, "\n");
}

} //namespace

template <typename ScannerT>
Cmd2Grammar::definition<ScannerT>::definition(Cmd2Grammar const& /*self*/)
{
    //these static constants for assign_a are workaround for assign_a
    //problems, as proposed by Joao Abecasis at Spirit-general ML
    //Message-ID: <435FB3DD.8030205@gmail.com>
    //Subject: [Spirit-general] Re: weird assign_a(x,y) problem
    static const bool true_ = true;
    static const bool false_ = false;
    static const int minus_one = -1;
    static const char *dot = ".";
    static const char *empty = "";


    in_data
        = eps_p [clear_a(vds)]
        >> !("in" >> (lexeme_d['@' >> uint_p [push_back_a(vds)]
                               ]
                       % ','
                     | str_p("@*") [push_back_a(vds, all_datasets)]
                     )
            )
        ;

    ds_prefix
        = lexeme_d['@' >> uint_p [assign_a(ds_pref)] 
           >> '.']
        | eps_p [assign_a(ds_pref, minus_one)]
        ;

    compact_str
        = lexeme_d['\'' >> (+~ch_p('\''))[assign_a(t)] 
                   >> '\'']
        | lexeme_d[+chset<>(anychar_p - chset<>(" \t\n\r;,#"))] [assign_a(t)]
        ;

    type_name
        = lexeme_d[(upper_p >> +alnum_p)] 
        ;

    function_param
        = lexeme_d[alpha_p >> *(alnum_p | '_')]
        ;

    existing_dataset_nr
        = lexeme_d['@' >> uint_p [assign_a(tmp_int)]
                  ]
        ;

    dataset_nr
        = str_p("@+") [assign_a(tmp_int, new_dataset)]
        | existing_dataset_nr
        ;

    dataset_handling
        = (dataset_nr >> ch_p('<') >> compact_str //load from file
           >> lexeme_d[!(alpha_p >> *alnum_p)] [assign_a(t2)] [clear_a(vn)]
           >> !(uint_p [push_back_a(vn)] 
                % ',')
          ) [&do_import_dataset]
        | dataset_nr >> ch_p('=') [clear_a(vn)] [assign_a(t, empty)] //sum/dup
          >> !(lexeme_d[lower_p >> +(alnum_p | '-' | '_')] [assign_a(t)])
          >> (lexeme_d['@' >> uint_p [push_back_a(vn)]  
                                           ] % '+') [&do_load_data_sum]
        | (existing_dataset_nr [clear_a(vt)]
           >> !('(' >> ((DataExpressionG
                        | ("*F(" >> DataExpressionG >> ")")
                        ) [push_back_a(vt)] 
                        % ',')
                >> ')')
           >> '>' >> compact_str) [&do_export_dataset]
        | str_p("@+")[&do_append_data] 
        ;

    plot_range  //first clear vr if needed 
        = (ch_p('[') >> ']') [push_back_a(vr,empty)][push_back_a(vr,empty)]
        | '[' >> (real_p|"."|eps_p) [push_back_a(vr)] 
          >> ':' >> (real_p|"."|eps_p) [push_back_a(vr)] 
          >> ']'  
        | str_p(".") [push_back_a(vr,dot)][push_back_a(vr,dot)] // [.:.]
        | eps_p [push_back_a(vr,empty)][push_back_a(vr,empty)] // [:]
        ;
    
    info_arg
        = (str_p("commands") >> IntRangeG) [&do_list_commands] 
        |  ( str_p("version") 
           | str_p("variables") 
           | VariableLhsG 
           | str_p("types") 
           | str_p("functions") 
           | str_p("datasets") 
           | str_p("commands") 
           | str_p("view") 
           | str_p("set")) [&do_print_info] 
        | ((str_p("fit-history") 
            | "fit"
            | "errors"
            | "peaks"
            | "formula"
                    ) >> in_data) [&do_print_info] 
        | (str_p("guess") [clear_a(vr)]
           >> plot_range >> in_data) [&do_print_info]
        | type_name[&do_print_func_type]
        | (no_actions_d[DataExpressionG][assign_a(t2)] 
             >> in_data) [&do_print_data_expr]
        | FunctionLhsG [&do_print_info]
        | ds_prefix >> (str_p("F")|"Z")[assign_a(t2)] 
          >> ( ('[' >> int_p [assign_a(tmp_int)] >> ']') [&do_print_func] 
             | eps_p [&do_print_sum_info]
             )
        | (ds_prefix >> str_p("dF") >> '(' 
           >> no_actions_d[DataExpressionG][assign_a(t2)] 
           >> ')') [&do_print_sum_derivatives_info]
        | (existing_dataset_nr 
                >> (str_p(".filename") | ".title" | eps_p)) [&do_print_info]
        | "debug " >> compact_str [&do_print_debug_info] //no output_redir
        // this will eat also ',', because voigt(x,y) needs it
        // unfortunatelly "i der x^2, sin(x)" is made impossible
        | "der " >> (+chset<>(anychar_p - chset<>(";#"))) [&do_print_deriv]
        | eps_p [&do_print_info] 
        ;

    guess
        = (FunctionLhsG [assign_a(t)] >> '=' 
          | eps_p [assign_a(t, empty)]
          )
          >> optional_suffix_p("g","uess")[clear_a(vt)] [clear_a(vr)] 
          >> type_name [assign_a(t2)]
          >> plot_range
          >> !((function_param >> '=' >> no_actions_d[FuncG])
                                                       [push_back_a(vt)]
               % ',')
          >> in_data
        ;

    optional_plus
        = str_p("+") [assign_a(with_plus, true_)] 
        | eps_p [assign_a(with_plus, false_)] 
        ;

    statement 
        = (optional_suffix_p("i","nfo") [assign_a(output_redir, empty)]
                                        [assign_a(prepared_info, empty)]
           >> optional_plus >> (info_arg % ',')
           >> !( ( str_p(">>") [assign_a(info_append, true_)]
                 | str_p(">") [assign_a(info_append, false_)]
                 )
                >> compact_str [assign_a(output_redir, t)]
               )
          ) [&do_output_info]
        | (optional_suffix_p("p","lot") [clear_a(vr)] 
           >> plot_range >> plot_range >> in_data) [&do_plot]
        | guess [&do_guess]
        | (ds_prefix >> "title" >> '=' >> compact_str)[&set_data_title]
        | dataset_handling
        ;
}


template Cmd2Grammar::definition<scanner<char const*, scanner_policies<skipper_iteration_policy<iteration_policy>, match_policy, action_policy> > >::definition(Cmd2Grammar const&);

template Cmd2Grammar::definition<scanner<char const*, scanner_policies<skipper_iteration_policy<iteration_policy>, match_policy, no_actions_action_policy<action_policy> > > >::definition(Cmd2Grammar const&);

Cmd2Grammar cmd2G;




syntax highlighted by Code2HTML, v. 0.9.1