/* 
 * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */


#ifndef __GRT_PLUGIN_WIZARD_H__
#define __GRT_PLUGIN_WIZARD_H__

#include "base/string_utilities.h"
#include "workbench/wb_context.h"
#include "grt/common.h"

#include <glib/gstdio.h>

#include "mforms/radiobutton.h"
#include "mforms/selector.h"
#include "mforms/table.h"

using namespace mforms;


#define PYTHON_SCRIPT_TEMPLATE \
"# -*- coding: utf-8 -*-\n"\
"# MySQL Workbench Python script\n"\
"# <description>\n"\
"# Written in MySQL Workbench %wbversion%\n"\
"\n"\
"import grt\n"\
"#import mforms\n"

#define LUA_SCRIPT_TEMPLATE \
"-- MySQL Workbench lua script\n"\
"-- <description>\n"\
"-- Written in MySQL Workbench %wbversion%\n"


#define PYTHON_MODULE_TEMPLATE \
"# -*- coding: utf-8 -*-\n"\
"# MySQL Workbench module\n"\
"# <description>\n"\
"# Written in MySQL Workbench %wbversion%\n"\
"\n"\
"from wb import *\n"\
"import grt\n"\
"#import mforms\n"\
"\n\n"\
"ModuleInfo = DefineModule(%modulename%)\n"\
"\n\n"\
"@ModuleInfo.export()\n"\
"def %functionname%():\n"\
"    pass\n"



class NewPluginDialog : public Form
{
  Label *newLabel(const std::string &text)
  {
    Label *l= new Label(text);
    l->set_wrap_text(true);
    return l;
  }  
  Label *newDescr(const std::string &text)
  {
    Label *l= new Label(text);
    l->set_style(SmallHelpTextStyle);
    return l;
  }  
public:
  struct PluginTemplate {
    std::string name;
    std::string code;
    
    bool operator < (const PluginTemplate &other) const 
    {
      return name < other.name;
    }
  };
  
  NewPluginDialog(Form *owner, const std::string &template_dir)
  : Form(owner),
  _lua_script(RadioButton::new_id()),
  _python_script(_lua_script.group_id()),
  _python_module(_lua_script.group_id()),
  _python_plugin(_lua_script.group_id()),
  _tab(mforms::TabViewTabless)
  {
    set_title(_("New Script File"));
    
    load_plugin_templates(template_dir);
    
    Box *vbox= manage(new Box(false));

    vbox->set_spacing(12);
    vbox->set_padding(24);
    
    Label *l= manage(newLabel(_("Select the type of file to create")));
    l->set_style(WizardHeadingStyle);
    vbox->add(l, false, false);
    
    Box *box= manage(new Box(false));
    vbox->add(box, false, true);
    
    Table *table= manage(new Table());
    box->add(table, false, true);
    
    table->set_row_count(4);
    table->set_column_count(2);
    table->set_row_spacing(12);
    table->set_column_spacing(8);
    table->set_padding(12);
    
    _python_script.set_text(_("Python Script"));
    table->add(&_python_script, 0, 1, 0, 1, HFillFlag);
    table->add(manage(newDescr(_("Python Scripts that can be manually executed in the Scripting IDE or from the command line."))),
               1, 2, 0, 1, HExpandFlag|HFillFlag);
    _python_script.set_active(true);
    _lua_script.set_text(_("Lua Script"));
    table->add(&_lua_script, 0, 1, 1, 2, HFillFlag);
    table->add(manage(newDescr(_("Lua Scripts that can be manually executed in the Scripting IDE or from the command line.\nScripts are not loaded automatically."))),
               1, 2, 1, 2, HExpandFlag|HFillFlag);    
    _python_plugin.set_text(_("Python Plugin"));
    table->add(&_python_plugin, 0, 1, 2, 3, HFillFlag);
    table->add(manage(newDescr(_("Plugins accessible by the user from various places in Workbench:"))),
               1, 2, 2, 3, HExpandFlag|HFillFlag);
    _python_module.set_text(_("Python Module"));
    table->add(&_python_module, 0, 1, 3, 4, HFillFlag);
    table->add(manage(newDescr(_("Python modules containing functions exported to be used by other scripts, modules or plugins in Workbench."))),
               1, 2, 3, 4, HExpandFlag|HFillFlag);
    
    scoped_connect(_lua_script.signal_clicked(),boost::bind(&NewPluginDialog::changed_type, this));
    scoped_connect(_python_script.signal_clicked(),boost::bind(&NewPluginDialog::changed_type, this));
    scoped_connect(_python_module.signal_clicked(),boost::bind(&NewPluginDialog::changed_type, this));
    scoped_connect(_python_plugin.signal_clicked(),boost::bind(&NewPluginDialog::changed_type, this));
    
    for (std::vector<PluginTemplate>::const_iterator tpl= _plugin_template_list.begin();
         tpl != _plugin_template_list.end(); ++tpl)
      _plugin_template.add_item(tpl->name);
    
    l= manage(newLabel(_("Details")));
    l->set_style(WizardHeadingStyle);
    vbox->add(l, false, false);

    //
    _script_args.set_padding(12);
    _script_args.set_row_count(1);
    _script_args.set_column_count(3);
    _script_args.set_row_spacing(8);
    _script_args.set_column_spacing(8);
    _script_args.add(manage(new Label(_("Script File Name:"))), 0, 1, 0, 1, HFillFlag);
    _script_args.add(&_filename, 1, 2, 0, 1, HExpandFlag|HFillFlag);
    scoped_connect(_filename.signal_changed(),boost::bind(&NewPluginDialog::validate, this));
    _script_args.add(manage(newDescr(_("The default location for scripts is in the scripts folder\nof your Workbench configuration folder."))), 
                     2, 3, 0, 1, HFillFlag);
    _tab.add_page(&_script_args, "");
    
    //
    _plugin_args.set_padding(12);
    _plugin_args.set_row_count(3);
    _plugin_args.set_column_count(3);
    _plugin_args.set_row_spacing(8);
    _plugin_args.set_column_spacing(8);
    _plugin_args.add(manage(new Label(_("Template:"))), 0, 1, 0, 1, HFillFlag);
    _plugin_args.add(&_plugin_template, 1, 2, 0, 1, HFillFlag|HExpandFlag);
    _plugin_args.add(manage(newDescr(_("Select the template for the type of plugin you want to write.\nA file may contain as many plugin functions as you want."))), 
                     2, 3, 0, 1, HFillFlag);

    _plugin_args.add(manage(new Label(_("Plugin Name:"))), 0, 1, 1, 2, HFillFlag);
    _plugin_args.add(&_plugin_name, 1, 2, 1, 2, HFillFlag|HExpandFlag);
    scoped_connect(_plugin_name.signal_changed(),boost::bind(&NewPluginDialog::name_changed, this, &_plugin_name));
    _plugin_args.add(manage(newDescr(_("Provide a unique name for the plugin. Use alphanumeric\ncharacters and _ only."))), 
                     2, 3, 1, 2, HFillFlag);

    _plugin_args.add(manage(new Label(_("File Name:"))), 0, 1, 2, 3, HFillFlag);
    _plugin_args.add(&_plugin_file, 1, 2, 2, 3, HExpandFlag|HFillFlag);
    scoped_connect(_plugin_file.signal_changed(),boost::bind(&NewPluginDialog::validate, this));
    _plugin_args.add(manage(newDescr(_("Name of the plugin file, must be unique and contain only\nalphanumeric chars and _. Specify name only, without a path."))), 
                     2, 3, 2, 3, HFillFlag);

    _plugin_name.set_value("myplugin");
    name_changed(&_plugin_name);
    _tab.add_page(&_plugin_args, "");
        
    //
    _module_args.set_padding(12);
    _module_args.set_row_count(3);
    _module_args.set_column_count(3);
    _module_args.set_row_spacing(8);
    _module_args.set_column_spacing(8);
    _module_args.add(manage(new Label(_("Module Name:"))), 0, 1, 0, 1, HFillFlag);
    _module_args.add(&_module_name, 1, 2, 0, 1, HFillFlag|HExpandFlag);
    _module_args.add(manage(newDescr(_("Provide a name for the module. Use alphanumeric\ncharacters and _ only."))), 
                     2, 3, 0, 1, HFillFlag);
    scoped_connect(_module_name.signal_changed(),boost::bind(&NewPluginDialog::name_changed, this, &_module_name));
    _module_args.add(manage(new Label(_("Function Name:"))), 0, 1, 1, 2, HFillFlag);
    _module_args.add(&_function_name, 1, 2, 1, 2, HFillFlag|HExpandFlag);    
    _module_args.add(manage(newDescr(_("Provide a name for the module function. Use alphanumeric\ncharacters and _ only."))), 
                     2, 3, 1, 2, HFillFlag);
    scoped_connect(_function_name.signal_changed(),boost::bind(&NewPluginDialog::validate, this));
    
    _module_args.add(manage(new Label(_("File Name:"))), 0, 1, 2, 3, HFillFlag);
    _module_args.add(&_module_file, 1, 2, 2, 3, HExpandFlag|HFillFlag);
    _module_args.add(manage(newDescr(_("Name of the module file, must be unique and contain only\nalphanumeric chars and _. Specify name only, without a path."))), 
                     2, 3, 2, 3, HFillFlag);
    scoped_connect(_module_file.signal_changed(),boost::bind(&NewPluginDialog::validate, this));

    _module_name.set_value("mymodule");
    name_changed(&_module_name);
    _tab.add_page(&_module_args, "");
    
    vbox->add(&_tab, true, true);
    
    _ok.set_text(_("Create"));
    _cancel.set_text(_("Cancel"));
    
    box = manage(new Box(true));
    box->add(&_error_label, false, true);
    box->set_spacing(12);
    Utilities::add_end_ok_cancel_buttons(box, &_ok, &_cancel);
    vbox->add_end(box, false, true);
    
    set_content(vbox);
    set_size(-1, 450);
    changed_type();
  }
  
  virtual bool advance()
  {
    if (_lua_script.get_active() || _python_script.get_active())
    {
      std::string value= _filename.get_string_value();
      
      if (value.empty())
      {
        Utilities::show_error(_("Invalid Name"),
                              _("Please fill in a name for the script/module."),
                              "OK");
        return false;
      }
      
      for (std::string::const_iterator c= value.begin(); c != value.end(); ++c)
      {
        if (!isalnum(*c) && *c != '_')
        {
          Utilities::show_error("Invalid Name",
                                _("Please use only alpha-numeric characters and _ for the name."),
                                "OK");
          
          return false;
        }
      }
    }
    return true;
  }
  
  
  bool run(std::string &filename, std::string &code, bool &is_script, std::string& language)
  {
    if (run_modal(&_ok, &_cancel))
    {
      if (_lua_script.get_active())
      {
        filename= _filename.get_string_value();
        code= LUA_SCRIPT_TEMPLATE;
        is_script= true;
        language = "lua";
        
        if (!filename.empty() && !g_str_has_suffix(filename.c_str(), ".lua"))
          filename.append(".lua");
      }
      else if (_python_script.get_active())
      {
        filename= _filename.get_string_value();
        code= PYTHON_SCRIPT_TEMPLATE;
        is_script= true;
        language = "python";
        
        if (!filename.empty() && !g_str_has_suffix(filename.c_str(), ".py"))
          filename.append(".py");
      }
      else if (_python_module.get_active())
      {
        filename= _module_file.get_string_value();
        code= PYTHON_MODULE_TEMPLATE;
        bec::replace_string_inplace(code, "%modulename%",
                                    _module_name.get_string_value());
        bec::replace_string_inplace(code, "%functionname%",
                                    _function_name.get_string_value());
        is_script= false;
        language = "python";
      }
      else if (_python_plugin.get_active())
      {
        int index= _plugin_template.get_selected_index();
        filename= _plugin_file.get_string_value();
        if (index < 0) index= 0;
        code= _plugin_template_list[index].code;
        bec::replace_string_inplace(code, "%modulename%",
                                    _plugin_name.get_string_value());
        bec::replace_string_inplace(code, "%functionname%",
                                    _plugin_name.get_string_value());
        bec::replace_string_inplace(code, "%pluginname%",
                                    _plugin_name.get_string_value());
        is_script= false;
        language = "python";
      }

      bec::replace_string_inplace(code, "%wbversion%", base::strfmt("%i.%i.%i", APP_MAJOR_NUMBER, APP_MINOR_NUMBER, APP_RELEASE_NUMBER));
      
      return true;
    }
    return false;
  }
  
private:
  void changed_type()
  {
    if (_lua_script.get_active())
    {
      _tab.set_active_tab(0);
    }
    else if (_python_script.get_active())
    {
      _tab.set_active_tab(0);
    }
    else if (_python_module.get_active())
    {
      _tab.set_active_tab(2);
    }
    else if (_python_plugin.get_active())
    {
      _tab.set_active_tab(1);
    }
    validate();
  }

  inline bool is_valid_id(const std::string &s)
  {
    for (std::string::const_iterator i= s.begin(); i != s.end(); ++i)
    {
      if (!isalnum(*i) && *i != '_')
        return false;
    }
    return !s.empty();
  }
  
  inline bool is_valid_filename(const std::string &file, const std::string &ext)
  {
    if (!g_str_has_suffix(file.c_str(), ext.c_str()))
      return is_valid_id(file);
    
    return is_valid_id(file.substr(0, file.size()-ext.size()));
  }
  
  void validate()
  {
    bool ok= true;
    
    _error_label.set_text("");
    if (_lua_script.get_active())
    {
      if (!_filename.get_string_value().empty())
      {
        std::string fn= _filename.get_string_value();
        if (!g_str_has_suffix(fn.c_str(), ".lua"))
          fn.append(".lua");
        if (!is_valid_filename(fn, ".lua"))
        {
          ok= false;
          _error_label.set_text("Invalid filename");
        }
      }
    }
    else if (_python_script.get_active())
    {
      // python scripts must have a filename (otherwise we can't set breakpoints)
      //if (!_filename.get_string_value().empty())
      {
        std::string fn= _filename.get_string_value();
        if (!g_str_has_suffix(fn.c_str(), ".py"))
          fn.append(".py");
        if (!is_valid_filename(fn, ".py"))
        {
          ok= false;
          _error_label.set_text("Invalid filename");
        }
      }
    }
    else if (_python_module.get_active())
    {
      ok= false;
      if (!is_valid_id(_module_name.get_string_value()))
        _error_label.set_text("Module name is invalid");
      else if (!is_valid_id(_function_name.get_string_value()))
        _error_label.set_text("Function name is invalid");
      else if (!is_valid_filename(_module_file.get_string_value(), "_grt.py"))
      {
        std::string path= _module_file.get_string_value();
        if (g_str_has_suffix(path.c_str(), "_grt.py"))
          _error_label.set_text("File name must end with _grt.py");
        else
          _error_label.set_text("File name is invalid");
      }
      else
        ok= true;
    }
    else if (_python_plugin.get_active())
    {
      ok= false;
      if (!is_valid_id(_plugin_name.get_string_value()))
        _error_label.set_text("Plugin name is invalid");
      else if (!is_valid_filename(_plugin_file.get_string_value(), "_grt.py"))
      {
        std::string path= _plugin_file.get_string_value();
        if (g_str_has_suffix(path.c_str(), "_grt.py"))
          _error_label.set_text("File name must end with _grt.py");
        else
          _error_label.set_text("Plugin file name is invalid");        
      }
      else
        ok= true;
    }    
    _ok.set_enabled(ok);
  }
 
  void name_changed(TextEntry *entry)
  {
    std::string name= entry->get_string_value();
    std::string filename;

    if (entry == &_filename)
    {
      ;
    }
    else if (entry == &_module_name)
    {
      char *tmp= g_utf8_strdown(name.data(), name.length());
      filename.append(tmp).append("_grt.py");
      g_free(tmp);
      _module_file.set_value(filename);
    }
    else if (entry == &_plugin_name)
    {
      char *tmp= g_utf8_strdown(name.data(), name.length());
      filename.append(name).append("_grt.py");
      g_free(tmp);
      _plugin_file.set_value(filename);
    }

    validate();
  }
  
  
  void load_plugin_templates(const std::string &template_dir)
  {
    _plugin_template_list.clear();

    GDir *dir= g_dir_open(template_dir.c_str(), 0, NULL);
    if (dir)
    {
      const gchar *entry;
      while ((entry = g_dir_read_name(dir)))
      {
        FILE *f= base_fopen((template_dir+"/"+entry).c_str(), "r");
        if (f)
        {
          char line[1024];

          PluginTemplate t;
          
          if (fgets(line, sizeof(line), f))
          {
            if (strncmp(line, "##", 2) == 0)
            {
              t.name= g_strstrip(line+2);
              
              while (fgets(line, sizeof(line), f))
              {
                char *end= line+(strlen(line)-1);
                while (*end == '\n' || *end == '\r')
                {
                  *end= 0;
                  if (end == line)
                    break;
                  --end;
                }
                t.code.append(line).append("\n");
              }
            } 
          }
          if (!t.name.empty())
            _plugin_template_list.push_back(t);
          fclose(f);
        }
      }
      g_dir_close(dir);
    }
    
    std::sort(_plugin_template_list.begin(), _plugin_template_list.end());
    PluginTemplate t;
    t.name= "Empty File";
    _plugin_template_list.insert(_plugin_template_list.begin(), t);    
  }
  
  std::vector<PluginTemplate> _plugin_template_list;
  
  RadioButton _lua_script;
  RadioButton _python_script;
  RadioButton _python_module;
  RadioButton _python_plugin;
  Selector _plugin_template;
  Table _script_args;
  TextEntry _filename;

  TabView _tab;
  
  Table _module_args;
  TextEntry _module_name;
  TextEntry _function_name;
  TextEntry _module_file;

  Table _plugin_args;
  TextEntry _plugin_name;
  TextEntry _plugin_file;
  Label _error_label;
  Button _ok;
  Button _cancel;
};

#endif
