/*
  Copyright (c) 2015, 2021, Oracle and/or its affiliates.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License, version 2.0,
  as published by the Free Software Foundation.

  This program is also distributed with certain software (including
  but not limited to OpenSSL) that is licensed under separate terms,
  as designated in a particular file or component or in included license
  documentation.  The authors of MySQL hereby grant you an additional
  permission to link the program and your derivative works with the
  separately licensed software that they have included with MySQL.

  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, version 2.0, 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 "mysqldump_tool_chain_maker_options.h"
#include <boost/algorithm/string.hpp>

using namespace Mysql::Tools::Dump;

bool Mysql::Tools::Dump::use_show_create_user;

void Mysqldump_tool_chain_maker_options::parallel_schemas_callback(char*)
{
  std::vector<std::string> schemas;
  boost::split(schemas, m_parallel_schemas_string.value(),
    boost::is_any_of(","), boost::token_compress_on);
  if (schemas.size() == 0)
    return;
  int threads= 0;
  std::string& first_part= schemas[0];
  size_t i;
  for (i=0; i < first_part.size() && first_part[i] >= '0'
    && first_part[i] <= '9'; ++i)
  {
    threads= threads * 10 + first_part[i] - '0';
  }
  if (i < first_part.size() && first_part[i] == ':')
  {
    first_part= first_part.substr(i + 1);
  }
  else
  {
    threads= 0;
  }
  if (threads)
    m_parallel_thread_count+= threads;
  else
    m_def_thread_count++;
  m_last_parallel_schemas_queue_id++;
  m_object_queue_threads.insert(std::make_pair(
    m_last_parallel_schemas_queue_id, threads));

  for (std::vector<std::string>::iterator it= schemas.begin();
    it != schemas.end(); ++it)
  {
    m_database_to_object_queue_id.insert(std::make_pair(
      *it, m_last_parallel_schemas_queue_id));
  }
}

bool Mysqldump_tool_chain_maker_options::is_object_included_in_dump(
  Abstract_data_object* object)
{
  return m_object_filter.is_object_included_in_dump(object);
}

int Mysqldump_tool_chain_maker_options::get_object_queue_threads_count(
  int object_queue_id)
{
  std::map<int, int>::iterator it=
    m_object_queue_threads.find(object_queue_id);
  if (it != m_object_queue_threads.end() && it->second != 0)
  {
    return it->second;
  }
  return m_default_parallelism;
}

int Mysqldump_tool_chain_maker_options::get_object_queue_id_for_schema(
  const std::string& schema)
{
  std::map<std::string, int>::iterator it=
    m_database_to_object_queue_id.find(schema);
  if (it != m_database_to_object_queue_id.end())
  {
    return it->second;
  }
  return 0;
}

void Mysqldump_tool_chain_maker_options::process_positional_options(
  std::vector<std::string> positional_options)
{
  if ((m_dump_all_databases ? 1 : 0) + (m_dump_selected_databases ? 1 : 0) > 1)
  {
    m_mysql_chain_element_options->get_program()->error(
      Mysql::Tools::Base::Message_data(1, "Usage of --all-databases and "
      "--databases are mutually  exclusive.",
      Mysql::Tools::Base::Message_type_error));
  }
  else if (m_dump_all_databases
    || (!m_dump_selected_databases && positional_options.size() == 0))
  {
    if (positional_options.size() > 0)
    {
      m_mysql_chain_element_options->get_program()->error(
        Mysql::Tools::Base::Message_data(1, "Positional options specified, "
        "while disalowed by usage of --all-databases.",
        Mysql::Tools::Base::Message_type_error));
    }
  }
  else if (m_dump_selected_databases)
  {
    if (positional_options.size() < 1)
    {
      m_mysql_chain_element_options->get_program()->error(
        Mysql::Tools::Base::Message_data(1, "No positional options "
        "specified, while expected by usage of --databases.",
        Mysql::Tools::Base::Message_type_error));
    }
    else
    {
      for (int i= positional_options.size(); --i >= 0;)
        m_object_filter.m_databases_included.push_back(
        std::make_pair("", positional_options[i]));
    }
  }
  else
  {
    std::string db_name= positional_options[0];

    m_object_filter.m_databases_included.push_back(std::make_pair(
      "", db_name));
    for (int i= positional_options.size(); --i >= 1;)
      m_object_filter.m_tables_included.push_back(std::make_pair(
      db_name, positional_options[i]));
  }

  /*
    We add standard exclusions only if objects are included by default, i.e.
    there are exclusions or there is no exclusions and inclusions.
  */
  if (m_object_filter.m_tables_excluded.size() > 0 ||
    m_object_filter.m_tables_included.size() == 0)
  {
    m_object_filter.m_tables_excluded.push_back(std::make_pair(
      "mysql", "apply_status"));
    m_object_filter.m_tables_excluded.push_back(std::make_pair(
      "mysql", "schema"));
    m_object_filter.m_tables_excluded.push_back(std::make_pair(
      "mysql", "general_log"));
    m_object_filter.m_tables_excluded.push_back(std::make_pair(
      "mysql", "slow_log"));
    m_object_filter.m_tables_excluded.push_back(std::make_pair(
      "mysql", "slave_master_info"));
    m_object_filter.m_tables_excluded.push_back(std::make_pair(
      "mysql", "slave_relay_log_info"));
    /*
      We filter out all the tables which store account and privilge
      information. ex: mysql.user, mysql.db, mysql.tables_priv,
      mysql.columns_priv, mysql.procs_priv, mysql.proxies_priv
    */
    if (use_show_create_user) {
      m_object_filter.m_tables_excluded.push_back(std::make_pair(
        "mysql", "user"));
      m_object_filter.m_tables_excluded.push_back(std::make_pair(
        "mysql", "db"));
      m_object_filter.m_tables_excluded.push_back(std::make_pair(
        "mysql", "tables_priv"));
      m_object_filter.m_tables_excluded.push_back(std::make_pair(
        "mysql", "columns_priv"));
      m_object_filter.m_tables_excluded.push_back(std::make_pair(
        "mysql", "procs_priv"));
      m_object_filter.m_tables_excluded.push_back(std::make_pair(
        "mysql", "proxies_priv"));
    }
    /*
      Since we dump CREATE EVENT/FUNCTION/PROCEDURE statement skip this table.
    */
    m_object_filter.m_tables_excluded.push_back(std::make_pair(
      "mysql", "event"));
    m_object_filter.m_tables_excluded.push_back(std::make_pair(
      "mysql", "proc"));
  }
  if (m_object_filter.m_databases_excluded.size() > 0 ||
    m_object_filter.m_databases_included.size() == 0)
  {
    m_object_filter.m_databases_excluded.push_back(std::make_pair(
      "", INFORMATION_SCHEMA_DB_NAME));
    m_object_filter.m_databases_excluded.push_back(std::make_pair(
      "", PERFORMANCE_SCHEMA_DB_NAME));
    m_object_filter.m_databases_excluded.push_back(std::make_pair(
      "", "ndbinfo"));
    m_object_filter.m_databases_excluded.push_back(std::make_pair(
      "", "sys"));
  }
}

void Mysqldump_tool_chain_maker_options::create_options()
{
  this->create_new_option(&m_dump_all_databases, "all-databases",
    "Dump all databases. This is default behaviour if no positional options "
    "are specified. Specifying this option is mutually exclusive with "
    "--databases.")
    ->set_short_character('A');
  this->create_new_option(&m_dump_selected_databases, "databases",
    "Dump selected databases, specified in positional options. Specifying "
    "this option is mutually exclusive with --all-databases.")
    ->set_short_character('B');
  this->create_new_option(&m_parallel_schemas_string, "parallel-schemas",
    "[N:]<list of: schema_name separated with ','>. Process tables in "
    "specified schemas using separate queue handled by "
    "--default-parallelism threads or N threads, if N is specified. Can be "
    "used multiple times to specify more parallel processes.")
    ->add_callback(new Mysql::Instance_callback
    <void, char*, Mysqldump_tool_chain_maker_options>(
    this, &Mysqldump_tool_chain_maker_options::parallel_schemas_callback));
  this->create_new_option(&m_default_parallelism, "default-parallelism",
    "Specifies number of threads to process each parallel queue for values "
    "N > 0. if N is 0 then no queue will be used. Default value is 2. "
    "If N > 1 then objects in dump file can have lines intersected. Usage "
    "of values greater than 1 is mutually exclusive with "
    "--single-transaction.")
    ->set_value(2);
  this->create_new_option(&m_result_file, "result-file",
    "Direct all output generated for all objects to a given file.");
  this->create_new_option(&m_compress_output_algorithm, "compress-output",
    "Compresses all output files with LZ4 or ZLIB compression algorithm.");
  this->create_new_option(&m_skip_rows_data, "skip-dump-rows",
    "Skip dumping rows of all tables to output.")
    ->set_short_character('d');
}

Mysqldump_tool_chain_maker_options::~Mysqldump_tool_chain_maker_options()
{
  delete m_formatter_options;
  delete m_object_reader_options;
}

Mysqldump_tool_chain_maker_options::Mysqldump_tool_chain_maker_options(
  const Mysql_chain_element_options* mysql_chain_element_options)
  : m_mysql_chain_element_options(mysql_chain_element_options),
  m_formatter_options(
    new Sql_formatter_options(mysql_chain_element_options)),
  m_object_reader_options(
    new Mysql_object_reader_options(mysql_chain_element_options)),
  m_last_parallel_schemas_queue_id(0),
  m_def_thread_count(0),
  m_parallel_thread_count(0),
  m_object_filter(mysql_chain_element_options->get_program())
{
  this->add_provider(m_formatter_options);
  this->add_provider(m_object_reader_options);
  this->add_provider(&m_object_filter);
}

int Mysqldump_tool_chain_maker_options::
  get_parallel_schemas_with_default_thread_count()
{
  return m_def_thread_count;
}

int Mysqldump_tool_chain_maker_options::get_parallel_schemas_thread_count()
{
  return m_parallel_thread_count;
}
