/* 
 * Copyright (c) 2012, 2013, 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
 */
#include "stdafx.h"

#include <pcre.h>

#include "base/string_utilities.h"
#include "base/util_functions.h"
#include "base/log.h"
#include "base/ui_form.h"

#include "mforms/label.h"
#include "mforms/table.h"
#include "mforms/uistyle.h"
#include "mforms/panel.h"

#include "grtpp.h"

#include "grt/editor_base.h"
#include "grts/structs.h"
#include "grts/structs.app.h"

#include "workbench/wb_context.h"
#include "workbench/wb_context_ui.h"

#include "bug_report_form.h"

#define MYSQL_COM_SERVICE "mysql.com credentials"
#define BUG_REPORT_USER_SETTING "WBBugReport:username"
#define BUG_REPORT_USER_NAME "WBBugReportUser"

DEFAULT_LOG_DOMAIN("WB Bug Report")
//--------------------------------------------------------------------------------------------------

static mforms::Label *new_label(const std::string &text, bool right_align=false, bool help=false)
{
  mforms::Label *label= mforms::manage(new mforms::Label());
  label->set_text(text);
  if (right_align)
    label->set_text_align(mforms::MiddleRight);
  if (help)
    label->set_style(mforms::SmallHelpTextStyle);
  return label;
}

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

BugReportForm::BugReportForm(wb::WBContextUI *wbui, const std::string& sys_info, const std::string &error_info):
Form(NULL, mforms::FormResizable), _top_box(false), 
  _credentials_box(false), _user_box(true), _email(mforms::NormalEntry), _password(mforms::PasswordEntry), _description(mforms::BothScrollBars), 
  _how_to_repeat(mforms::BothScrollBars), _dev_notes(mforms::BothScrollBars), _button_box(true),
  _stored_user(""), _stored_password(""), _logged_in(false)
{
  _wbui= wbui;

  _sys_info = sys_info;

  _error_info = error_info;

  set_title(_("Bug Report"));
  set_name("bug_report");

  set_content(&_top_box);

  _top_box.set_padding(MF_WINDOW_PADDING);
  _top_box.set_spacing(MF_TABLE_ROW_SPACING);

  create_credentials_box();

  create_details_box();

  create_text_section(_("Description"), &_description);
  mforms::Box* box = create_text_section(_("How To Repeat"), &_how_to_repeat);
  box->add(new_label(_("It is very important that repeat instructions are clear and detailed as the developers need to reproduce the bug to be able to fix it"), false, true), false);
  create_text_section(_("Notes to Developer"), &_dev_notes);

  _private_dev_notes.set_text(_("Make notes to developer private"));
  _private_dev_notes.set_active(false);
  _top_box.add(&_private_dev_notes, false);

  _include_log.set_text(_("Attach Workbench log file to bug report (File will not be public)"));
  _include_log.set_active(true);
  _top_box.add(&_include_log, false);

  _dev_notes.append_text(_sys_info);

  if (_error_info != "from_menu")
  {
    _dev_notes.append_text("\n\n");
    _dev_notes.append_text(_error_info);
  }

  _top_box.add_end(&_button_box, false, true);

  _button_box.set_spacing(MF_BUTTON_SPACING);

  scoped_connect(_report_button.signal_clicked(),boost::bind(&BugReportForm::report_clicked, this));
  scoped_connect(_sign_up_button.signal_clicked(),boost::bind(&BugReportForm::sign_up_clicked, this));
  scoped_connect(this->signal_closed(),boost::bind(&BugReportForm::form_closed, this));

  _cancel_button.set_text(_("Cancel"));
  _cancel_button.enable_internal_padding(true);

  _report_button.set_text(_("Report"));
  _report_button.enable_internal_padding(true);
  
  mforms::Utilities::add_end_ok_cancel_buttons(&_button_box, &_report_button, &_cancel_button);
  
  parse_sys_info();

  _os = "1";
  _really = "0";

  center();
}

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

BugReportForm::~BugReportForm(void)
{
}

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

void BugReportForm::create_credentials_box()
{
  mforms::Panel *credentials_frame = mforms::manage(new mforms::Panel(mforms::TitledBoxPanel));
  credentials_frame->set_title(_("mysql.com Login"));

  _top_box.add(credentials_frame, false);

  credentials_frame->add(&_credentials_box);

  _credentials_box.set_padding(MF_PANEL_SMALL_PADDING);

  _credentials_box.add(new_label(_("Enter your mysql.com login details:"), false), false, true);
  
  _user_box.set_padding(MF_WINDOW_PADDING);
  _user_box.set_spacing(MF_TABLE_COLUMN_SPACING);

  _user_box.add(new_label(_("Email Address :"), true), false);
  _user_box.add(&_email, true, true);

  _user_box.add(new_label(_("Password :"), true), false);
  _user_box.add(&_password, true, true);

  _stored_user = _wbui->get_wb()->get_grt_manager()->get_app_option_string(BUG_REPORT_USER_SETTING);

  _remember_password.set_text(_("Remember password"));
  mforms::Utilities::find_password(MYSQL_COM_SERVICE, BUG_REPORT_USER_NAME, _stored_password);

  if (_stored_password.length() > 1)
    _remember_password.set_active(true);

  _user_box.add(&_remember_password, false, true);  
  _credentials_box.add(&_user_box, false);

  _email.set_value(_stored_user);
  _password.set_value(_stored_password);
  
  mforms::Box *box = mforms::manage(new mforms::Box(true));
  box->set_spacing(MF_BUTTON_SPACING);

  _sign_up_button.set_text(_("Sign Up"));
  _sign_up_button.enable_internal_padding(true);

  box->add(new_label(_("OR if you don't have an account,"), false, true), false);
  box->add(&_sign_up_button, false);
  box->add(new_label(_("at mysql.com to submit bug reports, follow them up and more"), false, true), false);

  _credentials_box.add(box, false, true);
}

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

void BugReportForm::create_details_box()
{
  mforms::Panel *details_frame= mforms::manage(new mforms::Panel(mforms::TitledBoxPanel));
  details_frame->set_title(_("Report Details"));

  _top_box.add(details_frame, false);


  mforms::Table *table= mforms::manage(new mforms::Table());

  table->set_padding(MF_PANEL_SMALL_PADDING);
  table->set_column_spacing(MF_TABLE_COLUMN_SPACING);
  table->set_row_spacing(MF_TABLE_ROW_SPACING);
    
  table->set_column_count(4);
  table->set_row_count(2);

  table->add(new_label(_("Synopsis :"), true), 0, 1, 0, 1, mforms::HFillFlag);
  table->add(&_synopsis, 1, 4, 0, 1, mforms::HFillFlag|mforms::HExpandFlag);

  _category.add_item("*Please select*");
  _category.add_item("MySQL Workbench");
  _category.add_item("MySQL Workbench: Administration");
  _category.add_item("MySQL Workbench: SQL Editor");
  _category.add_item("MySQL Workbench: Modeling");
  _category.add_item("MySQL Workbench: MySQL Utilities");
  _category.add_item("MySQL Workbench: Migration");
  _category.add_item("MySQL Workbench: Docs");

  table->add(new_label(_("Category :"), true), 0, 1, 1, 2, mforms::HFillFlag);
  table->add(&_category, 1, 2, 1, 2, mforms::HFillFlag|mforms::HExpandFlag);

  _severity.add_item("S5 (Performance)");
  _severity.add_item("S4 (Feature Request)");
  _severity.add_item("S3 (Non-Critical)");
  _severity.add_item("S2 (Serious)");
  _severity.add_item("S1 (Critical)");
  _severity.set_selected(2);

  table->add(new_label(_("Severity :"), true), 2, 3, 1, 2, mforms::HFillFlag|mforms::HExpandFlag);
  table->add(&_severity, 3, 4, 1, 2, mforms::HFillFlag|mforms::HExpandFlag);
  
  details_frame->add(table);
}

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

mforms::Box *BugReportForm::create_text_section(const std::string& label, mforms::TextBox* text_box)
{
  mforms::Box *box = mforms::manage(new mforms::Box(false));
  mforms::Panel *frame= mforms::manage(new mforms::Panel(mforms::TitledBoxPanel));
  frame->set_title(label);

  box->set_padding(MF_TEXT_SECTION_PADDING, MF_TEXT_SECTION_PADDING, MF_TEXT_SECTION_PADDING, MF_TEXT_SECTION_PADDING);

#ifdef _WIN32
  text_box->set_size(600, 30);
#endif
  box->add(text_box, true, true);

  frame->add(box);

  _top_box.add(frame, true, true);

  // Returns the box that contains the controls
  return box;
}

//--------------------------------------------------------------------------------------------------
void BugReportForm::parse_sys_info()
{
  _sys_info_list = base::split(_sys_info, "\n");

  // Retrieves the WB Version
  const char *errptr;
  int erroffs=0;
  const char *pattern = "\\s*MySQL Workbench.*for\\s(.*)\\sversion\\s(\\d+\\.\\d+\\.\\d+).*";
  int patres[64];
  const char *value;

  pcre *patre= pcre_compile(pattern, 0, &errptr, &erroffs, NULL);
  if (!patre)
    throw std::logic_error("error compiling regex "+std::string(errptr));

  int rc = pcre_exec(patre, NULL, _sys_info_list[0].c_str(), _sys_info_list[0].length(), 0, 0, patres, sizeof(patres)/sizeof(int));
  if ( rc > 0 )
  {
    pcre_get_substring(_sys_info_list[0].c_str(), patres, rc, 1, &value);
    
    std::string os(value);
    pcre_free_substring(value);

    if (os == "Linux/Unix")    _os = "5";
    else if (os == "Mac OS X") _os = "6";
    else if (os == "Windows")  _os = "7"; 

    pcre_get_substring(_sys_info_list[0].c_str(), patres, rc, 2, &value);
    _version = value;
    pcre_free_substring(value);
  }

  bool found = false;

  for(size_t index = 0; !found && index < _sys_info_list.size(); index++)
  {
    if (base::starts_with(_sys_info_list[index], "OS:"))
    {
      found = true;
      _os_details = _sys_info_list[index].substr(4);
    }
  }


  pcre_free(patre);
  patre = NULL;
  value = "";
}

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

void BugReportForm::sign_up_clicked()
{
  mforms::Utilities::open_url("http://www.mysql.com/register/");
}

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

void BugReportForm::form_closed()
{
  if ((_email.get_string_value() != _stored_user || _password.get_string_value() != _stored_password))
  {
    std::string new_password = (_remember_password.get_active() && _logged_in) ? _password.get_string_value() : "";

    _wbui->get_wb()->get_grt_manager()->set_app_option(BUG_REPORT_USER_SETTING, grt::StringRef(_email.get_string_value()));
    mforms::Utilities::store_password(MYSQL_COM_SERVICE, BUG_REPORT_USER_NAME, new_password);
  }
  else if( _stored_password.length() > 0 && !_remember_password.get_active())
    mforms::Utilities::store_password(MYSQL_COM_SERVICE, BUG_REPORT_USER_NAME, "");
}

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

  void BugReportForm::report_clicked()
{
  std::string error = validate_fields();
  int return_code = -1;

  if (error.length() > 0)
    mforms::Utilities::show_error(_("Incomplete Bug Report Information"), error, _("OK"));
  else
  {
    grt::Module *module = _wbui->get_wb()->get_grt()->get_module("WbBugReport");

    if (module == NULL)
      throw std::logic_error("Internal error: can't find Workbench module.");

    // Fills a map with all the information needed for the bug submission.
    grt::DictRef data(_wbui->get_wb()->get_grt());
    data.set("in[sdesc]",grt::StringRef(_synopsis.get_string_value()));
    data.set("in[bug_type]",grt::StringRef(_category.get_string_value()));
    data.set("in[severity]",grt::StringRef(grt::IntegerRef(5 - _severity.get_selected_index()).repr()));
    data.set("in[status]",grt::StringRef("Open"));
    data.set("in[php_version]",grt::StringRef(_version));
    data.set("in[os]",grt::StringRef(_os));
    data.set("in[os_details]",grt::StringRef(_os_details));
    data.set("in[tags]",grt::StringRef("WBBugReporter"));
    data.set("in[assign]",grt::StringRef(""));
    data.set("in[target]",grt::StringRef(""));
    data.set("in[verifier]",grt::StringRef(""));
    data.set("in[affectedissues]",grt::StringRef(""));
    data.set("in[reviewer]",grt::StringRef(""));
    data.set("in[reviewer2]",grt::StringRef(""));
    data.set("in[lead]",grt::StringRef(""));
    data.set("in[eta]",grt::StringRef(""));
    data.set("in[howtorepeat]",grt::StringRef(_how_to_repeat.get_string_value()));
    data.set("in[suggestedfix]",grt::StringRef(""));
    data.set("in[really]",grt::StringRef(_really));

    if (_include_log.get_active())
      data.set("log_file", grt::StringRef(base::Logger::log_filename()));
      
    if (_private_dev_notes.get_active())
    {
      data.set("in[ldesc]",grt::StringRef(_description.get_string_value()));
      data.set("in[internalinfo]",grt::StringRef(_dev_notes.get_string_value()));
    }
    else
    {
      data.set("in[ldesc]",grt::StringRef(_description.get_string_value() + 
                                          "\n\n\n----- Developer Notes -----\n\n" + 
                                          _dev_notes.get_string_value()));
      data.set("in[internalinfo]",grt::StringRef(""));
    }

    // Sets the parameters for the python plugin.
    grt::BaseListRef args(_wbui->get_wb()->get_grt());
    args.ginsert(grt::StringRef(_email.get_string_value()));
    args.ginsert(grt::StringRef(_password.get_string_value()));
    args.ginsert(data);

    // Calls the plugin
    std::string result;
    try
    {
      grt::ValueRef resultRef = module->call_function("submitBug", args);

      // Gets the information returned by the plugin
      result = resultRef.repr();
    }
    catch(std::exception &e)
    {
      result = _("error|An error occurred while submitting the report.\n Please verify the proper submition by logging in at http://bugs.mysql.com");
      log_error("An error occurred while submitting the report: %s", e.what());
    }

    // The plugin will return the information as follows:
    //
    //        status|data
    // Where status is one of success, warning or error
    // The content of data will vary depending on the status
    std::vector<std::string> result_info = base::split(result, "|");
    _logged_in = true;

    if (result_info[0] == "success")
    {
      std::vector<std::string> result_details = base::split(result_info[1],"\n");

      std::string message = _("Successfully created bug ");

      if (result_details[0] == "no_log_submitted")
        message += result_details[2] + " : " + result_details[3] + "\nHowever there was a problem uploading the log file.\nPlease click on the Go to Bug Report button below to submit the log into the Bug Report";
      else
        message += result_details[1] + " : " + result_details[2] + "\nTo follow up click on the Go to Bug Report button below";

      return_code = mforms::Utilities::show_message(_("Bug Report Confirmation"), message, _("Go to Bug Report"), _("OK"));
      
      if (return_code == 1)
      {
        std::string bug_number = "";
        for(size_t index = 0; index < result_details.size() && bug_number == ""; index++)
        {
          if (result_details[index][0] == '#')
            bug_number = result_details[index].substr(1);
        }
        mforms::Utilities::open_url("http://bugs.mysql.com/bug.php?id=" + bug_number);
      
      }
      
      mforms::Form::end_modal(true);
    }
    else if (result_info[0] == "warning")
    {
      std::string message = result_info[1];
      std::vector<std::string> duplicates;

      // Parses the specific case where the warning is caused due to a possible duplicate
      if (result_info[1].find("Are you sure that you searched before you submitted your bug report?") != std::string::npos)
      {
        std::vector<std::string> bug_data = base::split (result_info[1], "\n");

        // Creates the bug lines of the duplicate candidates found
        std::string bug_line = "";
        message = bug_data[0] + "\n\n";
        for(size_t index = 1; index < bug_data.size() - 1; index++)
        {
          if (bug_data[index].find("Bug #") == 0)
          {
            // Inserts the bug id into the duplicates array.
            duplicates.push_back(bug_data[index].substr(5));
            
            message += "\n" + bug_line;
            bug_line = bug_data[index];
          }
          else
            bug_line += bug_data[index];
        }

        message += "\n" + bug_line;

        message += "\n\nFor more details on the possible duplicate bugs click on View Possible Duplicates button below. If you're sure that your report is a genuine bug that has not been reported before, close this message and click the Report button again to really submit this bug.";
        _really = "1";

        return_code = mforms::Utilities::show_message(_("Bug Report Warning"), message, _("View Possible Duplicates"), _("OK"));

        if (return_code == 1 && duplicates.size() > 0)
        {
          for(size_t index = 0; index < duplicates.size(); index++)
            mforms::Utilities::open_url("http://bugs.mysql.com/" + duplicates[index]);
        }
      }
      else
        mforms::Utilities::show_warning(_("Bug Report Warning"), message, _("OK"));
      
    }
    else
    {
      _logged_in = (result_info[1] != "Error accessing the bug system, please verify your email and password");
       mforms::Utilities::show_error(_("Bug Report Error"), result_info[1], _("OK"));
    }
  }
}

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

void BugReportForm::show()
{
  run_modal(NULL, &_cancel_button);
}

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

std::string BugReportForm::validate_fields()
{
  std::vector<std::string> errors;
  std::string error = "";

  if (base::trim(_email.get_string_value()).length() == 0)
    errors.push_back(_("The bug system user (email) must be provided"));

  if (base::trim(_email.get_string_value()).length() == 0)
    errors.push_back(_("The bug system password must be provided"));

  if (base::trim(_synopsis.get_string_value()).length() == 0)
    errors.push_back(_("A synopsis (short description) of the bug must be provided"));

  if (_category.get_selected_index() == 0)
    errors.push_back(_("A category for the bug must be selected"));

  if (base::trim(_description.get_string_value()).length() == 0)
    errors.push_back(_("A description of the bug must be provided"));

  if (base::trim(_how_to_repeat.get_string_value()).length() == 0)
    errors.push_back(_("A procedure to repeat the bug must be provided"));

  if (errors.size())
  {
    error = _("In order to successfully submit the bug report all required information must be"
      "provided. The following information is still missing:\n");

    for(size_t index = 0; index < errors.size(); index++)
      error += "\n" + errors[index];
  }

  return error;
}
