// 
//   Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// 
// This program 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 General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#ifdef HAVE_CONFIG_H
#include "gnashconfig.h"
#endif

#ifdef HAVE_DEJAGNU_H

#include <iostream>
#include <regex.h>
#include <cstdio>
#include <cerrno>
#include <fstream>
#include <cstring>
#include <vector>
#include <cstdint>

#include "dejagnu.h"
#include "log.h"
#include "rc.h"
#include "network.h"
#ifdef HAVE_MALLINFO
#include "gmemory.h"
#endif
#include "buffer.h"
#include "arg_parser.h"
#include "GnashException.h"

using namespace std;
using namespace cygnal;
using namespace gnash;
using namespace boost;

static void usage();

// Prototypes for test cases
static void test_resize();
static void test_construct();
static void test_copy();
static void test_find();
static void test_append();
static void test_remove();
static void test_destruct();
static void test_operators();

// Enable the display of memory allocation and timing data
static bool memdebug = false;

// We use the Memory profiling class to check the malloc buffers
// in the kernel to make sure the allocations and frees happen
// the way we expect them too. There is no real other way to tell.
#ifdef HAVE_MALLINFO
Memory *mem = 0;
#endif

TestState runtest;
LogFile& dbglogfile = LogFile::getDefaultInstance();
RcInitFile& rcfile = RcInitFile::getDefaultInstance();

int
main (int argc, char** argv)
{
    const Arg_parser::Option opts[] =
        {
            { 'h', "help",          Arg_parser::no  },
            { 'v', "verbose",       Arg_parser::no  },
            { 'w', "write",         Arg_parser::no  },
// Unless you have support for memory debugging turned on, and
// you have support for the Linux mallinfo() system call,
// this option is totally useless. This doesn't really matter
// as the memory testing is primarily used only during
// debugging or development.
            { 'm', "memstats",      Arg_parser::no  },
            { 'd', "dump",          Arg_parser::no  },
        };
    
    Arg_parser parser(argc, argv, opts);
    if( ! parser.error().empty() ) {
        cout << parser.error() << endl;
        exit(EXIT_FAILURE);
    }
    
    for( int i = 0; i < parser.arguments(); ++i ) {
        const int code = parser.code(i);
        try {
            switch( code ) {
              case 'h':
                  usage ();
                  exit(EXIT_SUCCESS);
              case 'v':
                    dbglogfile.setVerbosity();
                    log_debug(_("Verbose output turned on"));
                    break;
              case 'm':
                    log_debug(_("Enabling memory statistics"));
                    memdebug = true;
                    break;
              case 'w':
                  rcfile.useWriteLog(true);
                  log_debug(_("Logging to disk enabled"));
                  break;
                  
	    }
        }
        
        catch (Arg_parser::ArgParserException &e) {
            cerr << _("Error parsing command line options: ") << e.what() << endl;
            cerr << _("This is a Gnash bug.") << endl;
        }
    }
    
#ifdef HAVE_MALLINFO
    if (memdebug) {
        mem = new Memory;
        mem->startStats();
    }
#endif
    
// these tests are bogus unless you have both mallinfo()
// and also have memory statistics gathering turned on.
#if defined(HAVE_MALLINFO) && defined(USE_STATS_MEMORY)
    // test creating Buffers
    test_construct();
    // test destroying Buffers
    test_destruct();
#endif

    test_resize();
    test_copy();
    test_find();
    test_append();
    test_remove();
    test_operators();

// cygnal::Buffer::resize(unsigned int)
    
#if defined(HAVE_MALLINFO) && defined(USE_STATS_MEMORY)
    if (memdebug) {
        if (mem->analyze()) {
            runtest.pass("Buffer doesn't leak memory");
        } else {
            runtest.fail("Buffer leaks memory!");
        }
    }
#endif
    
    // cleanup
#ifdef HAVE_MALLINFO
    if (mem) {
        delete mem;
    }
#endif
}

void
test_resize()
{
    Buffer buf;
#ifdef HAVE_MALLINFO
    if (memdebug) {
        mem->addStats(__LINE__);             // take a sample
    }
#endif    
    if (buf.size() == cygnal::NETBUFSIZE) {
        runtest.pass ("Buffer::size(NETBUFSIZE)");
    } else {
        runtest.fail ("Buffer::size(NETBUFSIZE)");
    }
    
#ifdef HAVE_MALLINFO
    if (memdebug) {
        mem->addStats(__LINE__);             // take a sample
    }
#endif
    buf.resize(112);
#ifdef HAVE_MALLINFO
    if (memdebug) {
        mem->addStats(__LINE__);             // take a sample
    }
#endif
    if (buf.size() == 112) {
         runtest.pass ("Buffer::resize(112)");
     } else {
         runtest.fail ("Buffer::resize(112)");
    }
#ifdef HAVE_MALLINFO
    if (memdebug) {
        mem->addStats(__LINE__);             // take a sample
    }
#endif

    string str = "Hello World";
    buf = str;
    buf.resize(5);
    if (memcmp(buf.begin(), str.c_str(), 5) == 0) {
        runtest.pass ("Buffer resize(5)");
    } else {
        runtest.fail ("Buffer resize(5)");
    }
}

void
test_copy()
{
    // Make some data for the buffers
    Network::byte_t *data = 0;
    data = new Network::byte_t[10];
    memset(data, 0, 10);
    for (size_t i=1; i<10; i++) {
        *(data + i) = i + '0';
    }

    Buffer buf1;
    Network::byte_t *ptr1 = 0;
    ptr1 = buf1.reference();

    buf1.copy(data, 10);
    if (memcmp(ptr1, data, 10) == 0) {
         runtest.pass ("Buffer::copy(Network::byte_t *, size_t)");
    } else {
         runtest.fail ("Buffer::copy(Network::byte_t *, size_t)");
    }

    const char *str = "I'm bored";
    string str1 = str;
    buf1 = str1;
    if (memcmp(ptr1, str, 9) == 0) {
         runtest.pass ("Buffer::operator=(std::string &)");
    } else {
         runtest.fail ("Buffer::operator=(std::string &)");
    }

    Buffer buf2;
    buf2 = str;
    Network::byte_t *ptr2 = buf2.reference();
    if (memcmp(ptr2, str, 9) == 0) {
         runtest.pass ("Buffer::operator=(const char *)");
    } else {
         runtest.fail ("Buffer::operator=(const char *)");
    }

    std::uint16_t length = 12;
    Buffer buf3;
    buf3 = length;
    Network::byte_t *ptr3 = buf3.reference();
    std::uint16_t newlen = *(reinterpret_cast<std::uint16_t *>(ptr3));
    if (length == newlen) {
         runtest.pass ("Buffer::operator=(std::uint16_t)");
    } else {
         runtest.fail ("Buffer::operator=(std::uint16_t)");
    }

    double num = 1.2345;
    Buffer buf4;
    buf4 = num;

    // Copy the raw bytes used for the number into the temporary
    // data pointer, so we can do a comparison
    memcpy(data, &num, cygnal::AMF0_NUMBER_SIZE);

    if (memcmp(data, buf4.reference(), cygnal::AMF0_NUMBER_SIZE) == 0) {
         runtest.pass ("Buffer::operator=(double)");
    } else {
         runtest.fail ("Buffer::operator=(double)");
    }   

    Network::byte_t byte = 67;
    Buffer buf5;
    buf5 = byte;
    if (*buf5.reference() == 67) {
         runtest.pass ("Buffer::operator=(Network::byte_t)");
    } else {
         runtest.fail ("Buffer::operator=(Network::byte_t)");
    }

    cygnal::Element::amf0_type_e type = Element::NUMBER_AMF0;
    Buffer buf6;
    buf6 = type;
    if (*buf6.reference() == type) {
         runtest.pass ("Buffer::operator=(amf::Element::amf0_type_e)");
    } else {
         runtest.fail ("Buffer::operator=(amf::Element::amf0_type_e)");
    }
    
    bool flag = true;
    Buffer buf7;
    buf7 = flag;
    if (*buf7.reference() == flag) {
         runtest.pass ("Buffer::operator=(bool)");
    } else {
         runtest.fail ("Buffer::operator=(bool)");
    }
    

        // cleanup the temporary data
    delete[] data;
}

void
test_find()
{
    // Make some data for the buffers
    Network::byte_t *data = new Network::byte_t[10];
    for (size_t i=0; i<10; i++) {
        data[i] = i + 'a';
    }

    Buffer buf1, buf2, buf3;
    Network::byte_t *ptr1 = buf1.reference();

    // populate the buffer
    buf1.copy(data, 10);
    delete[] data;
    
    // See if we can find a character
    Network::byte_t *fptr = std::find(buf1.begin(), buf1.end(), 'c'); 
    if (fptr == (ptr1 + 2)) {
         runtest.pass ("Buffer::find(Network::byte_t)");
    } else {
         runtest.fail ("Buffer::find(Network::byte_t)");
    }

    const char *sub = "fgh";
#if 0
    Network::byte_t *ptr2 = const_cast<Network::byte_t *>(reinterpret_cast<const Network::byte_t *>(sub));
#endif
    fptr = std::search(buf1.begin(), buf1.end(), sub, sub+3);
    if (fptr == (ptr1 + 5)) {
         runtest.pass ("Buffer::find(Network::byte_t *, size_t)");
    } else {
         runtest.fail ("Buffer::find(Network::byte_t *, size_t)");
    }

// cygnal::Buffer::init(unsigned int)
}

void
test_append()
{
    Buffer buf1;
    buf1.clear();
//    Network::byte_t *ptr1 = buf1.reference();

    Network::byte_t *data1 = new Network::byte_t[10];
    memset(data1, 0, 10);
    for (size_t i=0; i< 10; i++) {
        data1[i] = i + 'a';
    }
    Network::byte_t *data2 = new Network::byte_t[10];
    memset(data2, 0, 10);
    for (size_t i=0; i< 10; i++) {
        data2[i] = i + 'A';
    }

    // append a string of bytes
    Network::byte_t *data3 = new Network::byte_t[20];
    memcpy(data3, data1, 10);
    memcpy(data3+10, data2, 10);
    buf1.copy(data1, 10);
    buf1.append(data2, 10);
    if (memcmp(data3, buf1.reference(), 20) == 0) {
         runtest.pass ("Buffer::append(Network::byte_t *, size_t)");
    } else {
         runtest.fail ("Buffer::append(Network::byte_t *, size_t)");
    }

    // append an unsigned byte
    Buffer buf2(30);
    buf2.clear();
    buf2.copy(data1, 10);
    Network::byte_t byte = '@';
    buf2 += byte;
    memset(data3, 0, 20);
    memcpy(data3, data1, 10);
    *(data3 + 10) = '@';
    if (memcmp(data3, buf2.reference(), 11) == 0) {
         runtest.pass ("Buffer::operator+=(Network::byte_t)");
    } else {
         runtest.fail ("Buffer::operator+=(Network::byte_t)");
    }

    // Append a number
    double num = 1.2345;
    Buffer buf3;
    buf3.clear();
    buf3.copy(data1, 10);
    buf3 += num;
    
    memset(data3, 0, 20);
    memcpy(data3, data1, 10);
    memcpy(data3 + 10, &num, sizeof(double));
    if (memcmp(data3, buf3.reference(), 10+sizeof(double)) == 0) {
         runtest.pass ("Buffer::operator+=(double)");
    } else {
         runtest.fail ("Buffer::operator+=(double)");
    }

    string str1 = "Writing test cases";
    const char *str2 = "is so tedious";
    string str3 = str1 + str2;
    Buffer buf6(50);
    buf6 = str1;
    buf6 += str2;
    if (memcmp(buf6.reference(), str3.c_str(), str3.size()) == 0) {
        runtest.pass ("Buffer::operator+=(const string &)");
    } else {
        runtest.fail ("Buffer::operator+=(const string &)");
    }

    std::uint16_t length = 1047;
    Buffer buf7(70);
    buf7.copy(data1, 10);
    buf7 += length;
    if (memcmp(buf7.reference() + 10, &length, sizeof(std::uint16_t)) == 0) {
        runtest.pass ("Buffer::operator+=(std::uint16_t)");
    } else {
        runtest.fail ("Buffer::operator+=(std::uint16_t)");
    }

    buf7 += buf6;
    // Network::byte_t *ptr1 = buf7.reference() + 10 + sizeof(std::uint16_t);
    // Network::byte_t *ptr2 = buf6.reference();
    if (memcmp(buf7.reference() + 10 + sizeof(std::uint16_t), buf6.reference(), 30) == 0) {
        runtest.pass ("Buffer::operator+=(Buffer &)");
    } else {
        runtest.fail ("Buffer::operator+=(Buffer &)");
    }

    bool flag = true;
    Buffer buf8;
    buf8.copy(data1, 10);
    buf8 += flag;
    if (*(buf8.reference() + 10) == 1) {
        runtest.pass ("Buffer::operator+=(bool)");
    } else {
        runtest.fail ("Buffer::operator+=(bool)");
    }
    
    // Clean up temporary data
    delete[] data1;
    delete[] data2;
    delete[] data3;
    
}

void
test_remove()
{
    Network::byte_t *data1 = new Network::byte_t[20];
    memset(data1, 0, 20);
    Network::byte_t *data2 = new Network::byte_t[20];
    memset(data2, 0, 20);
    Network::byte_t *data3 = new Network::byte_t[20];
    memset(data3, 0, 20);

    // populate a buffer with some data
    for (size_t i=0; i< 19; i++) {
        data1[i] = i + 'a';
    }

    // Build identical buffer nissing one character
    memcpy(data2, data1, 6);
    memcpy(data2 + 6, data1 + 7, 20-7);

    // Remove a single byte
    Network::byte_t byte = 'g';
    Buffer buf1(20);
    buf1.clear();
    buf1.copy(data1, 20);
    buf1.remove(byte);
    if (memcmp(data2, buf1.reference(), 19) == 0) {
         runtest.pass ("Buffer::remove(Network::byte_t)");
    } else {
         runtest.fail ("Buffer::remove(Network::byte_t)");
    }
    
    Buffer buf2(20);
    buf2.clear();
    buf2.copy(data1, 20);
    buf2.remove(6);
    if (memcmp(data2, buf2.reference(), 18) == 0) {
         runtest.pass ("Buffer::remove(int)");
    } else {
         runtest.fail ("Buffer::remove(int)");
    }

    // Remove a range of bytes
    memcpy(data3, data1, 6);
    memcpy(data3 + 6, data1 + 9, 1);
    
    Buffer buf3(20);
    buf3.clear();
    buf3.copy(data1, 20);
    buf3.remove(6, 8);
    if (memcmp(data3, buf3.reference(), 6) == 0) {
         runtest.pass ("Buffer::remove(int, int)");
    } else {
         runtest.fail ("Buffer::remove(int, int)");
    }

    delete[] data1;
    delete[] data2;
    delete[] data3;
}

void
test_construct()
{
// these tests are bogus unless you have both mallinfo()
// and also have memory statistics gathering turned on.
#if defined(HAVE_MALLINFO) && defined(USE_STATS_MEMORY)
    bool valgrind = false;
    
    size_t fudge = sizeof(long *)*5;
    
    Memory mem(5);
    mem.addStats(__LINE__);             // take a sample
    Buffer buf1;
    mem.addStats(__LINE__);             // take a sample
    size_t diff = mem.diffStats() - sizeof(buf1);    
    if (diff > NETBUFSIZE) {
        valgrind = true;
        log_debug("Running this test case under valgrind screws up mallinfo(), so the results get skewed");
    }
    // Different systems allocate memory slightly differently, so about all we can do to see
    // if it worked is check to make sure it's within a tight range of possible values.
     if ((buf1.size() == NETBUFSIZE) && (diff >= (NETBUFSIZE - fudge)) && diff <= (NETBUFSIZE + fudge)) {
        runtest.pass ("Buffer::Buffer()");
    } else {
        if (valgrind) {
            runtest.unresolved("Buffer::Buffer()) under valgrind");
        } else {
            runtest.fail("Buffer::Buffer()");
        }
    }
    
    mem.addStats(__LINE__);             // take a sample
    Buffer buf2(124);
    mem.addStats(__LINE__);             // take a sample
    diff = mem.diffStats() - sizeof(long *);
    if ((buf2.size() == 124) && (124 - fudge) && diff <= (124 + fudge)) {
        runtest.pass ("Buffer::Buffer(size_t)");
    } else {
        if (valgrind) {
            runtest.unresolved("Buffer::Buffer(size_t) under valgrind");
        } else {
            runtest.fail("Buffer::Buffer(size_t)");
        }
    }
#endif
}

// make sure when we delete a Buffer, *all* the allocated
// memory goes away. As the only way to do this is to examine
// the malloc buffers in the kernel, this will only work on
// POSIX conforming systems, and probabably only Linux & BSD.
void
test_destruct()
{
// these tests are bogus unless you have both mallinfo()
// and also have memory statistics gathering turned on.
#if defined(HAVE_MALLINFO) && defined(USE_STATS_MEMORY)
    Memory mem(5);
    mem.addStats(__LINE__);             // take a sample
    Buffer *buf1, *buf2;

    mem.startCheckpoint();
    buf1 = new Buffer(NETBUFSIZE);
    delete buf1;
    
    if (mem.endCheckpoint()) {
        runtest.pass ("Buffer::~Buffer()");
    } else {
        runtest.fail ("Buffer::~Buffer()");
    }

    mem.startCheckpoint();
    buf2 = new Buffer(124);
    delete buf2;
    
    if (mem.endCheckpoint()) {
        runtest.pass ("Buffer::~Buffer(size_t)");
    } else {
        runtest.fail ("Buffer::~Buffer(size_t)");
    }
#endif
}

void
test_operators()
{
    // Make some data for the buffers
    Buffer buf1, buf2;
    // valgrind gets pissed unless we zero the memory. Constructing
    // a buffer doesn't clear the memory to save speed, and it's
    // also unnecessary normally, but makes debugging easier.
    buf1.clear();
    buf2.clear();

    std::uint8_t *ptr1 = buf1.reference();
    for (size_t i=1; i< buf1.size(); i++) {
        ptr1[i] = i;
    }

    // buf1 has data, but buf2 doesn't, so if the
    // equivalance test fails, then this passed.
    buf2.clear();
    if (buf2 == buf1) {
         runtest.fail ("Buffer::operator==(Buffer &)");
     } else {
         runtest.pass ("Buffer::operator==(Buffer &)");
    }

    // This makes the new buffer be identical to the
    // the source buffer, including copying all the data.
    buf2 = buf1;
    if (buf1 == buf2) {
         runtest.pass ("Buffer::operator=(Buffer &)");
     } else {
         runtest.fail ("Buffer::operator=(Buffer &)");
    }

    Buffer *buf3, *buf4;
    buf3 = new Buffer;
    std::uint8_t *ptr2 = buf3->reference();
    for (size_t i=1; i< buf3->size(); i++) {
        ptr2[i] = i + 'a';
    }
    buf4 = new Buffer;
    if (buf3 == buf4) {
         runtest.fail ("Buffer::operator==(Buffer *)");
     } else {
         runtest.pass ("Buffer::operator==(Buffer *)");
    }
    delete buf4;

    // This makes the new buffer be identical to the
    // the source buffer, including copying all the data.
    buf4 = buf3;
    if (buf3 == buf4) {
         runtest.pass ("Buffer::operator=(Buffer *)");
    } else {
         runtest.fail ("Buffer::operator=(Buffer *)");
    }
    delete buf3;
    
    Buffer buf5(10);
    buf5.clear();
    std::uint8_t *ptr3 = buf5.reference();
    buf5 += 'a';
    buf5 += 'b';
    buf5 += 'c';
    if (memcmp(ptr3, "abc", 3) == 0) {
         runtest.pass ("Buffer::operator+=(char)");
    } else {
         runtest.fail ("Buffer::operator+=(char)");
    }

    Buffer buf6(6);
    buf6.clear();
    buf6 += 'D';
    buf6 += 'E';
    buf6 += 'F';    
    buf5 += buf6;
    ptr3 = buf5.reference();    // refresh the pointer, as it changes
                                // on a resize()
    // when appending Buffers, this destination has enough space to hold
    // the allocated bytes from the source.
    if ((memcmp(ptr3, "abcDEF", 6) == 0) && (buf5.size() == 10)) {
         runtest.pass ("Buffer::operator+=(Buffer &)");
    } else {
         runtest.fail ("Buffer::operator+=(Buffer &)");
    }

    bool caught = false;

    // make the source Buffer have more data
    buf6 += 'A';
    buf6 += 'B';
    buf6 += 'C';
    try {
        buf5 += buf6;
    }

    catch (GnashException& ge) {
        caught = true;
//        log_debug("Got exeception from operator+=: %s", ge.what());
    }
    // when appending Buffers, this destination doesn't have enough space to
    // hold the allocated bytes from the source, so is supposed to throw an
    //exception.
    if (caught) {
         runtest.pass ("Buffer::operator+=(Buffer &) error");
    } else {
         runtest.fail ("Buffer::operator+=(Buffer &) error");
    }
}

static void
usage()
{
    cout << _("test_buffer - test Buffer class") << endl
         << endl
         << _("Usage: test_buffer [options...]") << endl
         << _("  -h,  --help          Print this help and exit") << endl
         << _("  -v,  --verbose       Output verbose debug info") << endl
         << _("  -m,  --memdebug      Output memory statistics") << endl
         << endl;
}

#else

int
main(int /*argc*/, char** /* *argv */)
{
  // nop
  return 0;  
}

#endif
