// Copyright (C) 2014-2018 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/.

#include <config.h>

#include <d2/d2_log.h>
#include <d2/d2_cfg_mgr.h>
#include <d2/d2_simple_parser.h>
#include <cc/command_interpreter.h>
#include <util/encode/hex.h>

#include <boost/foreach.hpp>

using namespace isc::asiolink;
using namespace isc::config;
using namespace isc::data;
using namespace isc::process;

namespace isc {
namespace d2 {

namespace {

typedef std::vector<uint8_t> ByteAddress;

} // end of unnamed namespace

// *********************** D2CfgContext  *************************

D2CfgContext::D2CfgContext()
    : d2_params_(new D2Params()),
      forward_mgr_(new DdnsDomainListMgr("forward-ddns")),
      reverse_mgr_(new DdnsDomainListMgr("reverse-ddns")),
      keys_(new TSIGKeyInfoMap()),
      control_socket_(ConstElementPtr()) {
}

D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : ConfigBase(rhs) {
    d2_params_ = rhs.d2_params_;
    if (rhs.forward_mgr_) {
        forward_mgr_.reset(new DdnsDomainListMgr(rhs.forward_mgr_->getName()));
        forward_mgr_->setDomains(rhs.forward_mgr_->getDomains());
    }

    if (rhs.reverse_mgr_) {
        reverse_mgr_.reset(new DdnsDomainListMgr(rhs.reverse_mgr_->getName()));
        reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
    }

    keys_ = rhs.keys_;

    control_socket_ = rhs.control_socket_;
}

D2CfgContext::~D2CfgContext() {
}

ElementPtr
D2CfgContext::toElement() const {
    ElementPtr d2 = Element::createMap();
    // Set user-context
    contextToElement(d2);
    // Set ip-address
    const IOAddress& ip_address = d2_params_->getIpAddress();
    d2->set("ip-address", Element::create(ip_address.toText()));
    // Set port
    size_t port = d2_params_->getPort();
    d2->set("port", Element::create(static_cast<int64_t>(port)));
    // Set dns-server-timeout
    size_t dns_server_timeout = d2_params_->getDnsServerTimeout();
    d2->set("dns-server-timeout",
            Element::create(static_cast<int64_t>(dns_server_timeout)));
    // Set ncr-protocol
    const dhcp_ddns::NameChangeProtocol& ncr_protocol =
        d2_params_->getNcrProtocol();
    d2->set("ncr-protocol",
            Element::create(dhcp_ddns::ncrProtocolToString(ncr_protocol)));
    // Set ncr-format
    const dhcp_ddns::NameChangeFormat& ncr_format = d2_params_->getNcrFormat();
    d2->set("ncr-format",
            Element::create(dhcp_ddns::ncrFormatToString(ncr_format)));
    // Set forward-ddns
    ElementPtr forward_ddns = Element::createMap();
    forward_ddns->set("ddns-domains", forward_mgr_->toElement());
    d2->set("forward-ddns", forward_ddns);
    // Set reverse-ddns
    ElementPtr reverse_ddns = Element::createMap();
    reverse_ddns->set("ddns-domains", reverse_mgr_->toElement());
    d2->set("reverse-ddns", reverse_ddns);
    // Set tsig-keys
    ElementPtr tsig_keys = Element::createList();
    for (TSIGKeyInfoMap::const_iterator key = keys_->begin();
         key != keys_->end(); ++key) {
        tsig_keys->add(key->second->toElement());
    }
    d2->set("tsig-keys", tsig_keys);
    // Set control-socket (skip if null as empty is not legal)
    if (!isNull(control_socket_)) {
        d2->set("control-socket", UserContext::toElement(control_socket_));
    }
    // Set DhcpDdns
    ElementPtr result = Element::createMap();
    result->set("DhcpDdns", d2);

    return (result);
}

// *********************** D2CfgMgr  *************************

const char* D2CfgMgr::IPV4_REV_ZONE_SUFFIX = "in-addr.arpa.";

const char* D2CfgMgr::IPV6_REV_ZONE_SUFFIX = "ip6.arpa.";

D2CfgMgr::D2CfgMgr() : DCfgMgrBase(ConfigPtr(new D2CfgContext())) {
}

D2CfgMgr::~D2CfgMgr() {
}

ConfigPtr
D2CfgMgr::createNewContext() {
    return (ConfigPtr(new D2CfgContext()));
}

bool
D2CfgMgr::forwardUpdatesEnabled() {
    // Forward updates are not enabled if no forward servers are defined.
    return (getD2CfgContext()->getForwardMgr()->size() > 0);
}

bool
D2CfgMgr::reverseUpdatesEnabled() {
    // Reverse updates are not enabled if no reverse servers are defined.
    return (getD2CfgContext()->getReverseMgr()->size() > 0);
}

bool
D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
    if (fqdn.empty()) {
        // This is a programmatic error and should not happen.
        isc_throw(D2CfgError, "matchForward passed an empty fqdn");
    }

    // Fetch the forward manager from the D2 context.
    DdnsDomainListMgrPtr mgr = getD2CfgContext()->getForwardMgr();

    // Call the manager's match method and return the result.
    return (mgr->matchDomain(fqdn, domain));
}

bool
D2CfgMgr::matchReverse(const std::string& ip_address, DdnsDomainPtr& domain) {
    // Note, reverseIpAddress will throw if the ip_address is invalid.
    std::string reverse_address = reverseIpAddress(ip_address);

    // Fetch the reverse manager from the D2 context.
    DdnsDomainListMgrPtr mgr = getD2CfgContext()->getReverseMgr();

    return (mgr->matchDomain(reverse_address, domain));
}

std::string
D2CfgMgr::reverseIpAddress(const std::string& address) {
    try {
        // Convert string address into an IOAddress and invoke the
        // appropriate reverse method.
        isc::asiolink::IOAddress ioaddr(address);
        if (ioaddr.isV4()) {
            return (reverseV4Address(ioaddr));
        }

        return (reverseV6Address(ioaddr));

    } catch (const isc::Exception& ex) {
        isc_throw(D2CfgError, "D2CfgMgr cannot reverse address: "
                               << address << " : " << ex.what());
    }
}

std::string
D2CfgMgr::reverseV4Address(const isc::asiolink::IOAddress& ioaddr) {
    if (!ioaddr.isV4()) {
        isc_throw(D2CfgError, "D2CfgMgr address is not IPv4 address :"
                  << ioaddr);
    }

    // Get the address in byte vector form.
    const ByteAddress bytes = ioaddr.toBytes();

    // Walk backwards through vector outputting each octet and a dot.
    std::ostringstream stream;

    // We have to set the following variable to get
    // const_reverse_iterator type of rend(), otherwise Solaris GCC
    // complains on operator!= by trying to use the non-const variant.
    const ByteAddress::const_reverse_iterator end = bytes.rend();

    for (ByteAddress::const_reverse_iterator rit = bytes.rbegin();
         rit != end;
         ++rit)
    {
        stream << static_cast<unsigned int>(*rit) << ".";
    }

    // Tack on the suffix and we're done.
    stream << IPV4_REV_ZONE_SUFFIX;
    return(stream.str());
}

std::string
D2CfgMgr::reverseV6Address(const isc::asiolink::IOAddress& ioaddr) {
    if (!ioaddr.isV6()) {
        isc_throw(D2CfgError, "D2Cfg address is not IPv6 address: " << ioaddr);
    }

    // Turn the address into a string of digits.
    const ByteAddress bytes = ioaddr.toBytes();
    const std::string digits = isc::util::encode::encodeHex(bytes);

    // Walk backwards through string outputting each digits and a dot.
    std::ostringstream stream;

    // We have to set the following variable to get
    // const_reverse_iterator type of rend(), otherwise Solaris GCC
    // complains on operator!= by trying to use the non-const variant.
    const std::string::const_reverse_iterator end = digits.rend();

    for (std::string::const_reverse_iterator rit = digits.rbegin();
         rit != end;
         ++rit)
    {
        stream << static_cast<char>(*rit) << ".";
    }

    // Tack on the suffix and we're done.
    stream << IPV6_REV_ZONE_SUFFIX;
    return(stream.str());
}

const D2ParamsPtr&
D2CfgMgr::getD2Params() {
    return (getD2CfgContext()->getD2Params());
}

const isc::data::ConstElementPtr
D2CfgMgr::getControlSocketInfo() {
    return (getD2CfgContext()->getControlSocketInfo());
}

std::string
D2CfgMgr::getConfigSummary(const uint32_t) {
    return (getD2Params()->getConfigSummary());
}

void
D2CfgMgr::setCfgDefaults(ElementPtr mutable_config) {
    D2SimpleParser::setAllDefaults(mutable_config);
}

isc::data::ConstElementPtr
D2CfgMgr::parse(isc::data::ConstElementPtr config_set, bool check_only) {
    // Do a sanity check first.
    if (!config_set) {
        isc_throw(D2CfgError, "Mandatory config parameter not provided");
    }

    D2CfgContextPtr ctx = getD2CfgContext();

    // Set the defaults
    ElementPtr cfg = boost::const_pointer_cast<Element>(config_set);
    D2SimpleParser::setAllDefaults(cfg);

    // And parse the configuration.
    ConstElementPtr answer;
    std::string excuse;
    try {
        // Do the actual parsing
        D2SimpleParser parser;
        parser.parse(ctx, cfg, check_only);
    } catch (const isc::Exception& ex) {
        excuse = ex.what();
        answer = createAnswer(CONTROL_RESULT_ERROR, excuse);
    } catch (...) {
        excuse = "undefined configuration parsing error";
        answer = createAnswer(CONTROL_RESULT_ERROR, excuse);
    }

    // At this stage the answer was created only in case of exception.
    if (answer) {
        if (check_only) {
            LOG_ERROR(d2_logger, DHCP_DDNS_CONFIG_CHECK_FAIL).arg(excuse);
        } else {
            LOG_ERROR(d2_logger, DHCP_DDNS_CONFIG_FAIL).arg(excuse);
        }
        return (answer);
    }

    if (check_only) {
        answer = createAnswer(CONTROL_RESULT_SUCCESS,
                              "Configuration check successful");
    } else {
        answer = createAnswer(CONTROL_RESULT_SUCCESS,
                              "Configuration applied successfully.");
    }

    return (answer);
}


}; // end of isc::dhcp namespace
}; // end of isc namespace
