// This file is part of fityk program. Copyright (C) Marcin Wojdyr
// Licence: GNU General Public License version 2
// $Id: sdebug.cpp 296 2007-05-24 20:04:00Z wojdyr $

/// In this file:
///  Script Editor and Debugger (ScriptDebugDlg)

#include <wx/wxprec.h>
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <fstream>

#include "sdebug.h"
#include "gui.h" //ftk
#include "../cmd.h" //check_command_syntax()
#include "../logic.h" 

#include "img/open.xpm"
#include "img/exec_selected.xpm"
#include "img/exec_down.xpm"
#include "img/save.xpm"
#include "img/save_as.xpm"
#include "img/close.xpm"

using namespace std;

enum {
    ID_SE_OPEN           = 28300,
    ID_SE_EXECSEL               ,
    ID_SE_EXECDOWN              ,
    ID_SE_SAVE                  ,
    ID_SE_SAVE_AS               ,
    ID_SE_CLOSE                 ,
    ID_SE_NB                    ,
    ID_SE_TXT
};


BEGIN_EVENT_TABLE(ScriptDebugDlg, wxDialog)
    EVT_TOOL(ID_SE_OPEN, ScriptDebugDlg::OnOpenFile)
    EVT_TOOL(ID_SE_EXECSEL, ScriptDebugDlg::OnExecSelected)
    EVT_TOOL(ID_SE_EXECDOWN, ScriptDebugDlg::OnExecDown)
    EVT_TOOL(ID_SE_SAVE, ScriptDebugDlg::OnSave)
    EVT_TOOL(ID_SE_SAVE_AS, ScriptDebugDlg::OnSaveAs)
    EVT_TOOL(ID_SE_CLOSE, ScriptDebugDlg::OnClose)
    EVT_NOTEBOOK_PAGE_CHANGED(ID_SE_NB, ScriptDebugDlg::OnPageChange)
    EVT_TEXT(ID_SE_TXT, ScriptDebugDlg::OnTextChange)
END_EVENT_TABLE()

ScriptDebugDlg::ScriptDebugDlg(wxWindow* parent, wxWindowID id)
    : wxDialog(parent, id, wxT(""), 
               wxDefaultPosition, wxSize(600, 500), 
               wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
{
    wxBoxSizer *top_sizer = new wxBoxSizer(wxVERTICAL);
    tb = new wxToolBar(this, -1, wxDefaultPosition, wxDefaultSize, 
                       wxTB_HORIZONTAL | wxNO_BORDER);
    tb->SetToolBitmapSize(wxSize(24, 24));
    tb->AddTool(ID_SE_OPEN, wxT("Open"), wxBitmap(open_xpm), wxNullBitmap,
                wxITEM_NORMAL, wxT("Open File"), wxT("Open script file"));
    tb->AddSeparator();
    tb->AddTool(ID_SE_EXECSEL, wxT("Execute"), 
                wxBitmap(exec_selected_xpm), wxNullBitmap,
                wxITEM_NORMAL, wxT("Execute selected"), 
                wxT("Execute selected lines"));
    tb->AddTool(ID_SE_EXECDOWN, wxT("Execute"), 
                wxBitmap(exec_down_xpm), wxNullBitmap,
                wxITEM_NORMAL, wxT("Execute selected and select next"), 
                wxT("Execute selected lines"));
    tb->AddSeparator();
    tb->AddTool(ID_SE_SAVE, wxT("Save"), wxBitmap(save_xpm), wxNullBitmap,
                wxITEM_NORMAL, wxT("Save"), 
                wxT("Save to file"));
    tb->AddTool(ID_SE_SAVE_AS, wxT("Save as"), 
                wxBitmap(save_as_xpm), wxNullBitmap,
                wxITEM_NORMAL, wxT("Save as..."), 
                wxT("Save a copy to file"));
#if 0
    tb->AddSeparator();
    tb->AddTool(wxID_UNDO, wxT("Undo"), 
                wxBitmap(undo_xpm), wxNullBitmap,
                wxITEM_NORMAL, wxT("Undo"), 
                wxT("Undo the last edit"));
    tb->AddTool(wxID_REDO, wxT("Redo"), 
                wxBitmap(redo_xpm), wxNullBitmap,
                wxITEM_NORMAL, wxT("Redo"), 
                wxT("Redo the last undone edit"));
#endif
    tb->AddSeparator();
    tb->AddTool(ID_SE_CLOSE, wxT("Close"), wxBitmap(close_xpm), wxNullBitmap,
                wxITEM_NORMAL, wxT("Exit debugger"), wxT("Close debugger"));
    tb->Realize();
    top_sizer->Add(tb, 0, wxEXPAND); 
    nb = new wxNotebook(this, ID_SE_NB);

    list = new wxListView(nb, -1, 
                          wxDefaultPosition, wxDefaultSize,
                          wxLC_REPORT|wxLC_HRULES|wxLC_VRULES);
    list->InsertColumn(0, wxT("No"));
    list->InsertColumn(1, wxT("Text"));
    list->InsertColumn(2, wxT("Exec. time"));
    list->SetColumnWidth(0, 40);
    list->SetColumnWidth(1, 430);
    list->SetColumnWidth(2, 100);

    txt = new wxTextCtrl(nb, ID_SE_TXT, wxT(""), 
                         wxDefaultPosition, wxDefaultSize,
                         wxTE_MULTILINE|wxTE_RICH);

    nb->AddPage(list, wxT("view"));
    nb->AddPage(txt, wxT("edit"));
    top_sizer->Add(nb, 1, wxALL|wxEXPAND, 0);
    SetSizerAndFit(top_sizer);
    SetSize(600, 500);
    set_title();
}

void ScriptDebugDlg::OpenFile(wxWindow *parent)
{
    wxFileDialog dialog(parent, wxT("open script file"), dir, wxT(""), 
                        wxT("fityk scripts (*.fit)|*.fit|all files|*"), 
                        wxFD_OPEN /*| wxFD_FILE_MUST_EXIST*/);
    if (dialog.ShowModal() == wxID_OK) 
        do_open_file(dialog.GetPath());
    dir = dialog.GetDirectory();
}

bool ScriptDebugDlg::do_open_file(wxString const& path_)
{
    bool r = false;
    if (wxFileExists(path_)) 
        r = txt->LoadFile(path_);
    else
        txt->Clear();
    if (!r)
        txt->MarkDirty(); //new file -> modified
    path = path_;
    script_dir = get_directory(wx2s(path));
    set_title();
    make_list_from_txt();
    return r;
}

void ScriptDebugDlg::make_list_from_txt()
{
    list->DeleteAllItems();
    // there is at least one bug in wxTextCtrl::GetLineText()
    // (empty line gives "\n"+next line) so we don't use it
    // the bug was fixed on 2007-04-09
    vector<string> lines = split_string(wx2s(txt->GetValue()), "\n");
    for (size_t i = 0; i != lines.size(); ++i) 
        add_line(i+1, lines[i]);
}

void ScriptDebugDlg::OnSave(wxCommandEvent& event)
{ 
    if (!path.IsEmpty())
        save_file(path); 
    else
        OnSaveAs(event);
}

void ScriptDebugDlg::OnSaveAs(wxCommandEvent&)
{
    wxFileDialog dialog(this, wxT("save script as..."), dir, wxT(""), 
                        wxT("fityk scripts (*.fit)|*.fit|all files|*"), 
                        wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
    if (dialog.ShowModal() == wxID_OK) 
        save_file(dialog.GetPath());
    dir = dialog.GetDirectory();
}

void ScriptDebugDlg::save_file(wxString const& save_path)
{
    bool r = txt->SaveFile(save_path);
    if (r) {
        path = save_path;
        script_dir = get_directory(wx2s(path));
        txt->DiscardEdits();
        set_title();
    }
}

void ScriptDebugDlg::add_line(int n, string const& line)
{
    int pos = list->GetItemCount();
    list->InsertItem(pos, n == -1 ? wxString(wxT("...")) 
                                  : wxString::Format(wxT("%i"), n));
    string::size_type sep = line.find(';');
    string::size_type hash = line.find('#');
    string head, tail;
    if (sep != string::npos && (hash == string::npos || sep < hash)) {
        head = string(line, 0, sep+1);
        tail = string(line, sep+1);
    }
    else
        head = line;
    
    list->SetItem(pos, 1, s2wx(head));
    string::size_type nonblank = head.find_first_not_of(" \r\n\t");
    bool is_comment = (nonblank == string::npos || head[nonblank] == '#');
    list->SetItemData(pos, is_comment ? 1 : 0); //to be skipped if comment
    if (is_comment) 
        list->SetItemTextColour(pos, *wxGREEN);
    else if (!check_command_syntax(head))
        list->SetItemBackgroundColour(pos, *wxRED);
    else if (head[nonblank] == 'i' || head[nonblank] == 'p') { //info or plot
        list->SetItemTextColour(pos, *wxBLUE);
    }
    if (!tail.empty())
        add_line(-1, tail);
}

void ScriptDebugDlg::exec_line(int n)
{
    wxStopWatch sw;
    wxString line;
    if (nb->GetSelection() == 0)  //view tab
        line = get_list_item(n);
    else if (nb->GetSelection() == 1) { //edit tab
        line = txt->GetLineText(n);
        // there was a bug in wxTextCtrl::GetLineText(),
        // empty line gives "\n"+next line
        if (!line.empty() && line[0] == '\n') // wx bug 
            line = wxT("");
    }
    string s = wx2s(line);
    replace_all(s, "_EXECUTED_SCRIPT_DIR_/", script_dir);
    Commands::Status r = ftk->exec(s);
    long millisec = sw.Time();
    if (nb->GetSelection() == 0) { //view tab
        if (r == Commands::status_ok) {
            list->SetItem(n, 2, wxString::Format(wxT("%.2f"), millisec/1000.));
        }
        else {
            list->SetItem(n, 2, wxT("ERROR"));
        }
    }
}

int ScriptDebugDlg::ExecSelected()
{
    long last = -1;
    if (nb->GetSelection() == 0) { //view tab
        for (long i = list->GetFirstSelected(); i != -1; 
                i = list->GetNextSelected(i)) {
            exec_line(i);
            last = i;
        }
    }
    else if (nb->GetSelection() == 1) { //edit tab
        long from, to;
        txt->GetSelection(&from, &to);
        if (from == to) { //no selection
            long x, y;
            txt->PositionToXY(txt->GetInsertionPoint(), &x, &y);
            if (y >= 0) 
                exec_line(y);
            last = y;
        }
        else { //selection, exec all lines (not only selection)
            long x, y, y2;
            txt->PositionToXY(from, &x, &y);
            txt->PositionToXY(to, &x, &y2);
            for (int i = y; i <= y2; ++i) {
                if (i >= 0) 
                    exec_line(i);
            }
            return y2;
        }
    }
    return last;
}

void ScriptDebugDlg::OnExecDown(wxCommandEvent&)
{
    long last = ExecSelected();
    if (last == -1)
        return;
    ++last;
    if (nb->GetSelection() == 0) { //view tab
        //skip comments
        while (last < list->GetItemCount() && list->GetItemData(last) == 1)
            ++last;
        for (long i = 0; i < list->GetItemCount(); ++i)
            list->Select(i, i == last);
        if (last < list->GetItemCount())
            list->Focus(last);
    }
    else { //edit tab
        txt->SetInsertionPoint(txt->XYToPosition(0, last));
    }
}

wxString ScriptDebugDlg::get_list_item(int i)
{
    wxListItem info;
    info.SetId(i);
    info.SetColumn(1);
    info.SetMask(wxLIST_MASK_TEXT);
    list->GetItem(info);
    return info.GetText();
}

void ScriptDebugDlg::OnPageChange(wxNotebookEvent& event)
{
    int old_sel = event.GetOldSelection();
    if (old_sel == 0) {
        long line = list->GetFirstSelected();
        if (line >= 0)
            txt->SetInsertionPoint(txt->XYToPosition(0, line));
#if 0
        tb->EnableTool(wxID_UNDO, txt->CanUndo());
        tb->EnableTool(wxID_REDO, txt->CanRedo());
#endif
    }
    else if (old_sel == 1) {
        long x, y;
        txt->PositionToXY(txt->GetInsertionPoint(), &x, &y);
        make_list_from_txt();
        list->Select(y);
        list->Focus(y);
        list->SetFocus();
#if 0
        tb->EnableTool(wxID_UNDO, false);
        tb->EnableTool(wxID_REDO, false);
#endif
    }
}

void ScriptDebugDlg::OnTextChange(wxCommandEvent&)
{
    //if (GetTitle().EndsWith(" *") != txt->IsModified())  //wx2.8
    if ((GetTitle().Right(2) == wxT(" *")) != txt->IsModified())
        set_title();
#if 0
    tb->EnableTool(wxID_UNDO, txt->CanUndo());
    tb->EnableTool(wxID_REDO, txt->CanRedo());
#endif
}

void ScriptDebugDlg::set_title()
{
    wxString p = path.IsEmpty() ? wxString(wxT("unnamed")) : path;
    if (txt->IsModified())
        p += wxT(" *");
    SetTitle(p);
}




syntax highlighted by Code2HTML, v. 0.9.1