/*
   Copyright (c) 2014, 2015 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 ABSTRACT_OPTION_INCLUDED
#define ABSTRACT_OPTION_INCLUDED

#include <string>
#include <vector>
#include "my_getopt.h"
#include "i_option_changed_listener.h"
#include "i_callable.h"


namespace Mysql{
namespace Tools{
namespace Base{
namespace Options{

class Abstract_options_provider;

/**
  Abstract base with common option functionalities.
 */
template<typename T_type> class Abstract_option : public I_option
{
public:
  virtual ~Abstract_option();

  /**
    Adds new callback for this option for option_parsed() event to callback
    chain.
    I_Callable can be replaced with std::Function<void(char*)> once we get
    one.
   */
  T_type* add_callback(Mysql::I_callable<void, char*>* callback);

  /**
    Sets optid to given character to make possible usage of short option
    alternative.
   */
  T_type* set_short_character(char code);

protected:
  /**
    Constructs new option.
    @param value Pointer to object to receive option value.
    @param var_type my_getopt internal option type.
    @param name Name of option. It is used in command line option name as
      --name.
    @param desription Description of option to be printed in --help.
    @param default_value default value to be supplied to internal option
      data structure.
   */
  Abstract_option(void* value, ulong var_type, std::string name,
    std::string description, uint64 default_value);

  /**
    Returns my_getopt internal option data structure representing this option.
    To be used by Abstract_options_provider when preparing options array to
    return.
   */
  my_option get_my_option();

  /**
    Method to set listener on option changed events.
    For use from Abstract_options_provider class only.
   */
  void set_option_changed_listener(I_option_changed_listener* listener);

  my_option m_option_structure;

private:
  void call_callbacks(char* argument);

  std::vector<Mysql::I_callable<void, char*>*> m_callbacks;
  I_option_changed_listener* m_option_changed_listener;

  friend class Abstract_options_provider;
};

template<typename T_type> Abstract_option<T_type>::~Abstract_option()
{
  my_free((void*)this->m_option_structure.name);
  my_free((void*)this->m_option_structure.comment);

  for (std::vector<Mysql::I_callable<void, char*>*>::iterator
    it= this->m_callbacks.begin();
    it != this->m_callbacks.end();
    it++)
  {
    delete *it;
  }
}

template<typename T_type> T_type* Abstract_option<T_type>::add_callback(
  Mysql::I_callable<void, char*>* callback)
{
  this->m_callbacks.push_back(callback);
  return (T_type*)this;
}

template<typename T_type> T_type* Abstract_option<T_type>::set_short_character(
  char code)
{
  // Change optid to new one
  uint32 old_optid= this->m_option_structure.id;
  this->m_option_structure.id= (int)code;

  // Inform that it has changed
  if (this->m_option_changed_listener != NULL)
  {
    this->m_option_changed_listener->notify_option_optid_changed(
      this, old_optid);
  }

  return (T_type*)this;
}

template<typename T_type> Abstract_option<T_type>::Abstract_option(void* value,
    ulong var_type, std::string name, std::string description,
    uint64 default_value)
  : m_option_changed_listener(NULL)
{
  this->m_option_structure.block_size= 0;
  this->m_option_structure.max_value= 0;
  this->m_option_structure.min_value= 0;
  this->m_option_structure.sub_size= 0;
  this->m_option_structure.typelib= NULL;
  this->m_option_structure.u_max_value= NULL;

  this->m_option_structure.app_type= this;
  this->m_option_structure.arg_type= REQUIRED_ARG;
  this->m_option_structure.comment= my_strdup(
    PSI_NOT_INSTRUMENTED, description.c_str(), MYF(MY_FAE));
  // This in future can be changed to atomic operation (compare_and_exchange)
  this->m_option_structure.id= Abstract_option::last_optid;
  Abstract_option::last_optid++;
  ;
  this->m_option_structure.def_value= default_value;

  this->m_option_structure.name= my_strdup(
    PSI_NOT_INSTRUMENTED, name.c_str(), MYF(MY_FAE));
  /*
   TODO mbabij 15-04-2014: this is based on previous usages of my_option.
   Everyone sets this the same as my_option::value, explain why.
   */
  this->m_option_structure.u_max_value= value;

  this->m_option_structure.value= value;
  this->m_option_structure.var_type= var_type;
}

template<typename T_type> my_option Abstract_option<T_type>::get_my_option()
{
  return this->m_option_structure;
}

template<typename T_type> void
  Abstract_option<T_type>::set_option_changed_listener(
    I_option_changed_listener* listener)
{
  DBUG_ASSERT(this->m_option_changed_listener == NULL);

  this->m_option_changed_listener= listener;
}

template<typename T_type> void Abstract_option<T_type>::call_callbacks(
  char* argument)
{
  std::vector<Mysql::I_callable<void, char*>*>::iterator callback_it;
  for (callback_it= this->m_callbacks.begin();
    callback_it != this->m_callbacks.end(); callback_it++)
  {
    (**callback_it)(argument);
  }
}

}
}
}
}

#endif
