/*
 * Parts Copyright (C) 2005-2008 by Pieter Palmers
 *
 * This file is part of FFADO
 * FFADO = Free FireWire (pro-)audio drivers for Linux
 *
 * FFADO is based upon FreeBoB.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) version 3 of the License.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 *
 *  D-Bus++ - C++ bindings for D-Bus
 *
 *  Copyright (C) 2005-2007  Paolo Durante <shackan@gmail.com>
 *
 *
 *  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; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */


#include "serialize_expat_xml.h"
#include <expat.h>

#include <iostream>
#include <fstream>

IMPL_DEBUG_MODULE( Util::Xml::Node, Node, DEBUG_LEVEL_NORMAL );
IMPL_DEBUG_MODULE( Util::Xml::Document, Document, DEBUG_LEVEL_NORMAL );

std::istream& operator >> ( std::istream& in, Util::Xml::Document& doc )
{
    std::stringbuf xmlbuf;
    in.get(xmlbuf, '\0');
    doc.from_xml(xmlbuf.str());

    return in;
}

std::ostream& operator << ( std::ostream& out, const Util::Xml::Document& doc )
{
    return out << doc.to_xml();
}

using namespace Util;
using namespace Util::Xml;

Error::Error( const char* error, int line, int column )
{
    std::ostringstream estream;

    estream << "line " << line << ", column " << column << ": " << error;

    _error = estream.str();
}

Node::Node( const char* n, const char** a )
: name(n)
{
    if(a)
    for(int i = 0; a[i]; i += 2)
    {
        _attrs[a[i]] = a[i+1];

        debugOutput(DEBUG_LEVEL_VERBOSE, "xml:\t%s = %s", a[i], a[i+1]);
    }
}

Nodes Nodes::operator[]( const std::string& key )
{
    Nodes result;

    for(iterator i = begin(); i != end(); ++i)
    {
        Nodes part = (**i)[key];

        result.insert(result.end(), part.begin(), part.end());
    }
    return result;
}

Nodes Nodes::select( const std::string& attr, const std::string& value )
{
    Nodes result;

    for(iterator i = begin(); i != end(); ++i)
    {
        if((*i)->get(attr) == value)
            result.insert(result.end(), *i);
    }
    return result;
}

Nodes Node::operator[]( const std::string& key )
{
    Nodes result;

    if(key.length() == 0) return result;

    for(Children::iterator i = children.begin(); i != children.end(); ++i)
    {
        if(i->name == key)
            result.push_back(&(*i));
    }
    return result;
}

Nodes Node::find(const std::string &path)
{
    std::string::size_type slash_pos = path.find_first_of( "/" );
    if(slash_pos == std::string::npos) {
        // end of the line
        return (*this)[path];
    } else {
        // grab the prefix and suffix
        std::string prefix = path.substr(0, slash_pos);
        std::string suffix = path.substr(slash_pos+1);
        // accumulate results over all nodes
        Nodes results;
        Nodes nodes = (*this)[prefix];
        for(int i=0; i<nodes.size(); i++) {
            Nodes tmp = nodes.at(i)->find(suffix);
            for(int j=0; j<tmp.size(); j++) {
                results.push_back(tmp.at(j));
            }
        }
        return results;
    }
}

std::string Node::get( const std::string& attribute )
{
    if(_attrs.find(attribute) != _attrs.end())
        return _attrs[attribute];
    else
        return "";
}

void Node::set( const std::string& attribute, std::string value )
{
    if(value.length())
        _attrs[attribute] = value;
    else 
        _attrs.erase(value);
}

std::string Node::to_xml() const
{
    std::string xml;
    int depth = 0;

    _raw_xml(xml, depth);

    return xml;
}

void Node::_raw_xml( std::string& xml, int& depth ) const
{
    xml.append(depth*2, ' ');
    xml.append("<"+name);

    for(Attributes::const_iterator i = _attrs.begin(); i != _attrs.end(); ++i)
    {
        xml.append(" "+i->first+"=\""+i->second+"\"");
    }

    if(cdata.length() == 0 && children.size() == 0)
    {
        xml.append("/>\n");
    }
    else
    {
        xml.append(">");
        
        if(cdata.length())
        {
            xml.append(cdata);
        }

        if(children.size())
        {
            xml.append("\n");
            depth++;

            for(Children::const_iterator i = children.begin(); i != children.end(); ++i)
            {
                i->_raw_xml(xml, depth);
            }

            depth--;
            xml.append(depth*2, ' ');
        }
        xml.append("</"+name+">\n");
    }
}

Document::Document()
: root(0), _depth(0)
{
}
    
Document::Document( const std::string& xml )
: root(0), _depth(0)
{
    from_xml(xml);
}

Document::~Document()
{
    delete root;
}

struct Document::Expat
{
    static void start_doctype_decl_handler(
        void* data, const XML_Char* name, const XML_Char* sysid, const XML_Char* pubid, int has_internal_subset
    );
    static void end_doctype_decl_handler( void* data );
    static void start_element_handler( void *data, const XML_Char *name, const XML_Char **atts );
    static void character_data_handler( void *data, const XML_Char* chars, int len );
    static void end_element_handler( void *data, const XML_Char *name );
};

void Document::from_xml( const std::string& xml )
{
    _depth = 0;
    delete root;
    root = 0;

    XML_Parser parser = XML_ParserCreate("UTF-8");

    XML_SetUserData(parser, this);

    XML_SetDoctypeDeclHandler(
        parser,
        Document::Expat::start_doctype_decl_handler,
        Document::Expat::end_doctype_decl_handler
    );

    XML_SetElementHandler(
        parser,
        Document::Expat::start_element_handler,
        Document::Expat::end_element_handler
    );

    XML_SetCharacterDataHandler(
        parser,
        Document::Expat::character_data_handler
    );

    XML_Status status = XML_Parse(parser, xml.c_str(), xml.length(), true);

    if(status == XML_STATUS_ERROR)
    {
        const char* error = XML_ErrorString(XML_GetErrorCode(parser));
        int line = XML_GetCurrentLineNumber(parser);
        int column = XML_GetCurrentColumnNumber(parser);

        XML_ParserFree(parser);

        throw Error(error, line, column);
    }
    else
    {
        XML_ParserFree(parser);
    }
}

std::string Document::to_xml() const
{
    return root->to_xml();
}

void Document::Expat::start_doctype_decl_handler(
    void* data, const XML_Char* name, const XML_Char* sysid, const XML_Char* pubid, int has_internal_subset
)
{
}

void Document::Expat::end_doctype_decl_handler( void* data )
{
}

void Document::Expat::start_element_handler( void *data, const XML_Char *name, const XML_Char **atts )
{
    Document* doc = (Document*)data;

    debugOutput(DEBUG_LEVEL_VERBOSE, "xml:%d -> %s", doc->_depth, name);

    if(!doc->root)
    {
        doc->root = new Node(name, atts);
    }
    else
    {
        Node::Children* cld = &(doc->root->children);

        for(int i = 1; i < doc->_depth; ++i)
        {
            cld = &(cld->back().children);
        }
        cld->push_back(Node(name, atts));

        //std::cerr << doc->to_xml() << std::endl;
    }
    doc->_depth++;
}

void Document::Expat::character_data_handler( void *data, const XML_Char* chars, int len )
{
    Document* doc = (Document*)data;

    Node* nod = doc->root;

    for(int i = 1; i < doc->_depth; ++i)
    {
        nod = &(nod->children.back());
    }
    int x, y;

    x = 0;
    y = len-1;

    while(isspace(chars[y]) && y > 0) --y;
    while(isspace(chars[x]) && x < y) ++x;

    nod->cdata = std::string(chars, x, y+1);
}

void Document::Expat::end_element_handler( void *data, const char *name )
{
    Document* doc = (Document*)data;

    debugOutput(DEBUG_LEVEL_VERBOSE, "xml:%d <- %s", doc->_depth, name);

    doc->_depth--;
}

void Document::create_root_node( const XML_Char *name )
{
    if(!root)
    {
        delete root;
        _depth = 0;
    }
    root = new Node(name, NULL);
    _depth++;
}

void Document::write_to_file_formatted( const std::string filename )
{
    std::string xml = to_xml();
    std::ofstream file;
    file.open(filename.c_str());
    file << xml;
    file.close();
}

void Document::load_from_file( const std::string filename )
{
    std::string xml = "";
    std::string chunk = "";

    std::ifstream file;
    if (!file) {
        debugError("file not opened\n");
        return;
    }
    file.open(filename.c_str());
    while (file >> chunk) {
        xml = xml + chunk;
    }
    file.close();
    from_xml(xml);
}
