/*
** Copyright 2014-2015 Centreon
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
**
** For more information : contact@centreon.com
*/

#include <sstream>
#include "com/centreon/broker/bam/configuration/applier/meta_service.hh"
#include "com/centreon/broker/bam/metric_book.hh"
#include "com/centreon/broker/config/applier/state.hh"
#include "com/centreon/broker/logging/logging.hh"
#include "com/centreon/broker/multiplexing/publisher.hh"
#include "com/centreon/broker/neb/host.hh"
#include "com/centreon/broker/neb/service.hh"

using namespace com::centreon::broker;
using namespace com::centreon::broker::bam::configuration;

/**
 *  Default constructor.
 */
applier::meta_service::meta_service() {}

/**
 *  Copy constructor.
 *
 *  @param[in] other  Object to copy.
 */
applier::meta_service::meta_service(
                         applier::meta_service const& other) {
  _internal_copy(other);
}

/**
 *  Destructor.
 */
applier::meta_service::~meta_service() {}

/**
 *  Assignment operator.
 *
 *  @param[in] other  Object to copy.
 *
 *  @return This object.
 */
applier::meta_service& applier::meta_service::operator=(
                                                applier::meta_service const& other) {
  if (this != &other)
    _internal_copy(other);
  return (*this);
}

/**
 *  Apply configuration.
 *
 *  @param[in]  my_meta  Meta-services to apply.
 *  @param[out] book     Metric book.
 */
void applier::meta_service::apply(
                              state::meta_services const& my_meta,
                              metric_book& book) {
  //
  // DIFF
  //

  // Objects to delete are items remaining in the
  // set at the end of the iteration.
  std::map<unsigned int, applied> to_delete(_applied);

  // Objects to create are items remaining in the
  // set at the end of the iteration.
  state::meta_services to_create(my_meta);

  // Objects to modify are items found but
  // with mismatching configuration.
  std::list<configuration::meta_service> to_modify;

  // Iterate through configuration.
  for (state::meta_services::iterator
         it(to_create.begin()),
         end(to_create.end());
       it != end;) {
    std::map<unsigned int, applied>::iterator
      cfg_it(to_delete.find(it->first));
    // Found = modify (or not).
    if (cfg_it != to_delete.end()) {
      // Configuration mismatch, modify object.
      if (cfg_it->second.cfg != it->second)
        to_modify.push_back(it->second);
      to_delete.erase(cfg_it);
      bam::configuration::state::meta_services::iterator tmp = it;
      ++it;
      to_create.erase(tmp);
    }
    // Not found = create.
    else
      ++it;
  }

  //
  // OBJECT CREATION/DELETION
  //

  // Delete objects.
  for (std::map<unsigned int, applied>::iterator
         it(to_delete.begin()),
         end(to_delete.end());
       it != end;
       ++it) {
    logging::config(logging::medium)
      << "BAM: removing meta-service " << it->second.cfg.get_id();
    misc::shared_ptr<neb::service>
      s(_meta_service(
          it->first,
          it->second.cfg.get_host_id(),
          it->second.cfg.get_service_id()));
    s->enabled = false;
    book.unlisten(it->second.cfg.get_id(), it->second.obj.data());
    _applied.erase(it->first);
    multiplexing::publisher().write(s);
  }
  to_delete.clear();

  // Create new objects.
  for (state::meta_services::iterator
         it(to_create.begin()),
         end(to_create.end());
       it != end;
       ++it) {
    logging::config(logging::medium)
      << "BAM: creating meta-service " << it->first;
    misc::shared_ptr<bam::meta_service>
      new_meta(_new_meta(it->second, book));
    applied& content(_applied[it->first]);
    content.cfg = it->second;
    content.obj = new_meta;
    misc::shared_ptr<neb::host>
      h(_meta_host(it->second.get_host_id()));
    multiplexing::publisher().write(h);
    misc::shared_ptr<neb::service>
      s(_meta_service(
          it->first,
          it->second.get_host_id(),
          it->second.get_service_id()));
    multiplexing::publisher().write(s);
  }

  // Modify existing objects.
  for (std::list<configuration::meta_service>::iterator
         it(to_modify.begin()),
         end(to_modify.end());
       it != end;
       ++it) {
    std::map<unsigned int, applied>::iterator
      pos(_applied.find(it->get_id()));
    if (pos != _applied.end()) {
      logging::config(logging::medium)
        << "BAM: modifying meta-service " << it->get_id();
      _modify_meta(*pos->second.obj, book, pos->second.cfg, *it);
      pos->second.cfg = *it;
    }
    else
      logging::error(logging::high)
        << "BAM: attempting to modify meta-service " << it->get_id()
        << ", however associated object was not found. This is likely a"
        << " software bug that you should report to Centreon Broker "
        << "developers";
  }

  return ;
}

/**
 *  Find a meta-service by its ID.
 *
 *  @param[in] id  Meta-service ID.
 *
 *  @return Shared pointer to the applied meta-service object.
 */
misc::shared_ptr<bam::meta_service> applier::meta_service::find_meta(
                                                             unsigned int id) {
  std::map<unsigned int, applied>::iterator
    it(_applied.find(id));
  return ((it != _applied.end())
          ? it->second.obj
          : misc::shared_ptr<bam::meta_service>());
}

/**
 *  Copy internal data members.
 *
 *  @param[in] other  Object to copy.
 */
void applier::meta_service::_internal_copy(
                              applier::meta_service const& other) {
  _applied = other._applied;
  return ;
}

/**
 *  Get the virtual host of a meta-service.
 *
 *  @param[in] host_id  Host ID.
 *
 *  @return Virtual meta-service host.
 */
misc::shared_ptr<neb::host> applier::meta_service::_meta_host(
                                                     unsigned int host_id) {
  misc::shared_ptr<neb::host> h(new neb::host);
  h->host_id = host_id;
  h->host_name = "_Module_Meta";
  h->last_update = time(NULL);
  h->poller_id
    = com::centreon::broker::config::applier::state::instance().poller_id();
  return (h);
}

/**
 *  Get the virtual service of a meta-service.
 *
 *  @param[in] meta_id     Meta-service ID.
 *  @param[in] host_id     Host ID.
 *  @param[in] service_id  Service ID.
 *
 *  @return Virtual BA service.
 */
misc::shared_ptr<neb::service> applier::meta_service::_meta_service(
                                                        unsigned int meta_id,
                                                        unsigned int host_id,
                                                        unsigned int service_id) {
  misc::shared_ptr<neb::service> s(new neb::service);
  s->host_id = host_id;
  s->service_id = service_id;
  {
    std::ostringstream oss;
    oss << "meta_" << meta_id;
    s->service_description = oss.str().c_str();
  }
  s->last_update = time(NULL);
  return (s);
}

/**
 *  Modify a meta-service.
 *
 *  @param[in,out] obj      Meta-service object.
 *  @param[in,out] book     Metric book.
 *  @param[in]     old_cfg  Old configuration.
 *  @param[in]     new_cfg  New configuration.
 */
void applier::meta_service::_modify_meta(
                              bam::meta_service& obj,
                              metric_book& book,
                              configuration::meta_service const& old_cfg,
                              configuration::meta_service const& new_cfg) {
  // Remove old metrics from 1) the book and from 2) the meta-service.
  for (configuration::meta_service::metric_container::const_iterator
         it(old_cfg.get_metrics().begin()),
         end(old_cfg.get_metrics().end());
       it != end;
       ++it) {
    logging::config(logging::low)
      << "BAM: meta-service " << obj.get_id()
      << " does not depend of metric " << *it << " anymore";
    book.unlisten(*it, &obj);
    obj.remove_metric(*it);
  }

  // Add new metrics to 1) the book and to 2) the meta-service.
  for (configuration::meta_service::metric_container::const_iterator
         it(new_cfg.get_metrics().begin()),
         end(new_cfg.get_metrics().end());
       it != end;
       ++it) {
    logging::config(logging::low)
      << "BAM: meta-service " << obj.get_id() << " uses metric " << *it;
    book.listen(*it, &obj);
    obj.add_metric(*it);
  }

  // Modify meta-service properties.
  std::string const& computation_str(new_cfg.get_computation());
  bam::meta_service::computation_type computation;
  if ("MIN" == computation_str)
    computation = bam::meta_service::min;
  else if ("MAX" == computation_str)
    computation = bam::meta_service::max;
  else if (("SUM" == computation_str) || ("SOM" == computation_str))
    computation = bam::meta_service::sum;
  else
    computation = bam::meta_service::average;
  obj.set_computation(computation);
  obj.set_id(new_cfg.get_id());
  obj.set_host_id(new_cfg.get_host_id());
  obj.set_service_id(new_cfg.get_service_id());
  obj.set_level_warning(new_cfg.get_level_warning());
  obj.set_level_critical(new_cfg.get_level_critical());

  return ;
}

/**
 *  Create a new meta-service.
 *
 *  @param[in]  cfg   Meta-service configuration.
 *  @param[out] book  Metric listener book.
 */
misc::shared_ptr<bam::meta_service> applier::meta_service::_new_meta(
                                                             configuration::meta_service const& cfg,
                                                             metric_book& book) {
  misc::shared_ptr<bam::meta_service> meta(new bam::meta_service);
  _modify_meta(*meta, book, configuration::meta_service(), cfg);
  return (meta);
}
