//          Copyright Ferdinand Majerech 2011.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

module dyaml.test.common;

version(unittest)
{

import dyaml.node;
import dyaml.event;

import core.exception;
import std.algorithm;
import std.array;
import std.conv;
import std.file;
import std.range;
import std.path;
import std.traits;
import std.typecons;

package:

/**
Run a test.

Params:
    testFunction = Unittest function.
    unittestExt = Extensions of data files needed for the unittest.
    skipExt = Extensions that must not be used for the unittest.
 */
void run(D)(D testFunction, string[] unittestExt, string[] skipExt = [])
{
    immutable string dataDir = __FILE_FULL_PATH__.dirName ~  "/../../../test/data";
    auto testFilenames = findTestFilenames(dataDir);

    if (unittestExt.length > 0)
    {
        outer: foreach (base, extensions; testFilenames)
        {
            string[] filenames;
            foreach (ext; unittestExt)
            {
                if (!extensions.canFind(ext))
                {
                    continue outer;
                }
                filenames ~= base ~ '.' ~ ext;
            }
            foreach (ext; skipExt)
            {
                if (extensions.canFind(ext))
                {
                    continue outer;
                }
            }

            execute(testFunction, filenames);
        }
    }
    else
    {
        execute(testFunction, string[].init);
    }
}

// TODO: remove when a @safe ubyte[] file read can be done.
/**
Reads a file as an array of bytes.

Params:
    filename = Full path to file to read.

Returns: The file's data.
*/
ubyte[] readData(string filename) @trusted
{
    import std.file : read;
    return cast(ubyte[])read(filename);
}
void assertNodesEqual(const scope Node gotNode, const scope Node expectedNode) @safe
{
    import std.format : format;
    assert(gotNode == expectedNode, format!"got %s, expected %s"(gotNode.debugString, expectedNode.debugString));
}

/**
Determine if events in events1 are equivalent to events in events2.

Params:
    events1 = A range of events to compare with.
    events2 = A second range of events to compare.

Returns: true if the events are equivalent, false otherwise.
*/
bool compareEvents(T, U)(T events1, U events2)
if (isInputRange!T && isInputRange!U && is(ElementType!T == Event) && is(ElementType!U == Event))
{
    foreach (e1, e2; zip(events1, events2))
    {
        //Different event types.
        if (e1.id != e2.id)
        {
            return false;
        }
        //Different anchor (if applicable).
        if (e1.id.among!(EventID.sequenceStart, EventID.mappingStart, EventID.alias_, EventID.scalar)
            && e1.anchor != e2.anchor)
        {
            return false;
        }
        //Different collection tag (if applicable).
        if (e1.id.among!(EventID.sequenceStart, EventID.mappingStart) && e1.tag != e2.tag)
        {
            return false;
        }
        if (e1.id == EventID.scalar)
        {
            //Different scalar tag (if applicable).
            if (!(e1.implicit || e2.implicit) && e1.tag != e2.tag)
            {
                return false;
            }
            //Different scalar value.
            if (e1.value != e2.value)
            {
                return false;
            }
        }
    }
    return true;
}
/**
Throw an Error if events in events1 aren't equivalent to events in events2.

Params:
    events1 = First event array to compare.
    events2 = Second event array to compare.
*/
void assertEventsEqual(T, U)(T events1, U events2)
if (isInputRange!T && isInputRange!U && is(ElementType!T == Event) && is(ElementType!U == Event))
{
    auto events1Copy = events1.array;
    auto events2Copy = events2.array;
    assert(compareEvents(events1Copy, events2Copy), text("Got '", events1Copy, "', expected '", events2Copy, "'"));
}

private:

/**
Find unittest input filenames.

Params:  dir = Directory to look in.

Returns: Test input base filenames and their extensions.
*/
 //@trusted due to dirEntries
string[][string] findTestFilenames(const string dir) @trusted
{
    //Groups of extensions indexed by base names.
    string[][string] names;
    foreach (string name; dirEntries(dir, SpanMode.shallow))
    {
        if (isFile(name))
        {
            string base = name.stripExtension();
            string ext = name.extension();
            if (ext is null)
            {
                ext = "";
            }
            if (ext[0] == '.')
            {
                ext = ext[1 .. $];
            }

            //If the base name doesn't exist yet, add it; otherwise add new extension.
            names[base] = ((base in names) is null) ? [ext] : names[base] ~ ext;
        }
    }
    return names;
}

/**
Recursively copy an array of strings to a tuple to use for unittest function input.

Params:
    index = Current index in the array/tuple.
    tuple = Tuple to copy to.
    strings = Strings to copy.
*/
void stringsToTuple(uint index, F ...)(ref F tuple, const string[] strings)
in(F.length == strings.length)
do
{
    tuple[index] = strings[index];
    static if (index > 0)
    {
        stringsToTuple!(index - 1, F)(tuple, strings);
    }
}

/**
Execute an unittest on specified files.

Params:
    testName = Name of the unittest.
    testFunction = Unittest function.
    filenames = Names of input files to test with.
 */
void execute(D)(D testFunction, string[] filenames)
{
    //Convert filenames to parameters tuple and call the test function.
    alias F = Parameters!D[0..$];
    F parameters;
    stringsToTuple!(F.length - 1, F)(parameters, filenames);
    testFunction(parameters);
}

} // version(unittest)