// -*- 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;

    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 );
#if 1

        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);
#endif
        CPPUNIT_TEST(containers_with_spaces_test);
#if 1
        CPPUNIT_TEST(get_attr_iter_test);
        CPPUNIT_TEST(del_attr_table_test);
        CPPUNIT_TEST(append_attr_vector_test);
#endif
#if 0
        CPPUNIT_TEST(print_xml_test);
#endif

        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 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");
            }
            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() {
            at1->print_xml(stdout);
        }
    };

    CPPUNIT_TEST_SUITE_REGISTRATION(AttrTableTest);

} // namespace libdap

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

    while ((option_char = getopt()) != -1)
        switch (option_char) {
            case 'd':
                debug = 1;  // debug is a static global
                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 {
        while (i < argc) {
            test = string("libdap::AttrTableTest::") + argv[i++];
            if (debug)
                cerr << "Running " << test << endl;
            wasSuccessful = wasSuccessful && runner.run(test);
        }
    }

    return wasSuccessful ? 0 : 1;
}
