// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include <exceptions/exceptions.h>
#include <dns/rdataclass.h>
#include <datasrc/rbnode_rrset.h>
#include <testutils/dnsmessage_test.h>

#include <dns/tests/unittest_util.h>

#include <gtest/gtest.h>

#include <sstream>
#include <stdexcept>

using isc::UnitTestUtil;

using namespace isc;
using namespace isc::datasrc;
using namespace isc::datasrc::internal;
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::testutils;
using namespace isc::util;
using namespace std;

// These tests are very similar to those for RRset - indeed, this file was
// created from those tests.  However, the significant difference in behaviour
// between RRset and RBNodeRRset - that the "set" methods in the latter mostly
// result in exceptions being thrown - preclude use of full type
// parameterisation of the tests.

namespace {
const char* const RRSIG_TXT =
    "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
    "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
    "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
    "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
    "f49t+sXKPzbipN9g+s1ZPiIyofc=";

class RBNodeRRsetTest : public ::testing::Test {
protected:
    RBNodeRRsetTest() :
        test_name("test.example.com"),
        test_domain("example.com"),
        test_nsname("ns.example.com"),
        rrset_a(ConstRRsetPtr(new RRset(
                test_name, RRClass::IN(), RRType::A(), RRTTL(3600)))),
        rrset_a_empty(ConstRRsetPtr(new RRset(
                      test_name, RRClass::IN(), RRType::A(), RRTTL(3600)))),
        rrset_ns(ConstRRsetPtr(new RRset(
                 test_domain, RRClass::IN(), RRType::NS(), RRTTL(86400)))),
        rrset_ch_txt(ConstRRsetPtr(new RRset(
                     test_domain, RRClass::CH(), RRType::TXT(), RRTTL(0)))),
        rrset_siga(new RRset(test_name, RRClass::IN(), RRType::RRSIG(),
                   RRTTL(3600)))

    {
        // Add a couple of Rdata elements to the A RRset.  The easiest way to
        // do this is to override the "const" restrictions.  As this is a test,
        // we don't feel too bad about doing so.
        AbstractRRset* a_rrset =
            const_cast<AbstractRRset*>(rrset_a.getUnderlyingRRset().get());
        a_rrset->addRdata(in::A("192.0.2.1"));
        a_rrset->addRdata(in::A("192.0.2.2"));

        // Create the RRSIG corresponding to the rrset_a record.  The RDATA
        // won't match the A record it covers, although it is internally
        // self-consistent.
        AbstractRRset* sig_rrset =
            const_cast<AbstractRRset*>(rrset_siga.get());
        sig_rrset->addRdata(generic::RRSIG(RRSIG_TXT));
    }

    const Name test_name;
    const Name test_domain;
    const Name test_nsname;

    RBNodeRRset rrset_a;
    RBNodeRRset rrset_a_empty;
    const RBNodeRRset rrset_ns;
    const RBNodeRRset rrset_ch_txt;

    ConstRRsetPtr rrset_siga;
};

TEST_F(RBNodeRRsetTest, getRdataCount) {
    EXPECT_EQ(0, rrset_a_empty.getRdataCount());
    EXPECT_EQ(2, rrset_a.getRdataCount());
}

TEST_F(RBNodeRRsetTest, getName) {
    EXPECT_EQ(test_name, rrset_a.getName());
    EXPECT_EQ(test_domain, rrset_ns.getName());
}

TEST_F(RBNodeRRsetTest, getClass) {
    EXPECT_EQ(RRClass("IN"), rrset_a.getClass());
    EXPECT_EQ(RRClass("CH"), rrset_ch_txt.getClass());
}

TEST_F(RBNodeRRsetTest, getType) {
    EXPECT_EQ(RRType("A"), rrset_a.getType());
    EXPECT_EQ(RRType("NS"), rrset_ns.getType());
    EXPECT_EQ(RRType("TXT"), rrset_ch_txt.getType());
}

TEST_F(RBNodeRRsetTest, getTTL) {
    EXPECT_EQ(RRTTL(3600), rrset_a.getTTL());
    EXPECT_EQ(RRTTL(86400), rrset_ns.getTTL());
    EXPECT_EQ(RRTTL(0), rrset_ch_txt.getTTL());
}

TEST_F(RBNodeRRsetTest, setName) {
    EXPECT_THROW(rrset_a.setName(test_nsname), NotImplemented);
}

TEST_F(RBNodeRRsetTest, setTTL) {
    EXPECT_THROW(rrset_a.setTTL(RRTTL(86400)), NotImplemented);
}

TEST_F(RBNodeRRsetTest, toText) {
    EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n"
              "test.example.com. 3600 IN A 192.0.2.2\n",
              rrset_a.toText());

    // toText() cannot be performed for an empty RRset.
    EXPECT_THROW(rrset_a_empty.toText(), EmptyRRset);
}

TEST_F(RBNodeRRsetTest, isSameKind) {
    RBNodeRRset rrset_p(ConstRRsetPtr(new RRset(test_name, RRClass::IN(), RRType::A(), RRTTL(3600))));
    RBNodeRRset rrset_q(ConstRRsetPtr(new RRset(test_name, RRClass::IN(), RRType::A(), RRTTL(3600))));
    RRset rrset_w(test_name, RRClass::IN(), RRType::A(), RRTTL(3600));
    RRset rrset_x(test_nsname, RRClass::IN(), RRType::A(), RRTTL(3600));
    RRset rrset_y(test_name, RRClass::IN(), RRType::NS(), RRTTL(3600));
    RRset rrset_z(test_name, RRClass::CH(), RRType::A(), RRTTL(3600));

    EXPECT_TRUE(rrset_p.isSameKind(rrset_p));
    EXPECT_FALSE(rrset_p.isSameKind(rrset_q));

    EXPECT_TRUE(rrset_p.isSameKind(rrset_w));
    EXPECT_FALSE(rrset_p.isSameKind(rrset_x));
    EXPECT_FALSE(rrset_p.isSameKind(rrset_y));
    EXPECT_FALSE(rrset_p.isSameKind(rrset_z));
}

// Note: although the next two tests are essentially the same and used common
// test code, they use different test data: the MessageRenderer produces
// compressed wire data whereas the OutputBuffer does not.

template <typename T>
void
performToWireTest(T& dataHolder, const RBNodeRRset& rrset,
                  const RBNodeRRset& rrset_empty, const char* testdata)
{
    rrset.toWire(dataHolder);

    std::vector<unsigned char> wiredata;
    UnitTestUtil::readWireData(testdata, wiredata);
    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, dataHolder.getData(),
                        dataHolder.getLength(), &wiredata[0], wiredata.size());

    // toWire() cannot be performed for an empty RRset.
    dataHolder.clear();
    EXPECT_THROW(rrset_empty.toWire(dataHolder), EmptyRRset);
}

TEST_F(RBNodeRRsetTest, toWireRenderer) {
    MessageRenderer renderer;
    performToWireTest(renderer, rrset_a, rrset_a_empty, "rrset_toWire2");
}

TEST_F(RBNodeRRsetTest, toWireBuffer) {
    OutputBuffer buffer(0);
    performToWireTest(buffer, rrset_a, rrset_a_empty, "rrset_toWire1");
}

TEST_F(RBNodeRRsetTest, addRdata) {
    EXPECT_THROW(rrset_a.addRdata(in::A("192.0.2.3")), NotImplemented);

    // Check the same goes for trying to add the wrong type of data
    EXPECT_THROW(rrset_a.addRdata(generic::NS(test_nsname)), NotImplemented);
}

TEST_F(RBNodeRRsetTest, addRdataPtr) {
    EXPECT_THROW(rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(),
                                                    rrset_a_empty.getClass(),
                                                    "192.0.2.1")),
                 NotImplemented);
}

TEST_F(RBNodeRRsetTest, getRDataIterator) {
    RdataIteratorPtr it = rrset_a.getRdataIterator();
    for (int i = 0; i < 2; ++i) {
        ASSERT_FALSE(it->isLast());
        ASSERT_EQ(0, it->getCurrent().compare(in::A("192.0.2.1")));

        it->next();
        ASSERT_FALSE(it->isLast());
        ASSERT_EQ(0, it->getCurrent().compare(in::A("192.0.2.2")));

        it->next();
        ASSERT_TRUE(it->isLast());

        // Should be able repeat the iteration by calling first().
        it->first();
    }
}

// test operator<<.  We simply confirm it appends the result of toText().
TEST_F(RBNodeRRsetTest, LeftShiftOperator) {
    ostringstream oss;
    oss << rrset_a;
    EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n"
              "test.example.com. 3600 IN A 192.0.2.2\n", oss.str());
}

// addRRSIG tests.
TEST_F(RBNodeRRsetTest, addRRsigConstRdataPointer) {
    EXPECT_FALSE(rrset_a.getRRsig());
    ConstRdataPtr data = createRdata(rrset_siga->getType(),
                                     rrset_siga->getClass(), RRSIG_TXT);
    rrset_a.addRRsig(data);
    rrsetCheck(rrset_siga, rrset_a.getRRsig());
}

TEST_F(RBNodeRRsetTest, addRRsigRdataPointer) {
    EXPECT_FALSE(rrset_a.getRRsig());
    RdataPtr data = createRdata(rrset_siga->getType(), rrset_siga->getClass(),
                                RRSIG_TXT);
    rrset_a.addRRsig(data);
    rrsetCheck(rrset_siga, rrset_a.getRRsig());
}

TEST_F(RBNodeRRsetTest, addRRsigAbstractRRset) {
    EXPECT_FALSE(rrset_a.getRRsig());
    rrset_a.addRRsig(*(rrset_siga.get()));
    rrsetCheck(rrset_siga, rrset_a.getRRsig());
}

TEST_F(RBNodeRRsetTest, addRRsigConstantRRsetPointer) {
    EXPECT_FALSE(rrset_a.getRRsig());
    rrset_a.addRRsig(rrset_siga);
    rrsetCheck(rrset_siga, rrset_a.getRRsig());
}

TEST_F(RBNodeRRsetTest, addRRsigRRsetPointer) {
    EXPECT_FALSE(rrset_a.getRRsig());
    RRsetPtr rrsig(new RRset(test_name, RRClass::IN(), RRType::RRSIG(),
                   RRTTL(3600)));
    rrsig->addRdata(generic::RRSIG(RRSIG_TXT));
    rrset_a.addRRsig(rrsig);
    rrsetCheck(rrset_siga, rrset_a.getRRsig());
}

TEST_F(RBNodeRRsetTest, removeRRsig) {
    EXPECT_FALSE(rrset_a.getRRsig());
    rrset_a.addRRsig(*(rrset_siga.get()));
    EXPECT_TRUE(rrset_a.getRRsig());
    rrset_a.removeRRsig();
    EXPECT_FALSE(rrset_a.getRRsig());
}

}   // Anonymous namespace
