/*
 *
 * $Id: sourceheader 511311 2006-02-19 14:51:05Z trueg $
 *
 * This file is part of the Nepomuk KDE project.
 * Copyright (C) 2006-2009 Sebastian Trueg <trueg@kde.org>
 *
 * This library 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) any later version.
 * See the file "COPYING.LIB" for the exact licensing terms.
 */

#include "codegenerator.h"

#include "abstractcode.h"
#include "fastcode.h"
#include "property.h"
#include "resourceclass.h"
#include "safecode.h"

#include <QtCore/QFile>
#include <QtCore/QTextStream>
#include <QtCore/QRegExp>
#include <QtCore/QDebug>
#include <QtCore/QUrl>
#include <QtCore/QStringList>
#include <QtCore/QDir>


extern bool quiet;

static QString headerTemplate( CodeGenerator::Mode mode )
{
    QFile gplFile( ":gpl.tpl" );
    gplFile.open( QIODevice::ReadOnly );

    QFile headerFile( QString::fromLatin1( ":header_%1.tpl" )
            .arg( mode == CodeGenerator::SafeMode ? QLatin1String( "safe" ) : QLatin1String( "fast" ) ) );
    headerFile.open( QIODevice::ReadOnly );

    QString result = QString::fromLatin1( gplFile.readAll() );
    result += QString::fromLatin1( headerFile.readAll() );

    return result;
}

static QString sourceTemplate( CodeGenerator::Mode mode )
{
    QFile gplFile( ":gpl.tpl" );
    gplFile.open( QIODevice::ReadOnly );

    QFile sourceFile( QString::fromLatin1( ":source_%1.tpl" )
            .arg( mode == CodeGenerator::SafeMode ? QLatin1String( "safe" ) : QLatin1String( "fast" ) ) );
    sourceFile.open( QIODevice::ReadOnly );

    QString result = QString::fromLatin1( gplFile.readAll() );
    result += QString::fromLatin1( sourceFile.readAll() );

    return result;
}

static QString writeComment( const QString& comment, int indent )
{
    static const int maxLine = 50;

    QString s;

    if( !comment.isEmpty() ) {
        s += QString().fill( ' ', indent );
        s += "/**\n"
             + QString().fill( ' ', indent+1 )
             + "* ";

        QStringList words = comment.split( QRegExp("\\s"), QString::SkipEmptyParts );
        int cnt = 0;
        for( int i = 0; i < words.count(); ++i ) {
            if( cnt >= maxLine ) {
                s += '\n'
                     + QString().fill( ' ', indent+1 )
                     + "* ";
                cnt = 0;
            }

            s += words[i] + ' ';
            cnt += words[i].length();
        }

        if( cnt > 0 )
            s += '\n';
        s += QString().fill( ' ', indent+1 )
             + "*/";
    }

    return s;
}

CodeGenerator::CodeGenerator( Mode mode, const QList<ResourceClass*>& classes )
    : m_mode( mode ),
      m_classes( classes )
{
    if ( m_mode == SafeMode ) {
        m_code = new SafeCode;
        m_nameSpace = QLatin1String("Nepomuk");
    } else {
        m_code = new FastCode;
        m_nameSpace = QLatin1String("NepomukFast");
    }
}

CodeGenerator::~CodeGenerator()
{
    delete m_code;
}

bool CodeGenerator::write( const ResourceClass* resourceClass, const QString& folder ) const
{
    QFile f( folder + resourceClass->headerName() );
    if( !f.open( QIODevice::WriteOnly ) )
        return false;

    QTextStream s( &f );
    if( !writeHeader( resourceClass, s ) )
        return false;

    f.close();

    f.setFileName( folder + resourceClass->sourceName() );
    if( !f.open( QIODevice::WriteOnly ) )
        return false;

    if( !writeSource( resourceClass, s ) )
        return false;

    return true;
}

bool CodeGenerator::writeDummyClasses( const QString &folder ) const
{
    if ( m_mode == FastMode ) {
        QFile headerOutput( folder + "resource.h" );
        if ( !headerOutput.open( QIODevice::WriteOnly ) )
            return false;

        QFile sourceOutput( folder + "resource.cpp" );
        if ( !sourceOutput.open( QIODevice::WriteOnly ) )
            return false;

        QFile headerInput( ":dummyresource_header_fast.tpl" );
        headerInput.open( QIODevice::ReadOnly );

        QFile sourceInput( ":dummyresource_source_fast.tpl" );
        sourceInput.open( QIODevice::ReadOnly );

        headerOutput.write( headerInput.readAll() );
        sourceOutput.write( sourceInput.readAll() );
    }

    return true;
}

bool CodeGenerator::writeHeader( const ResourceClass *resourceClass, QTextStream& stream ) const
{
    QString s = headerTemplate( m_mode );
    ResourceClass* parent = resourceClass->parentClass( true );
    s.replace( "NEPOMUK_VISIBILITY_HEADER_INCLUDE", visibilityHeader() );
    s.replace( "NEPOMUK_VISIBILITY", visibilityExportMacro() );
    s.replace( "NEPOMUK_RESOURCECOMMENT", writeComment( resourceClass->comment(), 4 ) );
    s.replace( "NEPOMUK_RESOURCENAMEUPPER", resourceClass->name().toUpper() );
    s.replace( "NEPOMUK_RESOURCENAME", resourceClass->name() );
    if ( m_mode == FastMode && parent->name() == "Resource" )
        s.replace( "NEPOMUK_PARENTRESOURCE", "NepomukFast::Resource" );
    else
        s.replace( "NEPOMUK_PARENTRESOURCE", parent->name() );

    // A resource that is not part of the currently generated stuff is supposed
    // to be installed in include/nepomuk
    if ( parent->generateClass() ) {
        s.replace( "NEPOMUK_PARENT_INCLUDE", QString("\"%1.h\"").arg( parent->name().toLower() ) );
    }
    else {
        if ( m_mode == SafeMode )
            s.replace( "NEPOMUK_PARENT_INCLUDE", QString("<nepomuk2/%1.h>").arg( parent->name().toLower() ) );
        else
            s.replace( "NEPOMUK_PARENT_INCLUDE", QString("\"resource.h\"") );
    }

    QString methods;
    QTextStream ms( &methods );
    QSet<QString> includes;

    QListIterator<const Property*> it( resourceClass->allProperties() );
    while( it.hasNext() ) {
        const Property* p = it.next();

        if( p->maxCardinality() == 1 || p->cardinality() == 1 ) {
            Property * prop = const_cast<Property *>(p);
            bool isList = prop->isList();
            
            prop->setIsList( true );
            if( !writePropertyHeader( prop, resourceClass, ms ) )
                continue;
            
            prop->setIsList( false );
            if( !writePropertyHeader( prop, resourceClass, ms ) )
                continue;
            
            writePropertyUriHeader( prop, ms );
            
            prop->setIsList( isList );
        }
        else {
            if( !writePropertyHeader( p, resourceClass, ms ) )
                continue;
            writePropertyUriHeader( p, ms );
        }
        
        if( !p->hasSimpleType() )
            includes.insert( p->typeString( true ) );
    }


    it = resourceClass->allReverseProperties();
    while( it.hasNext() ) {
        const Property* p = it.next();

        if( p->literalRange().isEmpty() &&
            !p->range() ) {
            if ( !quiet )
                qDebug() << "(CodeGenerator::writeSource) type not defined for property: " << p->name() << endl;
            continue;
        }

        if ( p->inverseProperty() ) {
            // we already define a reverse property. So leave the generated one out
            continue;
        }

        if ( m_mode == SafeMode ) {
            ms << writeComment( QString("Get all resources that have this resource set as property '%1'. ")
                            .arg(p->name()) + p->comment() + QString(" \\sa ResourceManager::allResourcesWithProperty"), 2*4 ) << endl;
            ms << "        " << m_code->propertyReversePropertyGetterDeclaration( p, resourceClass ) << ";" << endl;
            ms << endl;
        }

        if( !p->hasSimpleType() )
            includes.insert( p->domain(true)->name() );
    }


    //
    // Nepomuk does not support multiple inheritance
    // So we have to use a workaround instead (we even include the one class used as "proper" superclass
    // since the order of the super classes is not deterministic and may change with a different serialization)
    //
    if( resourceClass->allParentResources().count() > 1 ) {
        foreach( ResourceClass* rc, resourceClass->allParentResources() ) {
            // ignore the one we derived from
            if( rc->generateClass() ) {
                const QString decl = m_code->resourcePseudoInheritanceDeclaration( resourceClass, rc );
                if ( decl.isEmpty() )
                    continue;
                ms << writeComment( QString("Nepomuk does not support multiple inheritance. Thus, to access "
                                            "properties from all parent classes helper methods like this are "
                                            "introduced. The object returned represents the exact same resource."), 2*4 ) << endl
                   << "        " << decl << ";" << endl << endl;

                includes.insert( rc->name() );
            }
        }
    }

    if ( m_mode == SafeMode ) {
        ms << writeComment( QString("Retrieve a list of all available %1 resources. "
                                    "This list consists of all resource of type %1 that are stored "
                                    "in the local Nepomuk meta data storage and any changes made locally. "
                                    "Be aware that in some cases this list can get very big. Then it might "
                                    "be better to use libKNep directly.").arg( resourceClass->name() ), 2*4 ) << endl;
        ms << "        static " << m_code->resourceAllResourcesDeclaration( resourceClass ) << ";" << endl;
    }

    QString includeString;
    QSetIterator<QString> includeIt( includes );
    while( includeIt.hasNext() ) {
        includeString += "    class " + includeIt.next() + ";\n";
    }

    s.replace( "NEPOMUK_OTHERCLASSES", includeString );
    s.replace( "NEPOMUK_METHODS", methods );

    stream << s;

    return true;
}

bool CodeGenerator::writePropertyHeader(const Property* p, const ResourceClass* resourceClass, QTextStream& ms) const
{
    if( p->literalRange().isEmpty() &&
        !p->range() ) {
        if ( !quiet )
            qDebug() << "(CodeGenerator::writeSource) type not defined for property: " << p->name() << endl;
        return false;
    }
        
    if ( m_mode == SafeMode ) {
        ms << writeComment( QString("Get property '%1'. ").arg(p->name()) + p->comment(), 2*4 ) << endl;
        ms << "        " << m_code->propertyGetterDeclaration( p, resourceClass ) << ";" << endl;
        ms << endl;
    }
    
    ms << writeComment( QString("Set property '%1'. ").arg(p->name()) + p->comment(), 2*4 ) << endl;
    ms << "        " << m_code->propertySetterDeclaration( p, resourceClass ) << ";" << endl;
    ms << endl;
    
    if( p->isList() ) {
        ms << writeComment( QString("Add a value to property '%1'. ").arg(p->name()) + p->comment(), 2*4 ) << endl;
        ms << "        " << m_code->propertyAdderDeclaration( p, resourceClass ) << ";" << endl;
        ms << endl;
    }
    
    return true;
}

void CodeGenerator::writePropertyUriHeader(const Property* p, QTextStream& ts) const
{
    ts << writeComment( QString( "\\return The URI of the property '%1'." ).arg( p->name() ), 2*4 ) << endl;
    ts << "        " << "static QUrl " << p->name()[0].toLower() << p->name().mid(1) << "Uri();" << endl;
    ts << endl;
}


bool CodeGenerator::writeSource( const ResourceClass* resourceClass, QTextStream& stream ) const
{
    QString s = sourceTemplate( m_mode );
    s.replace( "NEPOMUK_RESOURCENAMELOWER", resourceClass->name().toLower() );
    s.replace( "NEPOMUK_RESOURCENAME", resourceClass->name() );
    s.replace( "NEPOMUK_RESOURCETYPEURI", resourceClass->uri().toString() );
    if ( m_mode == FastMode && resourceClass->parentClass()->name() == "Resource" )
        s.replace( "NEPOMUK_PARENTRESOURCE", "NepomukFast::Resource" );
    else
        s.replace( "NEPOMUK_PARENTRESOURCE", resourceClass->parentClass()->name() );

    QString methods;
    QStringList includes;
    QTextStream ms( &methods );

    QListIterator<const Property*> it( resourceClass->allProperties() );
    while( it.hasNext() ) {
        const Property* p = it.next();

        if( p->maxCardinality() == 1 || p->cardinality() == 1 ) {
            Property * prop = const_cast<Property *>(p);
            bool isList = prop->isList();
            
            prop->setIsList( true );
            if( !writePropertySource( prop, resourceClass, ms ) )
                continue;
            
            prop->setIsList( false );
            if( !writePropertySource( prop, resourceClass, ms ) )
                continue;
            
            writePropertyUriSource( prop, resourceClass, ms );
            
            prop->setIsList( isList );
        }
        else {
            if( !writePropertySource( p, resourceClass, ms ) )
                continue;
            writePropertyUriSource( p, resourceClass, ms );
        }
        
        if ( !p->hasSimpleType() ) {
            includes.append( QString( "#include \"%1.h\"" ).arg( p->typeString( true ).toLower() ) );
        }
    }

    it = resourceClass->allReverseProperties();
    while( it.hasNext() ) {
        const Property* p = it.next();

        if( p->literalRange().isEmpty() &&
            !p->range() ) {
            if ( !quiet )
                qDebug() << "(CodeGenerator::writeSource) type not defined for property: " << p->name() << endl;
            continue;
        }

        if ( p->inverseProperty() ) {
            // we already define a reverse property. So leave the generated one out
            continue;
        }

        if ( m_mode == SafeMode )
            ms << m_code->propertyReversePropertyGetterDefinition( p, resourceClass ) << endl;

        includes.append( QString( "#include \"%1\"" ).arg( p->domain(true)->headerName() ) );
    }

    //
    // Nepomuk does not support multiple inheritance
    // So we have to use a workaround instead (we even include the one class used as "proper" superclass
    // since the order of the super classes is not deterministic and may change with a different serialization)
    //
    if( resourceClass->allParentResources().count() > 1 ) {
        foreach( ResourceClass* rc, resourceClass->allParentResources() ) {
            // ignore the one we derived from
            if( rc->generateClass() ) {
                ms << m_code->resourcePseudoInheritanceDefinition( resourceClass, rc ) << endl;
                includes.append( QString("#include \"%1.h\"").arg( rc->name().toLower() ) );
            }
        }
    }

    if ( m_mode == SafeMode )
        ms << m_code->resourceAllResourcesDefinition( resourceClass ) << endl;

    // HACK: remove duplicates and resource include
    includes = includes.toSet().toList();
    includes.removeAll( "#include \"resource.h\"" );

    s.replace( "NEPOMUK_METHODS", methods );
    s.replace( "NEPOMUK_INCLUDES", includes.join( "\n" ) );

    stream << s;

    return true;
}

bool CodeGenerator::writePropertySource(const Property* p, const ResourceClass* resourceClass, QTextStream& ms) const
{
    if( p->literalRange().isEmpty() && !p->range() ) {
        if ( !quiet )
            qDebug() << "(CodeGenerator::writeSource) type not defined for property: " << p->name() << endl;
        return false;
    }

    if ( m_mode == SafeMode )
        ms << m_code->propertyGetterDefinition( p, resourceClass ) << endl;

    ms << m_code->propertySetterDefinition( p, resourceClass ) << endl;
    if( p->isList() )
        ms << m_code->propertyAdderDefinition( p, resourceClass ) << endl;

    return true;
}

void CodeGenerator::writePropertyUriSource(const Property* p, const ResourceClass* resourceClass, QTextStream& ts) const
{
    // write the static method that returns the property's Uri
    ts << "QUrl " << resourceClass->name( m_nameSpace ) << "::" << p->name()[0].toLower() << p->name().mid(1) << "Uri()" << endl
       << "{" << endl
       << "    return QUrl::fromEncoded(\"" << p->uri().toString() << "\");" << endl
       << "}" << endl << endl;
}

bool CodeGenerator::writeSources( const QString& dir )
{
    bool success = true;

    foreach( ResourceClass* rc, classes() ) {
        if( rc->generateClass() )
            success &= write( rc, dir + QDir::separator() );
    }

    writeDummyClasses( dir + QDir::separator() );

    return success;
}


QStringList CodeGenerator::listHeader()
{
    QStringList l;
    foreach( ResourceClass* rc, classes() ) {
        if( rc->generateClass() )
            l.append( rc->headerName() );
    }
    return l;
}


QStringList CodeGenerator::listSources()
{
    QStringList l;
    foreach( ResourceClass* rc, classes() ) {
        if( rc->generateClass() )
            l.append( rc->sourceName() );
    }
    return l;
}


QString CodeGenerator::visibilityHeader() const
{
    if( m_visibility.isEmpty() )
        return QString();
    else
        return "#include \"" + m_visibility.toLower() + "_export.h\"";
}


QString CodeGenerator::visibilityExportMacro() const
{
    if( m_visibility.isEmpty() )
        return QString();
    else
        return m_visibility.toUpper() + "_EXPORT";
}
