/*-------------------------------------------------------------------------
 * drawElements Quality Program Test Executor
 * ------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Test case.
 *//*--------------------------------------------------------------------*/

#include "xeTestCase.hpp"

using std::vector;

namespace xe
{

const char *getTestCaseTypeName(TestCaseType caseType)
{
    switch (caseType)
    {
    case TESTCASETYPE_SELF_VALIDATE:
        return "SelfValidate";
    case TESTCASETYPE_CAPABILITY:
        return "Capability";
    case TESTCASETYPE_ACCURACY:
        return "Accuracy";
    case TESTCASETYPE_PERFORMANCE:
        return "Performance";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

static inline int getFirstComponentLength(const char *path)
{
    int compLen = 0;
    while (path[compLen] != 0 && path[compLen] != '.')
        compLen++;
    return compLen;
}

static bool compareNameToPathComponent(const char *name, const char *path, int compLen)
{
    for (int pos = 0; pos < compLen; pos++)
    {
        if (name[pos] != path[pos])
            return false;
    }

    if (name[compLen] != 0)
        return false;

    return true;
}

static void splitPath(const char *path, std::vector<std::string> &components)
{
    std::string pathStr(path);
    int compStart = 0;

    for (int pos = 0; pos < (int)pathStr.length(); pos++)
    {
        if (pathStr[pos] == '.')
        {
            components.push_back(pathStr.substr(compStart, pos - compStart));
            compStart = pos + 1;
        }
    }

    DE_ASSERT(compStart < (int)pathStr.length());
    components.push_back(pathStr.substr(compStart));
}

// TestNode

TestNode::TestNode(TestGroup *parent, TestNodeType nodeType, const char *name)
    : m_parent(parent)
    , m_nodeType(nodeType)
    , m_name(name)
{
    if (m_parent)
    {
        // Verify that the name is unique.
        if (parent->m_childNames.find(name) != parent->m_childNames.end())
            throw Error(std::string("Duplicate node '") + name + "' in '" + parent->getFullPath());

        m_parent->m_children.push_back(this);
        m_parent->m_childNames.insert(name);
    }
}

void TestNode::getFullPath(std::string &dst) const
{
    dst.clear();

    int nameLen             = 0;
    const TestNode *curNode = this;

    for (;;)
    {
        nameLen += (int)curNode->m_name.length();

        DE_ASSERT(curNode->m_parent);
        if (curNode->m_parent->getNodeType() != TESTNODETYPE_ROOT)
        {
            nameLen += 1;
            curNode = curNode->m_parent;
        }
        else
            break;
    }

    dst.resize(nameLen);

    curNode = this;
    int pos = nameLen;

    for (;;)
    {
        std::copy(curNode->m_name.begin(), curNode->m_name.end(), dst.begin() + (pos - curNode->m_name.length()));
        pos -= (int)curNode->m_name.length();

        DE_ASSERT(curNode->m_parent);
        if (curNode->m_parent->getNodeType() != TESTNODETYPE_ROOT)
        {
            dst[--pos] = '.';
            curNode    = curNode->m_parent;
        }
        else
            break;
    }
}

const TestNode *TestNode::find(const char *path) const
{
    if (m_nodeType == TESTNODETYPE_ROOT)
    {
        // Don't need to consider current node.
        return static_cast<const TestGroup *>(this)->findChildNode(path);
    }
    else
    {
        // Check if first component matches this node.
        int compLen = getFirstComponentLength(path);
        XE_CHECK(compLen > 0);

        if (compareNameToPathComponent(getName(), path, compLen))
        {
            if (path[compLen] == 0)
                return this;
            else if (getNodeType() == TESTNODETYPE_GROUP)
                return static_cast<const TestGroup *>(this)->findChildNode(path + compLen + 1);
            else
                return DE_NULL;
        }
        else
            return DE_NULL;
    }
}

TestNode *TestNode::find(const char *path)
{
    return const_cast<TestNode *>(const_cast<const TestNode *>(this)->find(path));
}

// TestGroup

TestGroup::TestGroup(TestGroup *parent, TestNodeType nodeType, const char *name) : TestNode(parent, nodeType, name)
{
    DE_ASSERT(nodeType == TESTNODETYPE_GROUP || nodeType == TESTNODETYPE_ROOT);
    DE_ASSERT(!parent == (nodeType == TESTNODETYPE_ROOT));
}

TestGroup::~TestGroup(void)
{
    for (std::vector<TestNode *>::iterator i = m_children.begin(); i != m_children.end(); i++)
        delete *i;
}

TestGroup *TestGroup::createGroup(const char *name)
{
    return new TestGroup(this, TESTNODETYPE_GROUP, name);
}

TestCase *TestGroup::createCase(TestCaseType caseType, const char *name)
{
    return TestCase::createAsChild(this, caseType, name);
}

const TestNode *TestGroup::findChildNode(const char *path) const
{
    int compLen = getFirstComponentLength(path);
    XE_CHECK(compLen > 0);

    // Try to find matching children.
    const TestNode *matchingNode = DE_NULL;
    for (vector<TestNode *>::const_iterator iter = m_children.begin(); iter != m_children.end(); iter++)
    {
        if (compareNameToPathComponent((*iter)->getName(), path, compLen))
        {
            matchingNode = *iter;
            break;
        }
    }

    if (matchingNode)
    {
        if (path[compLen] == 0)
            return matchingNode; // Last element in path, return matching node.
        else if (matchingNode->getNodeType() == TESTNODETYPE_GROUP)
            return static_cast<const TestGroup *>(matchingNode)->findChildNode(path + compLen + 1);
        else
            return DE_NULL;
    }
    else
        return DE_NULL;
}

// TestRoot

TestRoot::TestRoot(void) : TestGroup(DE_NULL, TESTNODETYPE_ROOT, "")
{
}

// TestCase

TestCase *TestCase::createAsChild(TestGroup *parent, TestCaseType caseType, const char *name)
{
    return new TestCase(parent, caseType, name);
}

TestCase::TestCase(TestGroup *parent, TestCaseType caseType, const char *name)
    : TestNode(parent, TESTNODETYPE_TEST_CASE, name)
    , m_caseType(caseType)
{
}

TestCase::~TestCase(void)
{
}

// TestHierarchyBuilder helpers

void addChildGroupsToMap(std::map<std::string, TestGroup *> &groupMap, TestGroup *group)
{
    for (int ndx = 0; ndx < group->getNumChildren(); ndx++)
    {
        TestNode *node = group->getChild(ndx);
        if (node->getNodeType() == TESTNODETYPE_GROUP)
        {
            TestGroup *childGroup = static_cast<TestGroup *>(node);
            std::string fullPath;
            childGroup->getFullPath(fullPath);

            groupMap.insert(std::make_pair(fullPath, childGroup));
            addChildGroupsToMap(groupMap, childGroup);
        }
    }
}

// TestHierarchyBuilder

TestHierarchyBuilder::TestHierarchyBuilder(TestRoot *root) : m_root(root)
{
    addChildGroupsToMap(m_groupMap, root);
}

TestHierarchyBuilder::~TestHierarchyBuilder(void)
{
}

TestCase *TestHierarchyBuilder::createCase(const char *path, TestCaseType caseType)
{
    // \todo [2012-09-05 pyry] This can be done with less string manipulations.
    std::vector<std::string> components;
    splitPath(path, components);
    DE_ASSERT(!components.empty());

    // Create all parents if necessary.
    TestGroup *curGroup = m_root;
    std::string curGroupPath;
    for (int ndx = 0; ndx < (int)components.size() - 1; ndx++)
    {
        if (!curGroupPath.empty())
            curGroupPath += ".";
        curGroupPath += components[ndx];

        std::map<std::string, TestGroup *>::const_iterator groupPos = m_groupMap.find(curGroupPath);
        if (groupPos == m_groupMap.end())
        {
            TestGroup *newGroup = curGroup->createGroup(components[ndx].c_str());
            m_groupMap.insert(std::make_pair(curGroupPath, newGroup));
            curGroup = newGroup;
        }
        else
            curGroup = groupPos->second;
    }

    return curGroup->createCase(caseType, components.back().c_str());
}

// TestSet helpers

static void addNodeAndParents(std::set<const TestNode *> &nodeSet, const TestNode *node)
{
    while (node != DE_NULL)
    {
        nodeSet.insert(node);
        node = node->getParent();
    }
}

static void addChildren(std::set<const TestNode *> &nodeSet, const TestGroup *group)
{
    for (int ndx = 0; ndx < group->getNumChildren(); ndx++)
    {
        const TestNode *child = group->getChild(ndx);
        nodeSet.insert(child);

        if (child->getNodeType() == TESTNODETYPE_GROUP)
            addChildren(nodeSet, static_cast<const TestGroup *>(child));
    }
}

static void removeChildren(std::set<const TestNode *> &nodeSet, const TestGroup *group)
{
    for (int ndx = 0; ndx < group->getNumChildren(); ndx++)
    {
        const TestNode *child = group->getChild(ndx);
        nodeSet.erase(child);

        if (child->getNodeType() == TESTNODETYPE_GROUP)
            removeChildren(nodeSet, static_cast<const TestGroup *>(child));
    }
}

static bool hasChildrenInSet(const std::set<const TestNode *> &nodeSet, const TestGroup *group)
{
    for (int ndx = 0; ndx < group->getNumChildren(); ndx++)
    {
        if (nodeSet.find(group->getChild(ndx)) != nodeSet.end())
            return true;
    }
    return false;
}

static void removeEmptyGroups(std::set<const TestNode *> &nodeSet, const TestGroup *group)
{
    if (!hasChildrenInSet(nodeSet, group))
    {
        nodeSet.erase(group);
        if (group->getParent() != DE_NULL)
            removeEmptyGroups(nodeSet, group->getParent());
    }
}

// TestSet

void TestSet::add(const TestNode *node)
{
    if (node->getNodeType() == TESTNODETYPE_TEST_CASE)
        addCase(static_cast<const TestCase *>(node));
    else
    {
        XE_CHECK(node->getNodeType() == TESTNODETYPE_GROUP || node->getNodeType() == TESTNODETYPE_ROOT);
        addGroup(static_cast<const TestGroup *>(node));
    }
}

void TestSet::addCase(const TestCase *testCase)
{
    addNodeAndParents(m_set, testCase);
}

void TestSet::addGroup(const TestGroup *testGroup)
{
    addNodeAndParents(m_set, testGroup);
    addChildren(m_set, testGroup);
}

void TestSet::remove(const TestNode *node)
{
    if (node->getNodeType() == TESTNODETYPE_TEST_CASE)
        removeCase(static_cast<const TestCase *>(node));
    else
    {
        XE_CHECK(node->getNodeType() == TESTNODETYPE_GROUP || node->getNodeType() == TESTNODETYPE_ROOT);
        removeGroup(static_cast<const TestGroup *>(node));
    }
}

void TestSet::removeCase(const TestCase *testCase)
{
    if (m_set.find(testCase) != m_set.end())
    {
        m_set.erase(testCase);
        removeEmptyGroups(m_set, testCase->getParent());
    }
}

void TestSet::removeGroup(const TestGroup *testGroup)
{
    if (m_set.find(testGroup) != m_set.end())
    {
        m_set.erase(testGroup);
        removeChildren(m_set, testGroup);
        if (testGroup->getParent() != DE_NULL)
            removeEmptyGroups(m_set, testGroup->getParent());
    }
}

// ConstTestNodeIterator

ConstTestNodeIterator::ConstTestNodeIterator(const TestNode *root) : m_root(root)
{
}

ConstTestNodeIterator ConstTestNodeIterator::begin(const TestNode *root)
{
    ConstTestNodeIterator iter(root);
    iter.m_iterStack.push_back(GroupState(DE_NULL));
    return iter;
}

ConstTestNodeIterator ConstTestNodeIterator::end(const TestNode *root)
{
    DE_UNREF(root);
    return ConstTestNodeIterator(root);
}

ConstTestNodeIterator &ConstTestNodeIterator::operator++(void)
{
    DE_ASSERT(!m_iterStack.empty());

    const TestNode *curNode  = **this;
    TestNodeType curNodeType = curNode->getNodeType();

    if ((curNodeType == TESTNODETYPE_GROUP || curNodeType == TESTNODETYPE_ROOT) &&
        static_cast<const TestGroup *>(curNode)->getNumChildren() > 0)
    {
        m_iterStack.push_back(GroupState(static_cast<const TestGroup *>(curNode)));
    }
    else
    {
        for (;;)
        {
            const TestGroup *group = m_iterStack.back().group;
            int &childNdx          = m_iterStack.back().childNdx;
            int numChildren        = group ? group->getNumChildren() : 1;

            childNdx += 1;
            if (childNdx == numChildren)
            {
                m_iterStack.pop_back();
                if (m_iterStack.empty())
                    break;
            }
            else
                break;
        }
    }

    return *this;
}

ConstTestNodeIterator ConstTestNodeIterator::operator++(int)
{
    ConstTestNodeIterator copy(*this);
    ++(*this);
    return copy;
}

const TestNode *ConstTestNodeIterator::operator*(void) const
{
    DE_ASSERT(!m_iterStack.empty());
    if (m_iterStack.size() == 1)
    {
        DE_ASSERT(m_iterStack[0].group == DE_NULL && m_iterStack[0].childNdx == 0);
        return m_root;
    }
    else
        return m_iterStack.back().group->getChild(m_iterStack.back().childNdx);
}

bool ConstTestNodeIterator::operator!=(const ConstTestNodeIterator &other) const
{
    return m_root != other.m_root || m_iterStack != other.m_iterStack;
}

} // namespace xe
