// Copyright (C) 2014-2025 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef CFG_OPTION_H
#define CFG_OPTION_H

#include <dhcp/option.h>
#include <dhcp/classify.h>
#include <dhcp/option_space_container.h>
#include <cc/cfg_to_element.h>
#include <cc/stamped_element.h>
#include <cc/user_context.h>
#include <dhcpsrv/cfg_option_def.h>
#include <dhcpsrv/key_from_key.h>
#include <boost/foreach.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/shared_ptr.hpp>
#include <stdint.h>
#include <list>
#include <string>
#include <vector>

namespace isc {
namespace dhcp {

class OptionDescriptor;

/// A pointer to option descriptor.
typedef boost::shared_ptr<OptionDescriptor> OptionDescriptorPtr;

/// A list of option descriptors.
typedef std::vector<OptionDescriptor> OptionDescriptorList;

/// @brief Option descriptor.
///
/// Option descriptor holds instance of an option and additional information
/// for this option. This information comprises whether this option is sent
/// to DHCP client only on request (persistent = false) or always
/// (persistent = true), or must never send (cancelled = true).
class OptionDescriptor : public data::StampedElement, public data::UserContext {
public:
    /// @brief Option instance.
    OptionPtr option_;

    /// @brief Persistence flag.
    ///
    /// If true, option is always sent to the client. If false, option is
    /// sent to the client when requested using ORO or PRL option.
    bool persistent_;

    /// @brief Cancelled flag.
    ///
    /// If true, option is never sent to the client. If false, option is
    /// sent when it should.
    /// @note: When true the action of this flag is final i.e. it can't be
    /// overridden at a more specific level and has precedence over persist.
    bool cancelled_;

    /// @brief Option value in textual (CSV) format.
    ///
    /// This field is used to convey option value in human readable format,
    /// the same as used to specify option value in the server configuration.
    /// This value is optional and can be held in the host reservations
    /// database instead of the binary format.
    ///
    /// Note that this value is carried in the option descriptor, rather than
    /// @c Option instance because it is a server specific value (same as
    /// persistence flag).
    ///
    /// An example of the formatted value is: "2001:db8:1::1, 23, some text"
    /// for the option which carries IPv6 address, a number and a text.
    std::string formatted_value_;

    /// @brief Option space name.
    ///
    /// Options are associated with option spaces. Typically, such association
    /// is made when the option is stored in the @c OptionContainer. However,
    /// in some cases it is also required to associate option with the particular
    /// option space outside of the container. In particular, when the option
    /// is fetched from a database. The database configuration backend will
    /// set option space upon return of the option. In other cases this value
    /// won't be set.
    std::string space_name_;

    /// @brief Collection of classes for the which option is allowed.
    /// An empty list means no class restrictions.
    ClientClasses client_classes_;

    /// @brief Constructor.
    ///
    /// @param opt option instance.
    /// @param persist if true, option is always sent.
    /// @param cancel if true, option is never sent.
    /// @param formatted_value option value in the textual format (optional).
    /// @param user_context user context (optional).
    OptionDescriptor(const OptionPtr& opt, bool persist, bool cancel,
                     const std::string& formatted_value = "",
                     data::ConstElementPtr user_context = data::ConstElementPtr())
        : data::StampedElement(), option_(opt), persistent_(persist),
          cancelled_(cancel), formatted_value_(formatted_value),
          space_name_() {
        setContext(user_context);
    }

    /// @brief Constructor.
    ///
    /// @param persist if true option is always sent.
    /// @param cancel if true, option is never sent.
    OptionDescriptor(bool persist, bool cancel)
        : data::StampedElement(), option_(OptionPtr()), persistent_(persist),
          cancelled_(cancel), formatted_value_(), space_name_() {}

    /// @brief Copy constructor.
    ///
    /// @param desc option descriptor to be copied.
    OptionDescriptor(const OptionDescriptor& desc)
        : data::StampedElement(desc),
          option_(desc.option_),
          persistent_(desc.persistent_),
          cancelled_(desc.cancelled_),
          formatted_value_(desc.formatted_value_),
          space_name_(desc.space_name_),
          client_classes_(desc.client_classes_) {
        setContext(desc.getContext());
    }

    /// @brief Assignment operator.
    ///
    /// @param other option descriptor to be assigned from.
    OptionDescriptor& operator=(const OptionDescriptor& other) {
        if (this != &other) {
            // Not self-assignment.
            data::StampedElement::operator=(other);
            option_ = other.option_;
            persistent_ = other.persistent_;
            cancelled_ = other.cancelled_;
            formatted_value_ = other.formatted_value_;
            space_name_ = other.space_name_;
            client_classes_ = other.client_classes_;
            setContext(other.getContext());
        }
        return (*this);
    }

    /// @brief Factory function creating an instance of the @c OptionDescriptor.
    ///
    /// @param opt option instance.
    /// @param persist if true, option is always sent.
    /// @param cancel if true, option is never sent.
    /// @param formatted_value option value in the textual format (optional).
    /// @param user_context user context (optional).
    ///
    /// @return Pointer to the @c OptionDescriptor instance.
    static OptionDescriptorPtr create(const OptionPtr& opt,
                                      bool persist,
                                      bool cancel,
                                      const std::string& formatted_value = "",
                                      data::ConstElementPtr user_context =
                                      data::ConstElementPtr());

    /// @brief Factory function creating an instance of the @c OptionDescriptor.
    ///
    /// @param persist if true option is always sent.
    /// @param cancel if true, option is never sent.
    ///
    /// @return Pointer to the @c OptionDescriptor instance.
    static OptionDescriptorPtr create(bool persist, bool cancel);

    /// @brief Factory function creating an instance of the @c OptionDescriptor.
    ///
    /// @param desc option descriptor to be copied.
    ///
    /// @return Pointer to the @c OptionDescriptor instance.
    static OptionDescriptorPtr create(const OptionDescriptor& desc);

    /// @brief Checks if the one descriptor is equal to another.
    ///
    /// @param other Other option descriptor to compare to.
    ///
    /// @return true if descriptors equal, false otherwise.
    bool equals(const OptionDescriptor& other) const;

    /// @brief Equality operator.
    ///
    /// @param other Other option descriptor to compare to.
    ///
    /// @return true if descriptors equal, false otherwise.
    bool operator==(const OptionDescriptor& other) const {
        return (equals(other));
    }

    /// @brief Inequality operator.
    ///
    /// @param other Other option descriptor to compare to.
    ///
    /// @return true if descriptors unequal, false otherwise.
    bool operator!=(const OptionDescriptor& other) const {
        return (!equals(other));
    }

    /// @brief Adds new client class for which the option is allowed.
    ///
    /// @param class_name Class name.
    void addClientClass(const std::string& class_name);

    /// @brief Validates an OptionDescriptor's client-classes against a list
    /// of classes
    ///
    /// @param cclasses list of ClientClasses to validate against
    /// @return True if descriptor's client-classes is empty or at least
    /// one of its members is found in the validation list.
    bool allowedForClientClasses(const ClientClasses& cclasses) const;

    /// @brief Get a copy of client classes
    ///
    /// @return Pointer to a copy of descriptor's client classes
    ClientClassesPtr copyClientClasses() const;
};

/// @brief Multi index container for DHCP option descriptors.
///
/// This container comprises three indexes to access option
/// descriptors:
/// - sequenced index: used to access elements in the order they
/// have been added to the container,
/// - option type index: used to search option descriptors containing
/// options with specific option code (aka option type).
/// - persistency flag index: used to search option descriptors with
/// 'persistent' flag set to true.
///
/// This container is the equivalent of four separate STL containers:
/// - std::list of all options,
/// - std::multimap of options with option code used as a multimap key,
/// - std::multimap of option descriptors with option persistency flag
/// used as a multimap key.
/// - std::multimap of option descriptors with option cancellation flag
/// used as a multimap key.
/// The major advantage of this container over 4 separate STL containers
/// is automatic synchronization of all indexes when elements are added,
/// removed or modified in the container. With separate containers,
/// the synchronization would have to be guaranteed by the Subnet class
/// code. This would increase code complexity and presumably it would
/// be much harder to add new search criteria (indexes).
///
/// @todo we may want to search for options using option spaces when
/// they are implemented.
///
/// @see http://www.boost.org/doc/libs/1_51_0/libs/multi_index/doc/index.html
typedef boost::multi_index_container<
    // Container comprises elements of OptionDescriptor type.
    OptionDescriptor,
    // Here we start enumerating various indexes.
    boost::multi_index::indexed_by<
        // Sequenced index allows accessing elements in the same way
        // as elements in std::list.
        // Sequenced is an index #0.
        boost::multi_index::sequenced<>,
        // Start definition of index #1.
        boost::multi_index::hashed_non_unique<
            // KeyFromKeyExtractor is the index key extractor that allows
            // accessing option type being held by the OptionPtr through
            // OptionDescriptor structure.
            KeyFromKeyExtractor<
                // Use option type as the index key. The type is held
                // in OptionPtr object so we have to call Option::getType
                // to retrieve this key for each element.
                boost::multi_index::const_mem_fun<
                    Option,
                    uint16_t,
                    &Option::getType
                >,
                // Indicate that OptionPtr is a member of
                // OptionDescriptor structure.
                boost::multi_index::member<
                    OptionDescriptor,
                    OptionPtr,
                    &OptionDescriptor::option_
                 >
            >
        >,
        // Start definition of index #2.
        // Use 'persistent' struct member as a key.
        boost::multi_index::hashed_non_unique<
            boost::multi_index::member<
                OptionDescriptor,
                bool,
                &OptionDescriptor::persistent_
            >
        >,
        // Start definition of index #3.
        // Use BaseStampedElement::getModificationTime as a key.
        boost::multi_index::ordered_non_unique<
            boost::multi_index::const_mem_fun<
                data::BaseStampedElement,
                boost::posix_time::ptime,
                &data::BaseStampedElement::getModificationTime
            >
        >,

        // Start definition of index #4.
        // Use BaseStampedElement::getId as a key.
        boost::multi_index::hashed_non_unique<
            boost::multi_index::tag<OptionIdIndexTag>,
            boost::multi_index::const_mem_fun<data::BaseStampedElement, uint64_t,
                                              &data::BaseStampedElement::getId>
        >,
        // Start definition of index #5.
        // Use 'cancelled' struct member as a key.
        boost::multi_index::hashed_non_unique<
            boost::multi_index::member<
                OptionDescriptor,
                bool,
                &OptionDescriptor::cancelled_
            >
        >,
        // Start definition of index #6.
        boost::multi_index::hashed_non_unique<
            boost::multi_index::composite_key<
                OptionDescriptor,
                KeyFromKeyExtractor<
                    boost::multi_index::const_mem_fun<
                        Option,
                        uint16_t,
                        &Option::getType
                    >,
                    boost::multi_index::member<
                        OptionDescriptor,
                        OptionPtr,
                        &OptionDescriptor::option_
                    >
                >,
                boost::multi_index::member<OptionDescriptor,
                                           ClientClasses,
                                           &OptionDescriptor::client_classes_>
            >
        >
    >
> OptionContainer;

/// Pointer to the OptionContainer object.
typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
/// Type of the index #1 - option type.
typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
/// Pair of iterators to represent the range of options having the
/// same option type value. The first element in this pair represents
/// the beginning of the range, the second element represents the end.
typedef std::pair<OptionContainerTypeIndex::const_iterator,
                  OptionContainerTypeIndex::const_iterator> OptionContainerTypeRange;
/// Type of the index #2 - option persistency flag.
typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
/// Pair of iterators to represent the range of options having the
/// same persistency flag. The first element in this pair represents
/// the beginning of the range, the second element represents the end.
typedef std::pair<OptionContainerPersistIndex::const_iterator,
                  OptionContainerPersistIndex::const_iterator> OptionContainerPersistRange;
/// Type of the index #5 - option cancellation flag.
typedef OptionContainer::nth_index<5>::type OptionContainerCancelIndex;
/// Pair of iterators to represent the range of options having the
/// same cancellation flag. The first element in this pair represents
/// the beginning of the range, the second element represents the end.
typedef std::pair<OptionContainerCancelIndex::const_iterator,
                  OptionContainerCancelIndex::const_iterator> OptionContainerCancelRange;

/// Type of the index #6 - option type + client_classes.
typedef OptionContainer::nth_index<6>::type OptionContainerTypeClassesIndex;
typedef std::pair<OptionContainerTypeClassesIndex::const_iterator,
                  OptionContainerTypeClassesIndex::const_iterator> OptionContainerTypeClassesRange;

/// @brief Represents option data configuration for the DHCP server.
///
/// This class holds a collection of options to be sent to a DHCP client.
/// Options are grouped by the option space or vendor identifier (for
/// vendor options).
///
/// The server configuration allows for specifying two distinct collections
/// of options: global options and per-subnet options in which some options
/// may overlap.
///
/// The collection of global options specify options being sent to the client
/// belonging to any subnets, i.e. global options are "inherited" by all
/// subnets.
///
/// The per-subnet options are configured for a particular subnet and are sent
/// to clients which belong to this subnet. The values of the options specified
/// for a particular subnet override the values of the global options.
///
/// This class represents a single collection of options (either global or
/// per-subnet). Each subnet holds its own object of the @c CfgOption type. The
/// @c CfgMgr holds a @c CfgOption object representing global options.
///
/// Note that having a separate copy of the @c CfgOption to represent global
/// options is useful when the client requests stateless configuration from
/// the DHCP server and no subnet is selected for this client. This client
/// will only receive global options.
class CfgOption : public isc::data::CfgToElement {
public:

    /// @brief default constructor
    CfgOption();

    /// @brief Indicates the object is empty
    ///
    /// @return true when the object is empty
    bool empty() const;

    /// @name Methods and operators used for comparing objects.
    ///
    //@{
    /// @brief Check if configuration is equal to other configuration.
    ///
    /// @param other An object holding configuration to compare to.
    ///
    /// @return true if configurations are equal, false otherwise.
    bool equals(const CfgOption& other) const;

    /// @brief Equality operator.
    ///
    /// @param other An object holding configuration to compare to.
    ///
    /// @return true if configurations are equal, false otherwise.
    bool operator==(const CfgOption& other) const {
        return (equals(other));
    }

    /// @brief Inequality operator.
    ///
    /// @param other An object holding configuration to compare to.
    ///
    /// @return true if configurations are unequal, false otherwise.
    bool operator!=(const CfgOption& other) const {
        return (!equals(other));
    }

    //@}

    /// @brief Adds instance of the option to the configuration.
    ///
    /// There are two types of options which may be passed to this method:
    /// - vendor options
    /// - non-vendor options
    ///
    /// The non-vendor options are grouped by the name of the option space
    /// (specified in textual format). The vendor options are grouped by the
    /// vendor identifier, which is a 32-bit unsigned integer value.
    ///
    /// In order to add new vendor option to the list the option space name
    /// (last argument of this method) should be specified as "vendor-X" where
    /// "X" is a 32-bit unsigned integer, e.g. "vendor-1234". Options for which
    /// the @c option_space argument doesn't follow this format are added as
    /// non-vendor options.
    ///
    /// @param option Pointer to the option being added.
    /// @param persistent Boolean value which specifies if the option should
    /// be sent to the client regardless if requested (true), or nor (false)
    /// @param cancelled Boolean value which specifies if the option must
    /// never be sent to the client.
    /// @param option_space Option space name.
    /// @param id Optional database id to be associated with the option.
    ///
    /// @throw isc::BadValue if the option space is invalid.
    void add(const OptionPtr& option, const bool persistent,
             const bool cancelled, const std::string& option_space,
             const uint64_t id = 0);

    /// @brief A variant of the @ref CfgOption::add method which takes option
    /// descriptor as an argument.
    ///
    /// @param desc Option descriptor holding option instance and other
    /// parameters pertaining to the option.
    /// @param option_space Option space name.
    ///
    /// @throw isc::BadValue if the option space is invalid.
    void add(const OptionDescriptor& desc, const std::string& option_space);

    /// @brief Replaces the instance of an option within this collection
    ///
    /// This method locates the option within the given space and replaces
    /// it with a copy of the given descriptor.  This effectively updates
    /// the contents without altering the container indexing.
    ///
    /// @param desc Option descriptor holding option instance and other
    /// parameters pertaining to the option.
    /// @param option_space Option space name.
    ///
    /// @throw isc::BadValue if the descriptor's option instance is null,
    /// if  space is invalid, or if the option does not already exist
    /// in the given space.
    void replace(const OptionDescriptor& desc, const std::string& option_space);

    /// @brief Merges another option configuration into this one.
    ///
    /// This method calls @c mergeTo() to add this configuration's
    /// options into @c other (skipping any duplicates).  Next it calls
    /// @c createDescriptorOption() for each option descriptor in the
    /// merged set.  This (re)-creates each descriptor's option based on
    /// the merged set of opt definitions. Finally, it calls
    /// @c copyTo() to overwrite this configuration's options with
    /// the merged set in @c other.
    ///
    /// @warning The merge operation will affect the @c other configuration.
    /// Therefore, the caller must not rely on the data held in the @c other
    /// object after the call to @c merge. Also, the data held in @c other must
    /// not be modified after the call to @c merge because it may affect the
    /// merged configuration.
    ///
    /// @param cfg_def set of of user-defined option definitions to use
    /// when merging.
    /// @param other option configuration to merge in.
    void merge(CfgOptionDefPtr cfg_def, CfgOption& other);

    /// @brief Re-create the option in each descriptor based on given definitions
    ///
    /// Invokes @c createDescriptorOption() on each option descriptor in
    /// each option space, passing in the given dictionary of option
    /// definitions.  If the descriptor's option is re-created, then the
    /// descriptor is updated by calling @c replace().
    ///
    /// @param cfg_def set of of user-defined option definitions to use
    /// when creating option instances.
    void createOptions(CfgOptionDefPtr cfg_def);

    /// @brief Creates an option descriptor's option based on a set of option defs
    ///
    /// This function's primary use is to create definition specific options for
    /// option descriptors fetched from a configuration backend, as part of a
    /// configuration merge.
    ///
    /// Given an OptionDescriptor whose option_ member contains a generic option
    /// (i.e has a code and/or data), this function will attempt to find a matching
    /// definition and then use that definition's factory to create an option
    /// instance specific to that definition.   It will then replace the descriptor's
    /// generic option with the specific option.
    ///
    /// Three sources of definitions are searched, in the following order:
    ///
    /// 1. Standard option definitions (@c LIBDHCP::getOptionDef))
    /// 2. Vendor option definitions (@c LIBDHCP::getVendorOptionDef))
    /// 3. User specified definitions passed in via cfg_def parameter.
    ///
    /// The code will use the first matching definition found.  It then applies
    /// the following rules:
    ///
    /// -# If no definition is found but the descriptor conveys a non-empty
    /// formatted value, throw an error.
    /// -# If not definition is found and there is no formatted value, return
    /// This leaves intact the generic option in the descriptor.
    /// -# If a definition is found and there is no formatted value, pass the
    /// descriptor's generic option's data into the definition's factory. Replace
    /// the descriptor's option with the newly created option.
    /// -# If a definition is found and there is a formatted value, split
    /// the value into vector of values and pass that into the definition's
    /// factory. Replace the descriptor's option with the newly created option.
    ///
    /// @param cfg_def the user specified definitions to use
    /// @param space the option space name of the option
    /// @param opt_desc OptionDescriptor describing the option.
    ///
    /// @return True if the descriptor's option instance was replaced.
    /// @throw InvalidOperation if the descriptor conveys a formatted value and
    /// there is no definition matching the option code in the given space, or
    /// if the definition factory invocation fails.
    static bool createDescriptorOption(CfgOptionDefPtr cfg_def, const std::string& space,
                                       OptionDescriptor& opt_desc);

    /// @brief Merges this configuration to another configuration.
    ///
    /// This method iterates over the configuration items held in this
    /// configuration and copies them to the configuration specified
    /// as a parameter. If an item exists in the destination it is not
    /// copied.
    ///
    /// @param [out] other Configuration object to merge to.
    void mergeTo(CfgOption& other) const;

    /// @brief Copies this configuration to another configuration.
    ///
    /// This method copies options configuration to another object.
    ///
    /// @param [out] other An object to copy the configuration to.
    void copyTo(CfgOption& other) const;

    /// @brief Appends encapsulated options to top-level options.
    ///
    /// This method iterates over the top-level options (from "dhcp4"
    /// and "dhcp6" option space) and checks which option spaces these
    /// options encapsulate. For each encapsulated option space, the
    /// options from this option space are appended to top-level options.
    void encapsulate();

    /// @brief Checks if options have been encapsulated.
    ///
    /// @return true if options have been encapsulated, false otherwise.
    bool isEncapsulated() const {
        return (encapsulated_);
    }

    /// @brief Returns all options for the specified option space.
    ///
    /// This method will not return vendor options, i.e. having option space
    /// name in the format of "vendor-X" where X is 32-bit unsigned integer.
    /// See @c getAll(uint32_t) for vendor options.
    ///
    /// @param option_space Name of the option space.
    ///
    /// @return Pointer to the container holding returned options. This
    /// container is empty if no options have been found.
    OptionContainerPtr getAll(const std::string& option_space) const;

    /// @brief Returns vendor options for the specified vendor id.
    ///
    /// @param vendor_id Vendor id for which options are to be returned.
    ///
    /// @return Pointer to the container holding returned options. This
    /// container is empty if no options have been found.
    OptionContainerPtr getAll(const uint32_t vendor_id) const;

    /// @brief Returns all non-vendor or vendor options for the specified
    /// option space.
    ///
    /// It combines the output of the @c getAll function variants. When
    /// option space has the format of "vendor-X", it retrieves the vendor
    /// options by vendor id, where X must be a 32-bit unsigned integer.
    /// Otherwise, it fetches non-vendor options.
    ///
    /// @param option_space Name of the option space.
    /// @return Pointer to the container holding returned options. This
    /// container is empty if no options have been found.
    OptionContainerPtr getAllCombined(const std::string& option_space) const;

    /// @brief Returns option for the specified key and option code.
    ///
    /// The key should be a string, in which case it specifies an option space
    /// name, or an uint32_t value, in which case it specifies a vendor
    /// identifier.
    ///
    /// @note If there are multiple options with the same key, only one will
    /// be returned.  No indication will be given of the presence of others,
    /// and the instance returned is not determinable. So please use
    /// the next method when multiple instances of the option are expected.
    ///
    /// @param key Option space name or vendor identifier.
    /// @param option_code Code of the option to be returned.
    /// @tparam Selector one of: @c std::string or @c uint32_t
    ///
    /// @return Descriptor of the option. If option hasn't been found, the
    /// descriptor holds null option.
    template<typename Selector>
    OptionDescriptor get(const Selector& key,
                         const uint16_t option_code) const {

        // Check for presence of options.
        OptionContainerPtr options = getAll(key);
        if (!options || options->empty()) {
            return (OptionDescriptor(false, false));
        }

        // Some options present, locate the one we are interested in.
        const OptionContainerTypeIndex& idx = options->get<1>();
        OptionContainerTypeIndex::const_iterator od_itr = idx.find(option_code);
        if (od_itr == idx.end()) {
            return (OptionDescriptor(false, false));
        }

        return (*od_itr);
    }

    /// @brief Returns options for the specified key and option code.
    ///
    /// The key should be a string, in which case it specifies an option space
    /// name, or an uint32_t value, in which case it specifies a vendor
    /// identifier.
    ///
    /// @param key Option space name or vendor identifier.
    /// @param option_code Code of the option to be returned.
    /// @tparam Selector one of: @c std::string or @c uint32_t
    ///
    /// @return List of Descriptors of the option.
    template<typename Selector>
    OptionDescriptorList getList(const Selector& key,
                                 const uint16_t option_code) const {

        OptionDescriptorList list;
        // Check for presence of options.
        OptionContainerPtr options = getAll(key);
        if (!options || options->empty()) {
            return (list);
        }

        // Some options present, locate the one we are interested in.
        const OptionContainerTypeIndex& idx = options->get<1>();
        OptionContainerTypeRange range = idx.equal_range(option_code);
        // This code copies descriptors and can be optimized not doing this.
        BOOST_FOREACH(auto const& od_itr, range) {
            list.push_back(od_itr);
        }

        return (list);
    }

    /// @brief Returns option for the specified key, option code and client classes tag.
    ///
    /// The key should be a string, in which case it specifies an option space
    /// name, or an uint32_t value, in which case it specifies a vendor
    /// identifier.
    ///
    /// @param key Option space name or vendor identifier.
    /// @param option_code Code of the option to be returned.
    /// @param client_classes client classes tag of the option to be returned.
    /// @tparam Selector one of: @c std::string or @c uint32_t
    ///
    /// @return Descriptor of the option. If option hasn't been found, the
    /// descriptor holds null option.
    template<typename Selector>
    OptionDescriptor get(const Selector& key,
                         const uint16_t option_code,
                         const ClientClasses& client_classes) const {

        // Check for presence of options.
        OptionContainerPtr options = getAll(key);
        if (!options || options->empty()) {
            return (OptionDescriptor(false, false));
        }

        // Some options present, locate the one we are interested in
        // using code + client_classes index.
        auto& idx6 = options->get<6>();
        auto const& od_itr6 = idx6.find(boost::make_tuple(option_code, client_classes));
        if (od_itr6 == idx6.end()) {
            return (OptionDescriptor(false, false));
        }

        return (*od_itr6);
    }

    /// @brief Fetches an option for a given code if it is allowed for the given list
    /// of client classes.
    ///
    /// @param key Option space name or vendor identifier.
    /// @param option_code Code of the option to be returned.
    /// @param cclasses list of ClientClasses to validate against
    /// @tparam Selector one of: @c std::string or @c uint32_t
    ///
    /// @return Descriptor of the option. If option hasn't been found, the
    /// descriptor holds null option.
    template<typename Selector>
    OptionDescriptor allowedForClientClasses(const Selector& key,
                                             const uint16_t option_code,
                                             const ClientClasses& cclasses) const {
        // Check for presence of options.
        OptionContainerPtr options = getAll(key);
        if (!options || options->empty()) {
            return (OptionDescriptor(false, false));
        }

        // We treat the empty client-classes case (if present) as a default.
        // If we encounter it before we reach the end of the list of options
        // remember it but keep checking the list for an actual match. We do
        // it this way to avoid expecting the entries in any particular order.
        auto & index = options->get<1>();
        auto range = index.equal_range(option_code);
        switch (std::distance(range.first, range.second)) {
        case 0:
            break;
        case 1:
            if ((*range.first).allowedForClientClasses(cclasses)) {
                return (*range.first);
            }
            break;
        default:
        {
            auto default_opt = index.end();
            auto otr = range.first;
            while (otr != range.second) {
                if ((*otr).allowedForClientClasses(cclasses)) {
                    if (!(*otr).client_classes_.empty()) {
                        return (*otr);
                    }

                    default_opt = otr;
                }

                ++otr;
            }

            // If we have a default return it.
            if (default_opt != index.end()) {
                return (*default_opt);
            }
        }}

        // None allowed.
        return (OptionDescriptor(false, false));
    }

    /// @brief Deletes option for the specified option space and option code.
    ///
    /// If the option is encapsulated within some non top level option space,
    /// it is also deleted from all option instances encapsulating this
    /// option space.
    ///
    /// @param option_space Option space name.
    /// @param option_code Code of the option to be returned.
    ///
    /// @return Number of deleted options.
    size_t del(const std::string& option_space, const uint16_t option_code);

    /// @brief Deletes option for the specified option space and option code.
    ///
    /// If the option is encapsulated within some non top level option space,
    /// it is also deleted from all option instances encapsulating this
    /// option space.
    ///
    /// @param option_space Option space name.
    /// @param option_code Code of the option to be returned.
    /// @param client_classes client classes tag of the option to be deleted.
    ///
    /// @return Number of deleted options.
    size_t del(const std::string& option_space, const uint16_t option_code,
               const ClientClasses& client_classes);

    /// @brief Deletes vendor option for the specified vendor id.
    ///
    /// @param vendor_id Vendor identifier.
    /// @param option_code Option code.
    ///
    /// @return Number of deleted options.
    size_t del(const uint32_t vendor_id, const uint16_t option_code);

    /// @brief Deletes all options having a given database id.
    ///
    /// Note that there are cases when there will be multiple options
    /// having the same id (typically id of 0). When configuration backend
    /// is in use it sets the unique ids from the database. In cases when
    /// the configuration backend is not used, the ids default to 0.
    /// Passing the id of 0 would result in deleting all options that were
    /// not added via the database.
    ///
    /// Both regular and vendor specific options are deleted with this
    /// method.
    ///
    /// This method internally calls @c encapsulate() after deleting
    /// options having the given id.
    ///
    /// @param id Identifier of the options to be deleted.
    ///
    /// @return Number of deleted options. Note that if a single option
    /// instance is encapsulated by multiple options it adds 1 to the
    /// number of deleted options even though the same instance is
    /// deleted from multiple higher level options.
    size_t del(const uint64_t id);

    /// @brief Returns a list of configured option space names.
    ///
    /// The returned option space names exclude vendor option spaces,
    /// such as "vendor-1234". These are returned by the
    /// @ref getVendorIdsSpaceNames.
    ///
    /// @return List comprising option space names.
    std::list<std::string> getOptionSpaceNames() const {
        return (options_.getOptionSpaceNames());
    }

    /// @brief Returns a list of all configured  vendor identifiers.
    std::list<uint32_t> getVendorIds() const {
        return (vendor_options_.getOptionSpaceNames());
    }

    /// @brief Returns a list of option space names for configured vendor ids.
    ///
    /// For each vendor-id the option space name returned is constructed
    /// as "vendor-XYZ" where XYZ is a @c uint32_t value without leading
    /// zeros.
    ///
    /// @return List comprising option space names for vendor options.
    std::list<std::string> getVendorIdsSpaceNames() const;

    /// @brief Unparse a configuration object
    ///
    /// @return a pointer to unparsed configuration
    virtual isc::data::ElementPtr toElement() const;

    /// @brief Unparse a configuration object
    ///
    /// @param cfg_option_def config option definitions
    /// @return a pointer to unparsed configuration
    virtual isc::data::ElementPtr toElement(CfgOptionDefPtr cfg_option_def) const;

    /// @brief Unparse a configuration object with optionally including
    /// the metadata.
    ///
    /// @param include_metadata boolean value indicating if the metadata
    /// should be included (if true) or not (if false).
    /// @param cfg_option_def config option definitions (optional).
    ///
    /// @return A pointer to the unparsed configuration.
    isc::data::ElementPtr
    toElementWithMetadata(const bool include_metadata,
                          CfgOptionDefPtr cfg_option_def = CfgOptionDefPtr()) const;

private:

    /// @brief Appends encapsulated options to the options in an option space.
    ///
    /// This method appends sub-options to the options belonging to the
    /// particular option space. For example: if the option space "foo"
    /// is specified, this function will go over all options belonging to
    /// "foo" and will check which option spaces they encapsulate. For each
    /// such option it will retrieve options for these option spaces and append
    /// as sub-options to options belonging to "foo".
    ///
    /// @param option_space Name of the option space containing option to
    /// which encapsulated options are appended.
    void encapsulateInternal(const std::string& option_space);

    /// @brief Appends encapsulated options from the option space encapsulated
    /// by the specified option.
    ///
    /// This method will go over all options belonging to the encapsulated space
    /// and will check which option spaces they encapsulate recursively,
    /// adding these options to the current option
    ///
    /// @param option which encapsulated options.
    void encapsulateInternal(const OptionPtr& option);

    /// @brief Merges data from two option containers.
    ///
    /// This method merges options from one option container to another
    /// option container. This function is templated because containers
    /// may use different type of selectors. For non-vendor options
    /// the selector is of the @c std::string type, for vendor options
    /// the selector is of the @c uint32_t type.
    ///
    /// @param src_container Reference to a container from which the data
    /// will be merged.
    /// @param [out] dest_container Reference to a container to which the
    /// data will be merged.
    /// @tparam Type of the selector: @c std::string or @c uint32_t.
    template <typename Selector>
    void mergeInternal(const OptionSpaceContainer<OptionContainer,
                       OptionDescriptor, Selector>& src_container,
                       OptionSpaceContainer<OptionContainer,
                       OptionDescriptor, Selector>& dest_container) const;

    /// @brief A flag indicating if options have been encapsulated.
    bool encapsulated_;

    /// @brief Type of the container holding options grouped by option space.
    typedef OptionSpaceContainer<OptionContainer, OptionDescriptor,
                                 std::string> OptionSpaceCollection;
    /// @brief Container holding options grouped by option space.
    OptionSpaceCollection options_;

    /// @brief Type of the container holding options grouped by vendor id.
    typedef OptionSpaceContainer<OptionContainer, OptionDescriptor,
                                 uint32_t> VendorOptionSpaceCollection;
    /// @brief Container holding options grouped by vendor id.
    VendorOptionSpaceCollection vendor_options_;
};

/// @name Pointers to the @c CfgOption objects.
//@{

/// @brief Non-const pointer.
typedef boost::shared_ptr<CfgOption> CfgOptionPtr;

/// @brief Const pointer.
typedef boost::shared_ptr<const CfgOption> ConstCfgOptionPtr;

/// @brief Const pointer list.
typedef std::list<ConstCfgOptionPtr> CfgOptionList;

//@}

}
}

#endif // CFG_OPTION_H
