#include "lib/self_test.h"

#include "lib/base32.h"
#include "lib/res/file/path.h"
#include "lib/res/file/file.h"
#include "lib/res/file/file_cache.h"
#include "lib/res/file/vfs.h"
#include "lib/res/file/archive.h"
#include "lib/res/file/archive_builder.h"
#include "lib/res/h_mgr.h"
#include "lib/res/mem.h"
#include "lib/rand.h"

class TestArchiveBuilder : public CxxTest::TestSuite 
{
    const char* const archive_fn;
    static const size_t NUM_FILES = 30;
    static const size_t MAX_FILE_SIZE = 20000;

    std::set<const char*> existing_names;
    const char* gen_random_name()
    {
        // 10 chars is enough for (10-1)*5 bits = 45 bits > u32
        char name_tmp[10];

        for(;;)
        {
            u32 rand_num = rand(0, 100000);
            base32(4, (const u8*)&rand_num, (u8*)name_tmp);

            // store filename in atom pool
            const char* atom_fn = file_make_unique_fn_copy(name_tmp);
            // done if the filename is unique (not been generated yet)
            if(existing_names.find(atom_fn) == existing_names.end())
            {
                existing_names.insert(atom_fn);
                return atom_fn;
            }
        }
    }

    struct TestFile
    {
        off_t size;
        u8* data;   // must be delete[]-ed after comparing
    };
    // (must be separate array and end with NULL entry (see Filenames))
    const char* filenames[NUM_FILES+1];
    TestFile files[NUM_FILES];

    void generate_random_files()
    {
        for(size_t i = 0; i < NUM_FILES; i++)
        {
            const off_t size = rand(0, MAX_FILE_SIZE);
            u8* data = new u8[size];

            // random data won't compress at all, and we want to exercise
            // the uncompressed codepath as well => make some of the files
            // easily compressible (much less values).
            const bool make_easily_compressible = (rand(0, 100) > 50);
            if(make_easily_compressible)
            {
                for(off_t i = 0; i < size; i++)
                    data[i] = rand() & 0x0F;
            }
            else
            {
                for(off_t i = 0; i < size; i++)
                    data[i] = rand() & 0xFF;
            }

            filenames[i] = gen_random_name();
            files[i].size = size;
            files[i].data = data;

            ssize_t bytes_written = vfs_store(filenames[i], data, size, FILE_NO_AIO);
            TS_ASSERT_EQUALS(bytes_written, size);
        }

        // 0-terminate the list - see Filenames decl.
        filenames[NUM_FILES] = NULL;
    }

public:
    TestArchiveBuilder()
        : archive_fn("test_archive_random_data.zip") {}

    void setUp()
    {
        (void)file_init();
        (void)file_set_root_dir(0, ".");
        vfs_init();
    }

    void tearDown()
    {
        vfs_shutdown();
        file_shutdown();
        path_reset_root_dir();
    }

    void test_create_archive_with_random_files()
    {
        if(!file_exists("archivetest")) // don't get stuck if this test fails and never deletes the directory it created
            TS_ASSERT_OK(dir_create("archivetest"));

        TS_ASSERT_OK(vfs_mount("", "archivetest"));

        generate_random_files();
        TS_ASSERT_OK(archive_build(archive_fn, filenames));

        // wipe out file cache, otherwise we're just going to get back
        // the file contents read during archive_build .
        file_cache_reset();

        // read in each file and compare file contents
        Handle ha = archive_open(archive_fn);
        TS_ASSERT(ha > 0);
        for(size_t i = 0; i < NUM_FILES; i++)
        {
            File f;
            TS_ASSERT_OK(afile_open(ha, filenames[i], 0, 0, &f));
            FileIOBuf buf = FILE_BUF_ALLOC;
            ssize_t bytes_read = afile_read(&f, 0, files[i].size, &buf);
            TS_ASSERT_EQUALS(bytes_read, files[i].size);

            TS_ASSERT_SAME_DATA(buf, files[i].data, files[i].size);

            TS_ASSERT_OK(file_buf_free(buf));
            TS_ASSERT_OK(afile_close(&f));
            SAFE_ARRAY_DELETE(files[i].data);
        }
        TS_ASSERT_OK(archive_close(ha));

        dir_delete("archivetest");
        file_delete(archive_fn);
    }

    void test_multiple_init_shutdown()
    {
        // setUp has already vfs_init-ed it and tearDown will vfs_shutdown.
        vfs_shutdown();
        vfs_init();
    }
};