// -*- c++ -*-

/*
 *
 * Copyright (C) 2002 Richard Moore <rich@kde.org>
 * Copyright (C) 2002 George Staikos <staikos@kde.org>
 *               2004, 2005 Dirk Ziegelmeier <dziegel@gmx.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <qdom.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qdatetime.h>

#include <klocale.h>
#include <kdebug.h>
#include <ksavefile.h>

#include "channelstore.h"

#include "channelioxml.h"

//
// XML Format Handler
//

static const int CHANNELIO_XML_FORMAT_VERSION = 4;

ChannelIOFormatXML::ChannelIOFormatXML(Kdetv *ktv, QObject *parent, const char* name)
    : KdetvChannelPlugin(ktv, "XML Channels", parent, name)
{
    _fmtName  = "xml";
    _menuName = i18n("kdetv XML");
    _flags    = FormatRead|FormatWrite;
}

//
// XML Loading
//

bool ChannelIOFormatXML::load( ChannelStore *store, ChannelFileMetaInfo *info,
                               QIODevice *file, const QString& /*fmt*/ )
{
    kdDebug() << "IOFormatXML::load(...)" << endl;

    if ( !doc.setContent( file ) )
        return false;

    return readDocument( store, info );
}

bool ChannelIOFormatXML::readDocument( ChannelStore *store, ChannelFileMetaInfo *info )
{
    kdDebug() << "IOFormatXML::readDocument(...)" << endl;

    this->store = store;
    QDomElement root = doc.documentElement();
    if ( (root.tagName() != "kwintv") && (root.tagName() != "kdetv") )
        return false;

    int version = readAttrInt(root, "version");

    if(version > CHANNELIO_XML_FORMAT_VERSION) {
        kdWarning() << "Channel file format is too new - please upgrade kdetv to a newer version to read this file" << endl;
        return false;
    }

    kdDebug() << "       Found a kdetv channel file" << endl;

    QDomNode n = root.firstChild();
    if ( n.isNull() || (!n.isElement()) )
        return false;

    root = n.toElement();
    if ( root.tagName() != "tvregion" )
        return false;

    kdDebug() << "       Found a region in the channel file" << endl;

    getMetaInfo(root, info);

    // Read in the region
    for (QDomNode n = root.firstChild();
         !n.isNull() && n.isElement();
         n = n.nextSibling()) {
        
        root = n.toElement();
        kdDebug() << "     Found " << root.tagName() << endl;
        
        if ( root.tagName() == "channels" ) {
            kdDebug() << "       Found a block of channels" << endl;
            
            QDomNode z = n.firstChild();
            while( (!z.isNull()) && z.isElement() ) {
                QDomElement e = z.toElement();
                Channel* ch;
                if(version < 3) {
                    ch = readChannelFormat2( e );
                } else if(version == 3) {
                    ch = readChannelFormat3( e );
                } else {
                    ch = readChannelFormat4( e );
                }
                if ( !ch ) {
                    kdWarning() << "        Error reading channel" << endl;
                    break;
                }
                store->addChannel( ch );

                z = z.nextSibling();
            }
        }
    }
    return true;
}

void ChannelIOFormatXML::getMetaInfo(const QDomElement &e, ChannelFileMetaInfo *info)
{
    kdDebug() << "IOFormatXML::getMetaInfo(...)" << endl;

    info->_contributor = QString::null;
    info->_region      = QString::null;
    info->_type        = QString::null;
    info->_comment     = QString::null;
    info->_lastUpdate  = QDateTime::currentDateTime();

    for (QDomNode n = e.firstChild();
         !n.isNull() && n.isElement();
         n = n.nextSibling()) {

        QDomElement root = n.toElement();
        kdDebug() << "     Found " << root.tagName() << endl;

        if ( root.tagName() == "info" ) {
            kdDebug() << "       Found the metainfo" << endl;
            for (QDomNode m = root.firstChild(); !m.isNull() && m.isElement();
                 m = m.nextSibling()) {
                QDomElement e = m.toElement();
                if (e.tagName() == "contributor") {
                    info->_contributor = e.text();
                } else if (e.tagName() == "country") {
                    info->_country = e.text();
                } else if (e.tagName() == "region") {
                    info->_region = e.text();
                } else if (e.tagName() == "type") {
                    info->_type = e.text();
                } else if (e.tagName() == "comment") {
                    info->_comment = e.text();
                } else if (e.tagName() == "lastupdate") {
                    info->_lastUpdate = QDateTime::fromString(e.text());
                }
            }
        } else if (root.tagName() == "global_controls") {
            for (QDomNode n = root.firstChild();
                 !n.isNull() && n.isElement();
                 n = n.nextSibling()) {
                if(n.toElement().tagName() == "device") {
                    QDomElement dev = n.toElement();
                    QDomElement name  = dev.namedItem("name").toElement();
                    QDomElement props = dev.namedItem("properties").toElement();
                    QString devName = name.text();
                    
                    for (QDomNode n = props.firstChild();
                         !n.isNull() && n.isElement();
                         n = n.nextSibling()) {
                        QString name;
                        QVariant value;
                        readVariant(n.toElement(), name, value);
                        info->_globalControls[devName][name] = value;
                    }
                } else {
                    QString name;
                    QVariant value;
                    readVariant(n.toElement(), name, value);
                    info->_globalControls["unknown"][name] = value;
                }
            }
        }
    }
}

Channel* ChannelIOFormatXML::readChannelFormat2( const QDomElement &elem )
{
    if ( elem.tagName() != "channel" ) {
        kdWarning() << "Error: tried to read " << elem.tagName()
                    << " where we expected a channel." << endl;
        return 0;
    }

    Channel *ch = new Channel( store );
    ch->setEnabled(readAttrBool(elem, "enabled"));
    for (QDomNode n = elem.firstChild(); !n.isNull() && n.isElement();
         n = n.nextSibling()) {
        QDomElement z = n.toElement();
        if (z.tagName() == "name")
            ch->setName(readText( z, "name" ));
        else if (z.tagName() == "source")
            ch->setChannelProperty( "source", readText( z, "source" ));
        else if (z.tagName() == "encoding")
            ch->setChannelProperty( "encoding", readText( z, "encoding" ));
        else if (z.tagName() == "number") 
            ch->setNumber(readTextInt( z, "number" ));
        else if (z.tagName() == "frequency") 
            ch->setChannelProperty("frequency", (Q_ULLONG)readTextULongLong( z, "frequency" ));
        else if (z.tagName() == "url") 
            ch->setURL(readText( z, "url" ));
        else if (z.tagName() == "description") 
            ch->setDescription(readText( z, "description" ));
        else if (z.tagName() == "pictureProperties") 
            readPicturePropertiesList(z, *ch);
    }

    return ch;
}

Channel* ChannelIOFormatXML::readChannelFormat3( const QDomElement &elem )
{
    if ( elem.tagName() != "channel" ) {
        kdDebug() << "Error: tried to read " << elem.tagName()
                  << " where we expected a channel." << endl;
        return 0;
    }

    Channel *ch = new Channel( store );
    ch->setEnabled(readAttrBool(elem, "enabled"));
    for (QDomNode n = elem.firstChild(); !n.isNull() && n.isElement();
         n = n.nextSibling()) {
        QDomElement z = n.toElement();
        if (z.tagName() == "name")
            ch->setName(readText( z, "name" ));
        else if (z.tagName() == "number") 
            ch->setNumber(readTextInt( z, "number" ));
        else if (z.tagName() == "url") 
            ch->setURL(readText( z, "url" ));
        else if (z.tagName() == "description") 
            ch->setDescription(readText( z, "description" ));
        else if (z.tagName() == "channel_properties")
            readChannelPropertiesListOld(z, *ch);
        else if ( (z.tagName() == "picture_properties") || (z.tagName() == "pictureProperties") )
            readPicturePropertiesList(z, *ch);
    }

    return ch;
}

Channel* ChannelIOFormatXML::readChannelFormat4( const QDomElement &elem )
{
    if ( elem.tagName() != "channel" ) {
        kdWarning() << "Error: tried to read " << elem.tagName()
                    << " where we expected a channel." << endl;
        return 0;
    }

    Channel *ch = new Channel( store );
    ch->setEnabled(readAttrBool(elem, "enabled"));
    for (QDomNode n = elem.firstChild(); !n.isNull() && n.isElement();
         n = n.nextSibling()) {
        QDomElement z = n.toElement();
        if (z.tagName() == "name")
            ch->setName(readText( z, "name" ));
        else if (z.tagName() == "number") 
            ch->setNumber(readTextInt( z, "number" ));
        else if (z.tagName() == "url") 
            ch->setURL(readText( z, "url" ));
        else if (z.tagName() == "description") 
            ch->setDescription(readText( z, "description" ));
        else if (z.tagName() == "channel_properties")
            readChannelPropertiesList(z, *ch);
        else if (z.tagName() == "controls")
            readControlLists(z, *ch);
    }

    return ch;
}

void ChannelIOFormatXML::readChannelPropertiesListOld(const QDomElement &elem, Channel& ch)
{
    for (QDomNode n = elem.firstChild();
         !n.isNull() && n.isElement();
         n = n.nextSibling()) {
        QDomElement z = n.toElement();
        ch.setChannelProperty(z.tagName(), readVariantOld(z));
    }
}

void ChannelIOFormatXML::readChannelPropertiesList(const QDomElement &elem, Channel& ch)
{
    for (QDomNode n = elem.firstChild();
         !n.isNull() && n.isElement();
         n = n.nextSibling()) {
        QString name;
        QVariant value;
        readVariant(n.toElement(), name, value);
        ch.setChannelProperty(name, value);
    }
}

void ChannelIOFormatXML::readPicturePropertiesList(const QDomElement &elem, Channel& ch)
{
    ch.setHasControls("unknown", readAttrBool(elem, "enabled"));

    for (QDomNode n = elem.firstChild();
         !n.isNull() && n.isElement();
         n = n.nextSibling()) {
        QDomElement z = n.toElement();
        ch.setControl("unknown", z.tagName(), readVariantOld(z));
    }
}

void ChannelIOFormatXML::readControlLists(const QDomElement &elem, Channel& ch)
{
    for (QDomNode n = elem.firstChild();
         !n.isNull() && n.isElement();
         n = n.nextSibling()) {
        QDomElement dev = n.toElement();
        QDomElement name  = dev.namedItem("name").toElement();
        QDomElement props = dev.namedItem("properties").toElement();
        QString devName = name.text();

        ch.setHasControls(devName, readAttrBool(dev, "enabled"));
        
        for (QDomNode n = props.firstChild();
             !n.isNull() && n.isElement();
             n = n.nextSibling()) {
            QString name;
            QVariant value;
            readVariant(n.toElement(), name, value);
            ch.setControl(devName, name, value);
        }
    }
}

void ChannelIOFormatXML::readVariant(const QDomElement &elem, QString& name, QVariant& value)
{
    QDomNode n = elem.namedItem("name");
    name = readText(n.toElement(), "name");

    QDomElement v = elem.namedItem("value").toElement();
    QVariant::Type t = QVariant::nameToType(readAttrText(v, "type", "Invalid").ascii());
    switch(t) {
    case QVariant::Int:
        value = QVariant(readTextInt(v, v.tagName()));
        break;
    case QVariant::Bool:
        value = QVariant(readTextBool(v, v.tagName()), 0);
        break;
    case QVariant::ULongLong:
        value = QVariant(readTextULongLong(v, v.tagName()));
        break;
    case QVariant::String:
        value = QVariant(readText(v, v.tagName()));
        break;
    default:
        value = QVariant();
        kdWarning() << "ChannelIOFormatXML::readVariant: Unknown type: " << QVariant::typeToName(t) << endl; 
    }
}

QVariant ChannelIOFormatXML::readVariantOld(const QDomElement &elem)
{
    QVariant::Type t = QVariant::nameToType(readAttrText(elem, "type", "Invalid").ascii());
    switch(t) {
    case QVariant::Int:
        return QVariant(readTextInt(elem, elem.tagName()));
        break;
    case QVariant::Bool:
        return QVariant(readTextBool(elem, elem.tagName()));
        break;
    case QVariant::ULongLong:
        return QVariant(readTextULongLong(elem, elem.tagName()));
        break;
    case QVariant::String:
        return QVariant(readText(elem, elem.tagName()));
        break;
    default:
        kdWarning() << "ChannelIOFormatXML::readVariantOld: Unknown type: " << QVariant::typeToName(t) << endl; 
    }
    return QVariant();
}

int ChannelIOFormatXML::readTextInt( const QDomElement &elem, const QString &tag )
{
    QString val = readText( elem, tag );
    if ( val.isNull() )
        return 0;

    return val.toInt();
}

bool ChannelIOFormatXML::readTextBool( const QDomElement &elem, const QString &tag )
{
    QString val = readText( elem, tag );
    if ( val.isNull() )
        return 0;

    if(val == "true") {
        return true;
    } else {
        return false;
    }
}

Q_ULLONG ChannelIOFormatXML::readTextULongLong( const QDomElement &elem, const QString &tag )
{
    QString val = readText( elem, tag );
    if ( val.isNull() )
        return 0;

    return val.toULongLong();
}

QString ChannelIOFormatXML::readText( const QDomElement &elem, const QString &tag )
{
    if ( elem.tagName() != tag )
        return QString::null;

    return elem.text();
}

int ChannelIOFormatXML::readAttrInt(const QDomElement &elem, const QString &name)
{
    QString val = elem.attribute(name, "-1");

    if (val == "-1")
        return(-1);
    else
        return(val.toInt());
}

bool ChannelIOFormatXML::readAttrBool(const QDomElement &elem, const QString &name)
{
    QString val = elem.attribute(name, "true");

    if (val == "true")
        return(true);
    else if (val == "false")
        return(false);

    return false;
}

QString ChannelIOFormatXML::readAttrText(const QDomElement &elem, const QString &name, const QString& defaultText)
{
    return elem.attribute(name, defaultText);
}

//
// XML Saving
//

bool ChannelIOFormatXML::save( ChannelStore *store, ChannelFileMetaInfo *info,
                               QIODevice *file, const QString& /*fmt*/ )
{
    kdDebug() << "IOFormatXML::save(...)" << endl;

    QTextStream ts( file );
    writeDocument( store, info );
    ts.setEncoding(QTextStream::UnicodeUTF8);
    doc.save(ts, 2);

    return true;
}

void ChannelIOFormatXML::writeDocument( ChannelStore *store, ChannelFileMetaInfo *info )
{
    this->store = store;
    
    doc = QDomDocument(); // Reset the document
    QDomProcessingInstruction instr = doc.createProcessingInstruction("xml","version=\"1.0\" encoding=\"UTF-8\"");
    doc.appendChild(instr);

    QDomElement root = writeElement( doc, "kdetv" );
    root.setAttribute( "version", QString::number( CHANNELIO_XML_FORMAT_VERSION ) );

    QDomElement elem = writeElement( root, "tvregion" );

    setMetaInfo(elem, info);

    QDomElement elem2 = writeElement( elem, "channels" );
    for (uint i = 0; i < store->count(); i++) {
        writeChannel( elem2, *store->channelAt(i) );
    }
}

void ChannelIOFormatXML::setMetaInfo( QDomElement &e, ChannelFileMetaInfo *info)
{
    QDomElement inforoot = writeElement( e, "info" );

    writeText(inforoot, "contributor", info->_contributor);
    writeText(inforoot, "country",     info->_country);
    writeText(inforoot, "region",      info->_region);
    writeText(inforoot, "type",        info->_type);
    writeText(inforoot, "comment",     info->_comment);
    writeText(inforoot, "lastupdate",  info->_lastUpdate.toString());

    QDomElement controls = writeElement(e, "global_controls");
    for(QMap<QString, Channel::PropertyList>::ConstIterator it = info->_globalControls.constBegin();
        it != info->_globalControls.constEnd();
        ++it) {
        QDomElement device = writeElement(controls, "device");
        writeText(device, "name", it.key());

        QDomElement properties = writeElement(device, "properties");
        writePropertyList(properties, it.data());
    }
}

QDomElement ChannelIOFormatXML::writeChannel( QDomElement &parent, const Channel& ch )
{
    QDomElement channel = writeElement( parent, "channel" );
    writeAttrBool ( channel, "enabled",     ch.enabled() );
    writeText     ( channel, "name",        ch.name() );
    writeTextInt  ( channel, "number",      ch.number() );
    writeText     ( channel, "url",         ch.url() );
    writeText     ( channel, "description", ch.description() );

    QDomElement controls = writeElement( channel, "controls" );
    for(QMap<QString, Channel::PropertyList>::ConstIterator it = ch.allControls().constBegin();
        it != ch.allControls().constEnd();
        ++it) {
        QDomElement device = writeElement(controls, "device");
        writeAttrBool(device, "enabled", ch.hasControls(it.key()));
        writeText(device, "name", it.key());

        QDomElement properties = writeElement(device, "properties");
        writePropertyList(properties, it.data());
    }

    QDomElement channelProperties = writeElement( channel, "channel_properties" );
    writePropertyList(channelProperties, ch.channelProperties());

    return channel;
}

QDomElement ChannelIOFormatXML::writeTextInt( QDomElement &parent, const QString &tag, int text )
{
    return writeText( parent, tag, QString("%1").arg(text) );
}

QDomElement ChannelIOFormatXML::writeTextBool( QDomElement &parent, const QString &tag, bool val )
{
    if(val) {
        return writeText( parent, tag, "true" );
    } else {
        return writeText( parent, tag, "false" );
    }
}

QDomElement ChannelIOFormatXML::writeTextULongLong( QDomElement &parent, const QString &tag, Q_ULLONG text )
{
    return writeText( parent, tag, QString("%1").arg(text) );
}

QDomElement ChannelIOFormatXML::writeText( QDomElement &parent, const QString &tag, const QString &text )
{
    QDomElement elem = writeElement( parent, tag );
    QDomText t = doc.createTextNode( text ); 
    elem.appendChild( t );

    return elem;
}

QDomElement ChannelIOFormatXML::writeElement( QDomNode &parent, const QString &tag )
{
    QDomElement elem = doc.createElement( tag );
    parent.appendChild( elem );
    return elem;
}

void ChannelIOFormatXML::writeAttrBool(QDomElement &parent, const QString &name, const bool val)
{
    if (val == true)
        parent.setAttribute(name, "true");
    else if (val == false)
        parent.setAttribute(name, "false");
}

void ChannelIOFormatXML::writeAttrText(QDomElement &parent, const QString &name, const QString& text)
{
    parent.setAttribute(name, text);
}

void ChannelIOFormatXML::writePropertyList(QDomElement &parent, const Channel::PropertyList list)
{
    for(Channel::PropertyList::const_iterator it = list.constBegin();
        it != list.constEnd();
        ++it) {
        if(it.data().isValid()) {
            writeVariant(parent, it.key(), it.data());
        }
    }
}

void ChannelIOFormatXML::writeVariant(QDomElement &parent, const QString& name, const QVariant& value)
{
    QDomElement property = writeElement(parent, "property");
    writeText(property, "name", name);

    QDomElement newElement;
    QVariant::Type t = value.type();
    switch(t) {
    case QVariant::Int:
        newElement = writeTextInt(property, "value", value.toInt());
        break;
    case QVariant::Bool:
        newElement = writeTextBool(property, "value", value.toBool());
        break;
    case QVariant::ULongLong:
        newElement = writeTextULongLong(property, "value", value.toULongLong());
        break;
    case QVariant::String:
        newElement = writeText(property, "value", value.toString());
        break;
    default:
        kdWarning() << "ChannelIOFormatXML::writePropertyList: unsupported QVariant, skipping: " << value.typeName() << endl;
    }

    writeAttrText(newElement, "type", value.typeName());
}

extern "C" {
	KDETV_EXPORT ChannelIOFormatXML* create_xmlchannels(Kdetv *ktv)
    {
		return new ChannelIOFormatXML(ktv, 0, "XML Channel Plugin");
	}
}

