/* 
* Copyright (c) 2009, 2012 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 "db_alter_script.h"
#include "db_alter_script_be.h"
#include "wb_config.h"

#include "grtui/grt_wizard_plugin.h"
#include "grtui/wizard_view_text_page.h"

//#include "db_mysql_diff_reporting.h"
#include "../backend/db_plugin_be.h"
#include "base/string_utilities.h"
#include "mforms/fs_object_selector.h"

using namespace grtui;
using namespace mforms;
using namespace base;

#include "grtui/connection_page.h"
#include "schema_matching_page.h"
#include "fetch_schema_names_page.h"
#include "fetch_schema_contents_page.h"
#include "synchronize_differences_page.h"

#include <boost/lambda/bind.hpp>

class DescriptionPage : public WizardPage
{
private:
  mforms::Label _description_text;
  mforms::CheckBox _show_description_check;
  grt::Module *_module;
public:
  DescriptionPage(WizardForm *form, grt::Module *module)
    : WizardPage(form, "intro"), _module(module)
  {
    set_title(_("Introduction"));
    set_short_title(_("Introduction"));
    _description_text.set_wrap_text(true);
    _description_text.set_text("This wizard allows you to compare a target database or script "
                               "with the open model, external script or a second database and "
                               "apply these changes back to the target.\n"
                               "It's also possible to export the ALTER script generated to a "
                               "file for executing it afterwards.\n"
                               "The changes are applied one way only, to the target database "
                               "and the source is left untouched.");
    add(&_description_text,false,true);
    _show_description_check.set_text("Always show this page");
    _show_description_check.set_active(_module->global_int_data("show_sync_help_page",1));
    add_end(&_show_description_check,false, true);
  }

  virtual void leave(bool advancing)
  {
    if (advancing)
      _module->set_global_data("show_sync_help_page", _show_description_check.get_active());
  }
  
  virtual void enter(bool advancing)
  {
    if (advancing)
      if (!_module->global_int_data("show_sync_help_page",1))
        _form->go_to_next();
  }
};

class FileImportPage : public WizardProgressPage
{
public:
  virtual void enter(bool advancing)
  {
    if (advancing)
    {
      reset_tasks();
    }
    WizardProgressPage::enter(advancing);
  }

  FileImportPage(WizardForm *form,const std::string description,const std::string file_value_name,DbMySQLDiffAlter *be):
  WizardProgressPage(form,description), _be(be), _file_value_name(file_value_name)
  {
    set_title(_("Parse and Load Schemata From File"));
    set_short_title(_("Parse Script File"));
    add_task(_("Retrieve database objects from file"),
      boost::bind(&FileImportPage::perform_fetch, this),
      _("Retrieving object lists from selected file..."));

    end_adding_tasks(false,
      _("Retrieval Completed Successfully"));

    set_status_text("");
  };

  db_CatalogRef get_catalog()const{return _catalog;};
protected:

  bool perform_fetch()
  {
    std::string err;
    std::string path = values().get_string(_file_value_name);
    _catalog = _be->get_cat_from_file_or_tree(path,err);
    if (!err.empty())
      throw std::runtime_error(err.c_str());
    return true;
  }

  db_CatalogRef _catalog;
  DbMySQLDiffAlter *_be;
  std::string _file_value_name;

};

class AlterSourceSelectPage : public WizardPage
{
private:
  DataSourceSelector _left;
  DataSourceSelector _right;
  DataSourceSelector _result;

  void left_changed()
  {
    if (_left.model_radio->get_active())
      _right.model_radio->set_enabled(false);
    else
      _right.model_radio->set_enabled(true);

    _left.file_selector.set_enabled(_left.file_radio->get_active());
  }

  void right_changed()
  {
    if (_right.model_radio->get_active())
      _left.model_radio->set_enabled(false);
    else
      _left.model_radio->set_enabled(true);

    _right.file_selector.set_enabled(_right.file_radio->get_active());
    _result.server_radio->set_enabled(_right.server_radio->get_active());
    if (!_right.server_radio->get_active())
      _result.file_radio->set_active(true);
  }

  virtual bool advance()
  {
    const char *sources[]= {
      "model", "server", "file"
    };

    values().gset("left_source", sources[get_left_source()]);
    values().gset("right_source", sources[get_right_source()]);
    values().gset("result", get_result());
    values().gset("result_path", _result.file_selector.get_filename());
    values().gset("left_source_file", _left.file_selector.get_filename());
    values().gset("right_source_file", _right.file_selector.get_filename());

    _result.file_selector.get_filename();
    if ((get_result() == DataSourceSelector::FileSource) && (!_result.file_selector.check_and_confirm_file_overwrite()))
      return false;

    //TDOO: Show file not found dialog
    if (get_left_source() == DataSourceSelector::FileSource
      && !g_file_test(_left.file_selector.get_filename().c_str(), G_FILE_TEST_EXISTS))
      return false;

    if (get_right_source() == DataSourceSelector::FileSource
      && !g_file_test(_right.file_selector.get_filename().c_str(), G_FILE_TEST_EXISTS))
      return false;

    return true;
  }

public:
  AlterSourceSelectPage(WizardForm *form)
    : WizardPage(form, "source"), _result(true)
  {
    set_title(_("Select Databases for Updates"));
    set_short_title(_("Select Sources"));
    
    add(&_left.panel, false, false);
    add(&_right.panel, false, false);
    add(&_result.panel, false, false);

    _left.panel.set_title(_("Source – Database To Take Updates From"));

    _left.set_change_slot(boost::bind(&AlterSourceSelectPage::left_changed, this));
    _right.set_change_slot(boost::bind(&AlterSourceSelectPage::right_changed, this));

    _left.model_radio->set_active(true);
    _right.model_radio->set_enabled(false);
    _right.server_radio->set_active(true);

    _left.file_source_selected();
    _right.file_source_selected();

    _right.panel.set_title(_("Destination – Database To Receive Updates"));
    _result.panel.set_title(_("Send Updates To:"));
    _result.model_radio->show(false);
    _result.server_radio->set_text("Destination Database Server");
    _result.file_radio->set_text("ALTER Script File:");
    _result.server_radio->set_active(true);
  }

  DataSourceSelector::SourceType get_left_source() { return _left.get_source(); }
  DataSourceSelector::SourceType get_right_source() { return _right.get_source(); }
  DataSourceSelector::SourceType get_result() { return _result.get_source(); }
};


class AlterViewResultPage : public ViewTextPage
{
public:
  AlterViewResultPage(WizardForm *form)
    : ViewTextPage(form, "viewdiff", (ViewTextPage::Buttons)(ViewTextPage::SaveButton|ViewTextPage::CopyButton), "SQL Files (*.sql)|*.sql")
  {
    set_short_title(_("Detected Changes"));
    set_title(_("Detected Changes to be Applied to Destination"));

  }

  void set_generate_text_slot(const boost::function<std::string()> &slot)
  {
    _generate= slot;
  }

  virtual void enter(bool advancing)
  {
    if (advancing)
    {
      std::string sql = _generate();
      _text.set_value(sql);
      values().gset("script",sql);
    }
  }

  virtual bool advance()
  {
    if (values().get_int("result") == DataSourceSelector::FileSource)
    {
      std::string path = values().get_string("result_path");
      if (!path.empty())
        save_text_to(path);
    }
    return true;  
  }

  virtual std::string next_button_caption()
  {
    return execute_caption();
  }

  virtual bool allow_cancel() const  { return false; }
  virtual bool next_closes_wizard() { return values().get_int("result") != DataSourceSelector::ServerSource; }

protected:  
  boost::function<std::string()> _generate;
};


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

class AlterApplyProgressPage : public WizardProgressPage
{
  bool _finished;
  Db_plugin *_dbplugin;

public:
  AlterApplyProgressPage(WizardForm *form) : WizardProgressPage(form, "apply_progress")
  {
    set_title(_("Applying Alter Progress"));
    set_short_title(_("Alter Progress"));

    add_async_task(_("Connect to DBMS"),
      boost::bind(&AlterApplyProgressPage::do_connect, this),
      _("Connecting to DBMS..."));

    TaskRow *task= add_async_task(_("Execute Alter Script"),
      boost::bind(&AlterApplyProgressPage::do_export, this),
      _("Applying Alter engineered SQL script in DBMS..."));
    task->process_finish= boost::bind(&AlterApplyProgressPage::export_finished, this, _1);

    end_adding_tasks(false,
      _("Applying Alter Finished Successfully"));

    set_status_text("");
  }


  virtual void enter(bool advancing)
  {
    _finished= false;

    if (advancing)
      reset_tasks();

    WizardProgressPage::enter(advancing);
  }


  virtual bool allow_back()
  {
    return WizardProgressPage::allow_back() && !_finished;
  }

  virtual bool allow_cancel()
  {
    return WizardProgressPage::allow_cancel() && !_finished;
  }


  bool do_connect()
  {
    execute_grt_task(boost::bind(boost::function<grt::ValueRef (bool)> (boost::lambda::constant(grt::ValueRef())),
        boost::bind(&DbConnection::test_connection,_dbplugin->db_conn())), false);
    return true;
  }  

  bool do_export()
  {
    _dbplugin->sql_script(values().get_string("script"));
    execute_grt_task(boost::bind(&Db_plugin::apply_script_to_db, _dbplugin, _1), false);

    return true;
  }


  void export_finished(const grt::ValueRef &result)
  {
    _finished= true;
  }

  virtual bool next_closes_wizard()
  {
    return true;
  }

  void set_db_plugin(Db_plugin *pl)
  {
    _dbplugin= pl;
  }

};

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

class AlterScriptSynchronizeDifferencesPage : public SynchronizeDifferencesPage
{
public:
  AlterScriptSynchronizeDifferencesPage(WizardForm *form, DbMySQLDiffAlter *be):SynchronizeDifferencesPage(form,be)
  {
    _update_model.show(false);
    _update_source.set_text(_("Update Destination"));
    _update_source.set_tooltip(_("Update the database/script with changes detected in the source."));
    _heading.set_text("Double click arrows in the list to choose whether to ignore changes or update destination DB");
    _update_model.set_text(_("Source Database"));
    _update_model.set_tooltip(_("Source Database with detected changes."));
    _skip.set_text(_("Ignore"));
    _skip.set_tooltip(_("Ignore the change."));
    _update_source.set_text(_("Update Destination"));
    _update_source.set_tooltip(_("Update the database/script with changes."));
  };
};

class WbPluginDiffAlter : public WizardPlugin
{
public:
  WbPluginDiffAlter(grt::Module *module)
    : WizardPlugin(module), _be(grtm())
  {
    add_page(mforms::manage(_description_page= new DescriptionPage(this,module)));
    add_page(mforms::manage(_source_page= new AlterSourceSelectPage(this)));

    Db_plugin *dbplugin[2];
    _left_db.grtm(grtm(), true);
    _right_db.grtm(grtm(), false);

    dbplugin[0]= &_left_db;
    dbplugin[1]= &_right_db;

    const char *title_prefix[2];
    title_prefix[0]= _("Source Database: ");
    title_prefix[1]= _("Destination Database: ");

    FileImportPage* file_pages[] = {_import_source_file,_import_destination_file};
    std::string file_page_names[] = {"import_src_file","import_dst_file"};
    std::string file_var_names[] = {"left_source_file","right_source_file"};

    for (int i= 0; i < 2; i++)
    {
      ConnectionPage *connect;
      add_page(mforms::manage(connect= new ConnectionPage(this, strfmt("connect%i", i).c_str())));
      connect->set_db_connection(dbplugin[i]->db_conn());
      connect->set_title(std::string(title_prefix[i]).append(connect->get_title()));

      FetchSchemaNamesProgressPage *fetch_names_page;
      add_page(mforms::manage(fetch_names_page= new FetchSchemaNamesProgressPage(this, strfmt("fetchNames%i",i).c_str())));
      fetch_names_page->set_db_connection(dbplugin[i]->db_conn());
      fetch_names_page->set_load_schemata_slot(boost::bind(&WbPluginDiffAlter::load_schemata, this, dbplugin[i]));
      fetch_names_page->set_title(std::string(title_prefix[i]).append(fetch_names_page->get_title()));

      SchemaMatchingPage *schema_page;
      add_page(mforms::manage(schema_page= new SchemaMatchingPage(this, strfmt("pickSchemata%i", i).c_str(), "Model Schema", "Script Schema")));
      schema_page->set_db_plugin(dbplugin[i]);
      schema_page->set_title(std::string(title_prefix[i]).append(schema_page->get_title()));

      FetchSchemaContentsProgressPage *fetch_schema_page;
      add_page(mforms::manage(fetch_schema_page= new FetchSchemaContentsProgressPage(this, strfmt("fetchSchema%i",i).c_str())));
      fetch_schema_page->set_db_plugin(dbplugin[i]);
      fetch_schema_page->set_title(std::string(title_prefix[i]).append(fetch_schema_page->get_title()));

      add_page(mforms::manage(file_pages[i] = new FileImportPage(this,file_page_names[i],file_var_names[i],&_be)));
    }
    _import_source_file = file_pages[0];
    _import_destination_file = file_pages[1];

    _diffs_page= new AlterScriptSynchronizeDifferencesPage(this, &_be);
    _diffs_page->set_title(_("Differences Found"));
    add_page(mforms::manage(_diffs_page));

    AlterViewResultPage *page;
    add_page(mforms::manage(page= new AlterViewResultPage(this)));
    page->set_generate_text_slot(boost::bind(&WbPluginDiffAlter::generate_alter, this));
    add_page(mforms::manage(_apply_page = new AlterApplyProgressPage(this)));
    _apply_page->set_db_plugin(dbplugin[1]);

    set_title(_("Synchronize With Any Source"));
  }

  std::string generate_alter()
  {
    std::string report;
    try
    {
      report = _be.generate_alter();
    }
    catch (const std::exception &exc)
    {
      report = base::strfmt("Error generating alter script: %s", exc.what());
    }
    return report;
  }


  virtual WizardPage *get_next_page(WizardPage *current)
  {
    std::string curid= current ? current->get_id() : "";
    std::string nextid;

    if (curid == "source")
    {
      if (_source_page->get_left_source() == DataSourceSelector::ServerSource)
        nextid= "connect0";
      else if (_source_page->get_left_source() == DataSourceSelector::FileSource)
        nextid = "import_src_file";
      else if (_source_page->get_left_source() == DataSourceSelector::ModelSource)
        nextid= _source_page->get_right_source() == DataSourceSelector::ServerSource? "connect1":"import_dst_file";
      else if (_source_page->get_right_source() == DataSourceSelector::ServerSource)
        nextid= "connect1";
      else
        nextid= "diffs";
    }
    else if ((curid == "fetchSchema0") || (curid == "import_src_file"))
    {
      if (_source_page->get_right_source() == DataSourceSelector::ServerSource)
        nextid= "connect1";
      else if (_source_page->get_right_source() == DataSourceSelector::FileSource)
        nextid= "import_dst_file";
      else
        nextid= "diffs";
    }else if (curid == "viewdiff")
      nextid= "apply_progress";
    else if (curid == "fetchSchema1")
      nextid= "diffs";

    if (nextid.empty())
      nextid = WizardForm::get_next_page(current)->get_id();

    if (nextid == "diffs")
    {
      db_CatalogRef left_catalog, right_catalog;
      std::string left_file, right_file;

      if (_source_page->get_left_source() == DataSourceSelector::ServerSource)
        left_catalog= _left_db.db_catalog();
      else if (_source_page->get_left_source() == DataSourceSelector::FileSource)
        left_catalog = _import_source_file->get_catalog();
      else if (_source_page->get_left_source() == DataSourceSelector::ModelSource)
        left_catalog = _be.get_model_catalog();

      if (_source_page->get_right_source() == DataSourceSelector::ServerSource)
        right_catalog = _right_db.db_catalog();
      else if (_source_page->get_right_source() == DataSourceSelector::FileSource)
        right_catalog = _import_destination_file->get_catalog();
      else if (_source_page->get_right_source() == DataSourceSelector::ModelSource)
        right_catalog = _be.get_model_catalog();

      _diffs_page->set_src(right_catalog);
      _diffs_page->set_dst(left_catalog);
    }
    return get_page_with_id(nextid);
  }


protected:
  DbMySQLDiffAlter _be;
  Db_plugin _left_db;
  Db_plugin _right_db;
  DescriptionPage* _description_page;
  FileImportPage* _import_source_file;
  FileImportPage* _import_destination_file;
  AlterSourceSelectPage *_source_page;
  AlterScriptSynchronizeDifferencesPage *_diffs_page;
  AlterApplyProgressPage* _apply_page;
  bool connect1_apply;

  std::vector<std::string> load_schemata(Db_plugin *db)
  {
    std::vector<std::string> names;
    db->load_schemata(names);
    _be.set_db_options(db->load_db_options());
    return names;
  }
};


WizardPlugin *createWbPluginDiffAlter(grt::Module *module, db_CatalogRef catalog)
{
  return new WbPluginDiffAlter(module);
}

void deleteWbPluginDiffAlter(WizardPlugin *plugin)
{
    delete plugin;
}
