//
//  omXmlTests.cpp
//  OmUtil
//
//  Created by David Van Brink on 3/27/15.
//  Copyright (c) 2015 omino.com. All rights reserved.
//

#include "omXmlTests.h"
#include "OmAsserts.h"

#include "Om.h"

#include <string>
#include <map>
#include <vector>


void testXmlOut1()
{
    OmXmlOutStreamChars ch;
    OmXmlWriter *w = new OmXmlWriter(&ch);
    
    
    
    w->setIndenting(0);
    w->beginElement("fish");
    w->addAttribute("a", "1");
    
    w->beginElement("cow");
    w->addAttribute("b", "2");
    w->endElement();
    w->endElement();
    
    delete w;
    
    const char* s = ch.get();
    ASSERT_EQUALS_STRING("simple xml", "<fish a=\"1\"><cow b=\"2\"/></fish>", s);
}

class Elemental
{
public:
    std::string name;
    std::map<std::string, std::string> attrs;
};
class MyHandler : public OmIXmlHandler
{
public:
    int elementCount = 0;
    int endElementCount = 0;
    int attributeCount = 0;
    
    std::vector<Elemental> elements;
    std::vector<std::string> elementEnds;
    void beginElement(S elementName,int attributeCount,S *attributeNames,S *attributeValues) override
    {
        this->elementCount++;
        this->attributeCount += attributeCount;
        
        Elemental e;
        e.name = elementName;
        for(int ix = 0; ix < attributeCount; ix++)
        {
            e.attrs[attributeNames[ix]] = attributeValues[ix];
        }
        this->elements.push_back(e);
    }
    
    void elementContent(S content) override    {
        
    }
    
    void endElement(S elementName) override
    {
        this->endElementCount++;
        this->elementEnds.push_back(elementName);
    }
    
};

OMTEST(testXmlReader1)
{
    const char *x = "<top a='1' b='2'><middle x='23' y='42'/></top>";
    OmXmlInStreamChars ch(x);
    MyHandler *handler = new MyHandler();
    OmXmlReader::readXml(&ch, handler);
    ASSERT_EQUALS_INT("elements?", 2, handler->elementCount);
    ASSERT_EQUALS_INT("elements?", 2, handler->endElementCount);
    ASSERT_EQUALS_INT("attrs?", 4, handler->attributeCount);
    
    ASSERT_EQUALS_STRING("1st element", "top", handler->elements[0].name);
    ASSERT_EQUALS_STRING("1st element attr", "1", handler->elements[0].attrs["a"]);
    ASSERT_EQUALS_STRING("1st element attr", "2", handler->elements[0].attrs["b"]);
    ASSERT_EQUALS_STRING("2nd element", "middle", handler->elements[1].name);
    ASSERT_EQUALS_STRING("2nd element attr", "23", handler->elements[1].attrs["x"]);
    ASSERT_EQUALS_STRING("2nd element attr", "42", handler->elements[1].attrs["y"]);
    
    // element ends shoulda been called in reverse.
    ASSERT_EQUALS_STRING("end", "middle", handler->elementEnds[0]);
    ASSERT_EQUALS_STRING("end", "top", handler->elementEnds[1]);
}


OMTEST(testXmlTreeReader)
{
    const char *x = "<top a='1' b='2'><middle x='23' y='42'/><middle x='86' y='99'/></top>";
    OmNodeXmlHandler h;

    OmXmlInStreamChars ch(x);
    OmXmlReader::readXml(&ch, &h);

    OmNode *n = h.root;
    
    ASSERT_EQUALS_STRING("top element", "top", n->name);
    ASSERT_EQUALS_STRING("top attr", "1", n->getAttributeValue("a"));
    ASSERT_EQUALS_STRING("top attr", "2", n->getAttributeValue("b"));
    ASSERT_EQUALS_INT("children", 2, n->children.size());
    
    ASSERT_EQUALS_STRING("child attr", "86", n->children[1]->getAttributeValue("x"));
    ASSERT_EQUALS_STRING("child attr", "99", n->children[1]->getAttributeValue("y"));

    delete(n);
}

OMTEST(testOddChars)
{
    OmXmlOutStreamString xsw;
    OmXmlWriter xw(&xsw);
    
    xw.beginElement("e1");
    xw.addAttribute("attr1", "quote:\", lt:<");
    xw.endElement();
    
    printf("x is %s", xsw.s.c_str());
    
    OmXmlInStreamChars xsr(xsw.s.c_str(), xsw.s.size());
    OmNodeXmlHandler mh;
    OmXmlReader::readXml(&xsr, &mh);
    
    OmNode *n = mh.root;

    if(ASSERT_NOT_NULL("got OmNode?", n))
    {
        ASSERT_EQUALS_STRING("top element", "e1", n->name);
        if(ASSERT_EQUALS_INT("children", 1, n->getAttributeCount()))
        {
            ASSERT_EQUALS_STRING("attr value", "quote:\", lt:<", n->getAttributeValue("attr1"));
        }
    }
}

OMTEST(testInsertText)
{
    OmXmlOutStreamString xsw;
    OmXmlWriter xw(&xsw);
    
    xw.beginElement("e1");
    xw.addContent("content went in here");
    xw.endElement();
    
    auto s = xsw.s;
    auto sCrushed = replaceAll(s, " ", "");
    sCrushed = replaceAll(sCrushed, "\n", "");
    ASSERT_EQUALS_STRING("with content", "<e1>contentwentinhere</e1>", sCrushed);
    
    // get it back.
    OmXmlInStreamChars xsr(xsw.s.c_str(), xsw.s.size());
    OmNodeXmlHandler mh;
    OmXmlReader::readXml(&xsr, &mh);
    
    OmNode *n = mh.root;
    
    if(ASSERT_NOT_NULL("got OmNode?", n))
    {
        ASSERT_EQUALS_STRING("top element", "e1", n->name);
        if(ASSERT_EQUALS_INT("children", 1, n->children.size()))
        {
            ASSERT_EQUALS_STRING("content?", "__content__", n->children[0]->name);
            auto sc = trim(n->children[0]->getAttributeValue("__content__"));
            ASSERT_EQUALS_STRING("text value", "content went in here", sc);
        }
    }
}

OMTEST(testCdata1)
{
    std::string s = "<ele><![CDATA[This is the CData. <morexml>Hi there!</morexml>]]> &amp;bye!</ele>";
    OmXmlInStreamChars xsr(s.c_str(), s.size());
    OmNodeXmlHandler mh;
    OmXmlReader::readXml(&xsr, &mh);
    
    OmNode *n = mh.root;
    
    if(ASSERT_NOT_NULL("got OmNode?", n))
    {
        ASSERT_EQUALS_STRING("top element", "ele", n->name);
        if(ASSERT_EQUALS_INT("children", 1, n->children.size()))
        {
            ASSERT_EQUALS_STRING("content?", "__content__", n->children[0]->name);
            auto sc = trim(n->children[0]->getAttributeValue("__content__"));
            ASSERT_EQUALS_STRING("text value", "This is the CData. <morexml>Hi there!</morexml> &bye!", sc);
        }
    }
    
    delete n;
}

OMTEST(testCdataWrite)
{
    OmXmlOutStreamString xsw;
    OmXmlWriter xw(&xsw);
    
    xw.beginElement("e1");
    xw.addContent("<xmlInside> This is nested xml. </xmlInside>");
    xw.endElement();
    
    std::string s = xsw.s;
    ASSERT_TRUE("looks like cdata? " + s, contains(s, "CDATA"));
    
    OmXmlInStreamChars xsr(s.c_str(), s.size());
    OmNodeXmlHandler mh;
    OmXmlReader::readXml(&xsr, &mh);
    
    OmNode *n = mh.root;
    ASSERT_EQUALS_STRING("content?", "__content__", n->children[0]->name);
    std::string sc = n->children[0]->getAttributeValue("__content__");
    ASSERT_EQUALS_STRING("text value", "<xmlInside> This is nested xml. </xmlInside>", sc);
}


OMTEST(testCdataWriteNoExtraWhiteSpace)
{
    OmXmlOutStreamString xsw;
    OmXmlWriter xw(&xsw);
    
    xw.beginElement("e1");
    xw.addContent("0123456789");
    xw.endElement();
    
    std::string s = xsw.s;

    OmXmlInStreamChars xsr(s.c_str(), s.size());
    OmNodeXmlHandler mh;
    OmXmlReader::readXml(&xsr, &mh);
    
    OmNode *n = mh.root;
    ASSERT_EQUALS_STRING("content?", "__content__", n->children[0]->name);
    std::string sc = n->children[0]->getAttributeValue("__content__");
    ASSERT_EQUALS_STRING("text value", "0123456789", sc);
}

OMTEST(testHonkingBigAttributeString)
{
    OmXmlOutStreamString xsw;
    OmXmlWriter xw(&xsw);
    
    xw.beginElement("e1");
    std::string attrS;
    long veryLong = 2000008;
    for(int ix = 0; ix < veryLong; ix++)
    {
        attrS += '0' + (ix % 10);
    }
    xw.addAttribute("a", attrS.c_str());
    xw.endElement();
    
    std::string s = xsw.s;
    if(ASSERT_TRUE(ssprintf("XML is quite long? %d > %d?", s.length(), veryLong), s.length() > veryLong))
    {
        // these tests onlh make sense if the XML is kind of looking ok.
        OmXmlInStreamChars xsr(s.c_str(), s.size());
        OmNodeXmlHandler mh;
        OmXmlReader::readXml(&xsr, &mh);
        
        std::string attrSBack = mh.root->getAttributeValue("a");
        ASSERT_EQUALS_INT("length back?", veryLong, attrSBack.length());
        
        ASSERT_EQUALS_STRING("attr back", attrS, attrSBack);
    }
    
    
}

OMTEST(testCdataOnAScript)
{
    std::string sc = R"(
-- this is a lua script
    function foo(x)
        return x + 13
    end
)";
    OmXmlOutStreamString xsw;
    OmXmlWriter xw(&xsw);
    
    xw.beginElement("e1");
    xw.addContent(sc.c_str());
    xw.endElement();
    std::string xml = xsw.s;
    bool hasHash = contains(xml, "#");
    bool hasFunction = contains(xml, "function");
    
    ASSERT_FALSE(ssprintf("xml has no #s\n%s", xml.c_str()), hasHash);
    ASSERT_TRUE(ssprintf("xml has function\n%s", xml.c_str()), hasFunction);
}

void testOmXml()
{
    testXmlOut1();
    
    testXmlReader1();
    testXmlTreeReader();
    
    testOddChars();
    
    testInsertText();
    testCdata1();
    testCdataWrite();
    testCdataWriteNoExtraWhiteSpace();
    testCdataOnAScript();
    
    testHonkingBigAttributeString();
    
    OMTESTEND();
}
