/**
 * @file llrngwriter.cpp
 * @brief Generates Relax NG schema from param blocks
 *
 * $LicenseInfo:firstyear=2003&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * 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;
 * version 2.1 of the License only.
 *
 * 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
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "llrngwriter.h"
#include "lluicolor.h"

#include "lluictrlfactory.h"

#include "boost/bind.hpp"

static  LLInitParam::Parser::parser_read_func_map_t sReadFuncs;
static  LLInitParam::Parser::parser_write_func_map_t sWriteFuncs;
static  LLInitParam::Parser::parser_inspect_func_map_t sInspectFuncs;

//
// LLRNGWriter - writes Relax NG schema files based on a param block
//
LLRNGWriter::LLRNGWriter()
: Parser(sReadFuncs, sWriteFuncs, sInspectFuncs)
{
    // register various callbacks for inspecting the contents of a param block
    registerInspectFunc<bool>(boost::bind(&LLRNGWriter::writeAttribute, this, "boolean", _1, _2, _3, _4));
    registerInspectFunc<std::string>(boost::bind(&LLRNGWriter::writeAttribute, this, "string", _1, _2, _3, _4));
    registerInspectFunc<U8>(boost::bind(&LLRNGWriter::writeAttribute, this, "unsignedByte", _1, _2, _3, _4));
    registerInspectFunc<S8>(boost::bind(&LLRNGWriter::writeAttribute, this, "signedByte", _1, _2, _3, _4));
    registerInspectFunc<U16>(boost::bind(&LLRNGWriter::writeAttribute, this, "unsignedShort", _1, _2, _3, _4));
    registerInspectFunc<S16>(boost::bind(&LLRNGWriter::writeAttribute, this, "signedShort", _1, _2, _3, _4));
    registerInspectFunc<U32>(boost::bind(&LLRNGWriter::writeAttribute, this, "unsignedInt", _1, _2, _3, _4));
    registerInspectFunc<S32>(boost::bind(&LLRNGWriter::writeAttribute, this, "integer", _1, _2, _3, _4));
    registerInspectFunc<F32>(boost::bind(&LLRNGWriter::writeAttribute, this, "float", _1, _2, _3, _4));
    registerInspectFunc<F64>(boost::bind(&LLRNGWriter::writeAttribute, this, "double", _1, _2, _3, _4));
    registerInspectFunc<LLColor4>(boost::bind(&LLRNGWriter::writeAttribute, this, "string", _1, _2, _3, _4));
    registerInspectFunc<LLUIColor>(boost::bind(&LLRNGWriter::writeAttribute, this, "string", _1, _2, _3, _4));
    registerInspectFunc<LLUUID>(boost::bind(&LLRNGWriter::writeAttribute, this, "string", _1, _2, _3, _4));
    registerInspectFunc<LLSD>(boost::bind(&LLRNGWriter::writeAttribute, this, "string", _1, _2, _3, _4));
}

void LLRNGWriter::writeRNG(const std::string& type_name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace)
{
    mGrammarNode = node;
    mGrammarNode->setName("grammar");
    mGrammarNode->createChild("xmlns", true)->setStringValue("http://relaxng.org/ns/structure/1.0");
    mGrammarNode->createChild("datatypeLibrary", true)->setStringValue("http://www.w3.org/2001/XMLSchema-datatypes");
    mGrammarNode->createChild("ns", true)->setStringValue(xml_namespace);

    node = mGrammarNode->createChild("start", false);
    node = node->createChild("ref", false);
    node->createChild("name", true)->setStringValue(type_name);

    addDefinition(type_name, block);
}

void LLRNGWriter::addDefinition(const std::string& type_name, const LLInitParam::BaseBlock& block)
{
    if (mDefinedElements.find(type_name) != mDefinedElements.end()) return;
    mDefinedElements.insert(type_name);

    LLXMLNodePtr node = mGrammarNode->createChild("define", false);
    node->createChild("name", true)->setStringValue(type_name);

    mElementNode = node->createChild("element", false);
    mElementNode->createChild("name", true)->setStringValue(type_name);
    mChildrenNode = mElementNode->createChild("zeroOrMore", false)->createChild("choice", false);

    mAttributesWritten.first = mElementNode;
    mAttributesWritten.second.clear();
    mElementsWritten.clear();

    block.inspectBlock(*this);

    // add includes for all possible children
    const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name);
    const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type);

    // add include declarations for all valid children
    for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems();
        it != widget_registryp->currentRegistrar().endItems();
        ++it)
    {
        std::string child_name = it->first;
        if (child_name == type_name)
        {
            continue;
        }

        LLXMLNodePtr old_element_node = mElementNode;
        LLXMLNodePtr old_child_node = mChildrenNode;
        //FIXME: add LLDefaultParamBlockRegistry back when working on schema generation
        //addDefinition(child_name, (*LLDefaultParamBlockRegistry::instance().getValue(type))());
        mElementNode = old_element_node;
        mChildrenNode = old_child_node;

        mChildrenNode->createChild("ref", false)->createChild("name", true)->setStringValue(child_name);
    }

    if (mChildrenNode->mChildren.isNull())
    {
        // remove unused children node
        mChildrenNode->mParent->mParent->deleteChild(mChildrenNode->mParent);
    }
}

void LLRNGWriter::writeAttribute(const std::string& type, const Parser::name_stack_t& stack, S32 min_count, S32 max_count, const std::vector<std::string>* possible_values)
{
    if (max_count == 0) return;

    name_stack_t non_empty_names;
    std::string attribute_name;
    for (name_stack_t::const_iterator it = stack.begin();
        it != stack.end();
        ++it)
    {
        const std::string& name = it->first;
        if (!name.empty())
        {
            non_empty_names.push_back(*it);
        }
    }

    if (non_empty_names.empty()) return;

    for (name_stack_t::const_iterator it = non_empty_names.begin();
        it != non_empty_names.end();
        ++it)
    {
        if (!attribute_name.empty())
        {
            attribute_name += ".";
        }
        attribute_name += it->first;
    }

    // singular attribute, e.g. <foo bar="1"/>
    if (non_empty_names.size() == 1 && max_count == 1)
    {
        if (mAttributesWritten.second.find(attribute_name) == mAttributesWritten.second.end())
        {
            LLXMLNodePtr node = createCardinalityNode(mElementNode, min_count, max_count)->createChild("attribute", false);
            node->createChild("name", true)->setStringValue(attribute_name);
            node->createChild("data", false)->createChild("type", true)->setStringValue(type);

            mAttributesWritten.second.insert(attribute_name);
        }
    }
    // compound attribute
    else
    {
        std::string element_name;

        // traverse all but last element, leaving that as an attribute name
        name_stack_t::const_iterator end_it = non_empty_names.end();
        end_it--;

        for (name_stack_t::const_iterator it = non_empty_names.begin();
            it != end_it;
            ++it)
        {
            if (it != non_empty_names.begin())
            {
                element_name += ".";
            }
            element_name += it->first;
        }

        elements_map_t::iterator found_it = mElementsWritten.find(element_name);
        // <choice>
        //   <group>
        //     <optional>
        //       <attribute name="foo.bar"><data type="string"/></attribute>
        //     </optional>
        //     <optional>
        //       <attribute name="foo.baz"><data type="integer"/></attribute>
        //     </optional>
        //   </group>
        //   <element name="foo">
        //     <optional>
        //       <attribute name="bar"><data type="string"/></attribute>
        //     </optional>
        //     <optional>
        //       <attribute name="baz"><data type="string"/></attribute>
        //     </optional>
        //   </element>
        //   <element name="outer.foo">
        //     <ref name="foo"/>
        //   </element>
        // </choice>

        if (found_it != mElementsWritten.end())
        {
            // reuse existing element
            LLXMLNodePtr choice_node = found_it->second.first;

            // attribute with this name not already written?
            if (found_it->second.second.find(attribute_name) == found_it->second.second.end())
            {
                // append to <group>
                LLXMLNodePtr node = choice_node->mChildren->head;
                node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false);
                node->createChild("name", true)->setStringValue(attribute_name);
                addTypeNode(node, type, possible_values);

                // append to <element>
                node = choice_node->mChildren->head->mNext->mChildren->head;
                node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false);
                node->createChild("name", true)->setStringValue(non_empty_names.back().first);
                addTypeNode(node, type, possible_values);

                // append to <element>
                //node = choice_node->mChildren->head->mNext->mNext->mChildren->head;
                //node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false);
                //node->createChild("name", true)->setStringValue(non_empty_names.back().first);
                //addTypeNode(node, type, possible_values);

                found_it->second.second.insert(attribute_name);
            }
        }
        else
        {
            LLXMLNodePtr choice_node = mElementNode->createChild("choice", false);

            LLXMLNodePtr node = choice_node->createChild("group", false);
            node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false);
            node->createChild("name", true)->setStringValue(attribute_name);
            addTypeNode(node, type, possible_values);

            node = choice_node->createChild("optional", false);
            node = node->createChild("element", false);
            node->createChild("name", true)->setStringValue(element_name);
            node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false);
            node->createChild("name", true)->setStringValue(non_empty_names.back().first);
            addTypeNode(node, type, possible_values);

            //node = choice_node->createChild("optional", false);
            //node = node->createChild("element", false);
            //node->createChild("name", true)->setStringValue(mDefinitionName + "." + element_name);
            //node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false);
            //node->createChild("name", true)->setStringValue(non_empty_names.back().first);
            //addTypeNode(node, type, possible_values);

            attribute_data_t& attribute_data = mElementsWritten[element_name];
            attribute_data.first = choice_node;
            attribute_data.second.insert(attribute_name);
        }
    }
}

void LLRNGWriter::addTypeNode(LLXMLNodePtr parent_node, const std::string& type, const std::vector<std::string>* possible_values)
{
    if (possible_values)
    {
        LLXMLNodePtr enum_node = parent_node->createChild("choice", false);
        for (std::vector<std::string>::const_iterator it = possible_values->begin();
            it != possible_values->end();
            ++it)
        {
            enum_node->createChild("value", false)->setStringValue(*it);
        }
    }
    else
    {
        parent_node->createChild("data", false)->createChild("type", true)->setStringValue(type);
    }
}

LLXMLNodePtr LLRNGWriter::createCardinalityNode(LLXMLNodePtr parent_node, S32 min_count, S32 max_count)
{
    // unlinked by default, meaning this attribute is forbidden
    LLXMLNodePtr count_node = new LLXMLNode();
    if (min_count == 0)
    {
        if (max_count == 1)
        {
            count_node = parent_node->createChild("optional", false);
        }
        else if (max_count > 1)
        {
            count_node = parent_node->createChild("zeroOrMore", false);
        }
    }
    else if (min_count >= 1)
    {
        if (max_count == 1 && min_count == 1)
        {
            // just add raw element, will count as 1 and only 1
            count_node = parent_node;
        }
        else
        {
            count_node = parent_node->createChild("oneOrMore", false);
        }
    }
    return count_node;
}