// -*- mode: c++; c-basic-offset:4 -*-

// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.

// Copyright (c) 2002,2003 OPeNDAP, Inc.
// Author: James Gallagher <jgallagher@opendap.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.

#include "config.h"

#include <cppunit/TextTestRunner.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/extensions/HelperMacros.h>

#include <cstring>
#include <string>
#include <sstream>

#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "GNURegex.h"
#include "AttrTable.h"
#include "debug.h"

#include "testFile.h"
#include "GetOpt.h"

static bool debug = false;

#undef DBG
#define DBG(x) do { if (debug) (x); } while(false);

using namespace CppUnit;
using namespace std;
using namespace libdap;

static string build_fqn(AttrTable *at, string fqn)
{
    // The strange behavior at the top level is because the top level of an
    // AttrTable (i.e. the DAS) is anonymous. Another bad design... jhrg 2/8/06
    if (!at || !at->get_parent() || at->get_name().empty())
        return fqn;
    else
        return build_fqn(at->get_parent(), at->get_name() + string(".") + fqn);
}

namespace libdap {

class AttrTableTest: public TestFixture {
private:
    AttrTable *at1;
    AttrTable *cont_a, *cont_b, *cont_c, *cont_ba, *cont_ca, *cont_caa;
    char a[1024];    

public:
    AttrTableTest()
    {
    }
    ~AttrTableTest()
    {
    }

    void setUp()
    {
        at1 = new AttrTable;
        cont_a = at1->append_container("a");
        cont_a->append_attr("size", "Int32", "7");
        cont_a->append_attr("type", "String", "cars");

        cont_b = at1->append_container("b");
        cont_b->append_attr("number", "Int32", "1");
        cont_b->append_attr("type", "String", "houses");
        cont_ba = cont_b->append_container("ba");
        cont_ba->append_attr("name", "String", "fred");

        cont_c = at1->append_container("c");
        cont_ca = cont_c->append_container("ca");
        cont_caa = cont_ca->append_container("caa");
        cont_caa->append_attr("color", "String", "red");

        // This AttrTable looks like:
        //      Attributes {
        //          a {
        //              Int32 size 7;
        //              String type cars;
        //          }
        //          b {
        //              Int32 number 1;
        //              String type houses;
        //              ba {
        //                  String name fred;
        //              }
        //          }
        //          c {
        //              ca {
        //                  caa {
        //                      String color red;
        //                  }
        //              }
        //          }
        //      }
    }

    void tearDown()
    {
        delete at1;
        at1 = 0;
    }

    bool re_match(Regex &r, const char *s)
    {
        return r.match(s, strlen(s)) == (int) strlen(s);
    }

    CPPUNIT_TEST_SUITE (AttrTableTest);

    CPPUNIT_TEST (clone_test);
    CPPUNIT_TEST (find_container_test);
    CPPUNIT_TEST (get_parent_test);
    CPPUNIT_TEST (recurrsive_find_test);
    CPPUNIT_TEST (find_test);
    CPPUNIT_TEST (copy_ctor);
    CPPUNIT_TEST (assignment);
    CPPUNIT_TEST (erase_test);
    CPPUNIT_TEST (names_with_spaces_test);
    CPPUNIT_TEST (is_global_att_test);
    CPPUNIT_TEST (add_container_alias_test);
    CPPUNIT_TEST (attr_alias_test);
    CPPUNIT_TEST (attr_alias_2_test);
    CPPUNIT_TEST (containers_with_spaces_test);
    CPPUNIT_TEST (get_attr_iter_test);
    CPPUNIT_TEST (del_attr_table_test);
    CPPUNIT_TEST (append_attr_vector_test);
    CPPUNIT_TEST (print_xml_test);
    CPPUNIT_TEST (print_simple_test);

    CPPUNIT_TEST_SUITE_END();

    // Tests for methods

    // This is to test for leaks in the clone() method.
    void clone_test()
    {
        AttrTable *att = new AttrTable;
        att->append_container(new AttrTable(*cont_a), "copy_of_a");
        delete att;
    }

    void recurrsive_find_test()
    {
        AttrTable::Attr_iter location;
        AttrTable *a = at1->recurrsive_find("color", &location);
        CPPUNIT_ASSERT(a && a == cont_caa && a->get_name(location) == "color");

        a = cont_caa->recurrsive_find("color", &location);
        CPPUNIT_ASSERT(a && a == cont_caa && a->get_name(location) == "color");

        a = at1->recurrsive_find("ba", &location);
        CPPUNIT_ASSERT(a && a == cont_b && a->get_name(location) == "ba");
    }

    void get_parent_test()
    {
        CPPUNIT_ASSERT(cont_caa->get_parent() == cont_ca);
        CPPUNIT_ASSERT(cont_ca->get_parent() == cont_c);
        CPPUNIT_ASSERT(cont_c->get_parent() == at1);
        CPPUNIT_ASSERT(at1->get_parent() == 0);
        CPPUNIT_ASSERT(build_fqn(cont_caa, "color") == "c.ca.caa.color");
    }

    void find_container_test()
    {
        AttrTable *tmp = at1->find_container("a");
        CPPUNIT_ASSERT(tmp != 0);
        CPPUNIT_ASSERT(tmp == cont_a);

        CPPUNIT_ASSERT(at1->find_container("b.ba") == cont_ba);
        CPPUNIT_ASSERT(at1->find_container("a.b") == 0);
        CPPUNIT_ASSERT(at1->find_container("c.ca.caa") == cont_caa);
        CPPUNIT_ASSERT(at1->find_container("caa") == 0);

        CPPUNIT_ASSERT(at1->find_container("a.size") == 0);
        CPPUNIT_ASSERT(at1->find_container("b.ba.name") == 0);
    }

    void find_test()
    {
        AttrTable *tmp;
        AttrTable::Attr_iter iter;
        at1->find("a", &tmp, &iter);
        CPPUNIT_ASSERT(tmp && iter != tmp->attr_end() && tmp->is_container(iter) && tmp->get_name(iter) == "a");
        at1->find("a.size", &tmp, &iter);
        CPPUNIT_ASSERT(
            tmp && iter != tmp->attr_end() && !tmp->is_container(iter) && tmp->get_name(iter) == "size"
                && tmp->get_attr(iter) == "7");
        at1->find("b.type", &tmp, &iter);
        CPPUNIT_ASSERT(
            tmp && iter != tmp->attr_end() && !tmp->is_container(iter) && tmp->get_name(iter) == "type"
                && tmp->get_attr(iter) == "houses");
        at1->find("c.ca.caa.color", &tmp, &iter);
        CPPUNIT_ASSERT(
            tmp && iter != tmp->attr_end() && !tmp->is_container(iter) && tmp->get_name(iter) == "color"
                && tmp->get_attr(iter) == "red");
        at1->find("d.size", &tmp, &iter);
        CPPUNIT_ASSERT(!tmp);
        at1->find("c.size", &tmp, &iter);
        CPPUNIT_ASSERT(tmp == cont_c && iter == tmp->attr_end());
    }

    void copy_ctor()
    {
        AttrTable at2 = *at1;
        AttrTable::Attr_iter piter = at2.attr_begin();
        CPPUNIT_ASSERT(at2.get_name(piter) == "a");
        CPPUNIT_ASSERT(at2.is_container(piter));
        AttrTable *tmp = at2.get_attr_table(piter);
        AttrTable::Attr_iter qiter = tmp->attr_begin();
        CPPUNIT_ASSERT(tmp->get_name(qiter) == "size");
        piter++;
        CPPUNIT_ASSERT(at2.get_name(piter) == "b");
        CPPUNIT_ASSERT(at2.is_container(piter));
        piter++;
        CPPUNIT_ASSERT(at2.get_name(piter) == "c");
        CPPUNIT_ASSERT(at2.is_container(piter));
    }

    void assignment()
    {
        AttrTable at2;
        at2 = *at1;

        AttrTable::Attr_iter piter = at2.attr_begin();
        CPPUNIT_ASSERT(at2.get_name(piter) == "a");
        CPPUNIT_ASSERT(at2.is_container(piter));
        AttrTable *tmp = at2.get_attr_table(piter);
        AttrTable::Attr_iter qiter = tmp->attr_begin();
        CPPUNIT_ASSERT(tmp->get_name(qiter) == "size");
        piter++;
        CPPUNIT_ASSERT(at2.get_name(piter) == "b");
        CPPUNIT_ASSERT(at2.is_container(piter));
        piter++;
        CPPUNIT_ASSERT(at2.get_name(piter) == "c");
        CPPUNIT_ASSERT(at2.is_container(piter));
    }

    void erase_test()
    {
        // Copy at1 to at2 and verify that at2 is full of stuff...
        AttrTable at2 = *at1;
        AttrTable::Attr_iter piter = at2.attr_begin();
        CPPUNIT_ASSERT(at2.get_name(piter) == "a");
        CPPUNIT_ASSERT(at2.is_container(piter));
        AttrTable *tmp = at2.get_attr_table(piter);
        AttrTable::Attr_iter qiter = tmp->attr_begin();
        CPPUNIT_ASSERT(tmp->get_name(qiter) == "size");
        piter++;
        CPPUNIT_ASSERT(at2.get_name(piter) == "b");
        CPPUNIT_ASSERT(at2.is_container(piter));
        piter++;
        CPPUNIT_ASSERT(at2.get_name(piter) == "c");
        CPPUNIT_ASSERT(at2.is_container(piter));

        at2.erase();
        CPPUNIT_ASSERT(at2.attr_map.size() == 0);
        CPPUNIT_ASSERT(at2.d_name == "");
    }

    void names_with_spaces_test()
    {
        // Create an AttrTable where some names have spaces. The spaces
        // should be replaced by %20 escapes.
        // Replacing the spaces with %20 was the bad, old, behavior. Now
        // the spaces can stay. If someone is writing a DAS using the {}
        // notation, they can use '%20' for the spaces. In the printed
        // DAS using the {} notation, spaces will be represented by %20.
        AttrTable *t = new AttrTable;
        t->append_attr("long name", "String", "first");
        t->append_attr("longer name", "String", "\"second test\"");
        //string sof;
        ostringstream oss;
        t->print(oss, "");
        //FILE2string(sof, of, t->print(of, ""));
        string attrs = "String long%20name \"first\";\n\
String longer%20name \"second test\";";
        //CPPUNIT_ASSERT(sof.find(attrs) != string::npos);
        CPPUNIT_ASSERT(oss.str().find(attrs) != string::npos);
        delete t;
        t = 0;
    }

    void is_global_att_test()
    {
        for (AttrTable::Attr_iter iter = at1->attr_begin(); iter != at1->attr_end(); iter++) {
            at1->set_is_global_attribute(iter, true);
            CPPUNIT_ASSERT(at1->is_global_attribute(iter));
        }
    }

    void add_container_alias_test()
    {
        CPPUNIT_ASSERT_THROW(at1->add_container_alias("a", at1), Error);
    }

    void attr_alias_test()
    {
        at1->attr_alias("alias", at1, "a");
        CPPUNIT_ASSERT(at1->get_type("alias") == "Container");
    }

    void attr_alias_2_test()
    {
        at1->attr_alias("alias", "a");
        CPPUNIT_ASSERT(at1->get_type("alias") == "Container");
    }

    void containers_with_spaces_test()
    {
        AttrTable *top = new AttrTable;
        try {
            AttrTable *cont = top->append_container("Data Field");
            cont->append_attr("long name", "String", "first");
            cont->add_value_alias(top, "an alias", "long name");
            CPPUNIT_ASSERT_THROW(cont->add_value_alias(top, "an alias", "long name"), Error);
        }
        catch (Error &e) {
            cerr << e.get_error_message() << endl;
            CPPUNIT_ASSERT("Caught Error exception!" && false);
        }
        try {
            ostringstream oss;
            top->print(oss);
            Regex r(
                ".*Data%20Field \\{\n\
.*String long%20name \"first\";\n\
.*Alias an%20alias long%20name;\n\
.*\\}\n");
            DBG(cout << ">" << oss.str() << "<" << endl);
            CPPUNIT_ASSERT(re_match(r, oss.str().c_str()));
            delete top;
            top = 0;
        }
        catch (Error &e) {
            cerr << e.get_error_message() << endl;
            CPPUNIT_ASSERT("Caught Error exception!" && false);
        }
    }

    void get_attr_iter_test()
    {
        int n = at1->get_size();
        CPPUNIT_ASSERT(n == 3);

        AttrTable::Attr_iter i = at1->get_attr_iter(0);
        CPPUNIT_ASSERT(at1->get_name(i) == "a");
        i = at1->get_attr_iter(2);
        CPPUNIT_ASSERT(at1->get_name(i) == "c");

        i = at1->get_attr_iter(1);
        CPPUNIT_ASSERT(at1->is_container(i));
        AttrTable *t1 = at1->get_attr_table(i);
        AttrTable::Attr_iter k = t1->get_attr_iter(1);
        CPPUNIT_ASSERT(t1->get_name(k) == "type");
        CPPUNIT_ASSERT(t1->get_attr(k, 0) == "houses");
    }

    void del_attr_table_test()
    {
        AttrTable *b = at1->find_container("b");
        AttrTable::Attr_iter i = b->attr_begin();
        CPPUNIT_ASSERT(b->get_name(i) == "number");
        i += 2;
        CPPUNIT_ASSERT(b->get_name(i) == "ba");

        b->del_attr_table(i);

        i = b->attr_begin();
        CPPUNIT_ASSERT(b->get_name(i) == "number");
        i += 2;
        CPPUNIT_ASSERT(i == b->attr_end());

        // try a second table. at2 contains a scalar attribute followed by a
        // container named 'a'.
        AttrTable *at2;
        try {
            at2 = new AttrTable;
            at2->set_name("at2");
            at2->append_attr("color", "String", "red");
            AttrTable *cont_at2 = at2->append_container("cont_at2");
            cont_at2->append_attr("size", "Int32", "7");
            cont_at2->append_attr("type", "String", "cars");
            i = at2->attr_begin();
            CPPUNIT_ASSERT(at2->get_name(i) == "color");
            i++;
            CPPUNIT_ASSERT(at2->get_name(i) == "cont_at2");

            at2->del_attr_table(i);

            i = at2->attr_begin();
            CPPUNIT_ASSERT(at2->get_name(i) == "color");
            i++;
            CPPUNIT_ASSERT(i == at2->attr_end());

            delete at2;
            at2 = 0;
        }
        catch (Error &e) {
            cerr << "Error: " << e.get_error_message() << endl;
            delete at2;
            at2 = 0;
            throw;
        }
        catch (...) {
            cerr << "caught an exception!" << endl;
            delete at2;
            at2 = 0;
            throw;
        }
    }

    void append_attr_vector_test()
    {
        // ("size", "Int32", "7")
        vector<string> vs;
        vs.push_back("8");
        vs.push_back("9");
        cont_a->append_attr("size", "Int32", &vs);

        CPPUNIT_ASSERT(cont_a->get_attr("size", 0) == "7");
        CPPUNIT_ASSERT(cont_a->get_attr("size", 1) == "8");
        CPPUNIT_ASSERT(cont_a->get_attr("size", 2) == "9");
        CPPUNIT_ASSERT(cont_a->get_attr_num("size") == 3);
    }

    void print_xml_test()
    {
        ostringstream sof;
        at1->print_xml(sof);
        CPPUNIT_ASSERT(sof.str().find("<Attribute name=\"a\" type=\"Container\">") != string::npos);
    }

    void print_simple_test()
    {
        FILE *fp;
        fp = fopen("AttrTableTest_print_simple.output", "w");
        AttrTable::Attr_iter i = at1->simple_find("a");
        at1->simple_print(fp, "", i, false);
        fclose(fp);
        ifstream ifs("AttrTableTest_print_simple.output");
        stringstream buf;
        buf<<ifs.rdbuf();
        CPPUNIT_ASSERT(buf.str().find("String type \"cars\";") != string::npos);        
        ifs.close();
    }
    
};

CPPUNIT_TEST_SUITE_REGISTRATION (AttrTableTest);

} // namespace libdap

int main(int argc, char *argv[])
{
    GetOpt getopt(argc, argv, "dh");
    int option_char;

    while ((option_char = getopt()) != -1)
        switch (option_char) {
        case 'd':
            debug = 1;  // debug is a static global
            break;

        case 'h': {     // help - show test names
            cerr << "Usage: AttrTableTest has the following tests:" << endl;
            const std::vector<Test*> &tests = libdap::AttrTableTest::suite()->getTests();
            unsigned int prefix_len = libdap::AttrTableTest::suite()->getName().append("::").length();
            for (std::vector<Test*>::const_iterator i = tests.begin(), e = tests.end(); i != e; ++i) {
                cerr << (*i)->getName().replace(0, prefix_len, "") << endl;
            }
            break;
        }

        default:
            break;
        }

    CppUnit::TextTestRunner runner;
    runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());

    bool wasSuccessful = true;
    string test = "";
    int i = getopt.optind;
    if (i == argc) {
        // run them all
        wasSuccessful = runner.run("");
    }
    else {
        for (; i < argc; ++i) {
            if (debug) cerr << "Running " << argv[i] << endl;
            test = libdap::AttrTableTest::suite()->getName().append("::").append(argv[i]);
            wasSuccessful = wasSuccessful && runner.run(test);
        }
    }

    return wasSuccessful ? 0 : 1;
}
