/****************************************************************************
**
** Definition of VcprojGenerator class.
**
** Copyright (C) 1992-2003 Trolltech AS.  All rights reserved.
** Copyright (C) 2005,2006 Christian Ehrlicher
**
** This file is part of qmake.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
**********************************************************************/

#include "msvc_nmake.h"
#include "option.h"
#include <qregexp.h>
#include <qdir.h>
#include <stdlib.h>
#include <time.h>

extern void removeDuplicates( QStringList &list );
#ifdef MSVC_USE_MSC_VER
extern int getCurrentMsvcVersion( const QString &generator = QString() );
#endif

NmakeMakefileGenerator::NmakeMakefileGenerator()
        : Win32MakefileGenerator(), init_flag( false )
{}

NmakeMakefileGenerator::~NmakeMakefileGenerator()
{}

bool NmakeMakefileGenerator::writeMakefile( QTextStream &t )
{
    writeHeader( t );
    if ( !project->variables() [ "QMAKE_FAILED_REQUIREMENTS" ].isEmpty() ) {
        t << "all clean:" << "\n\t"
        << "@echo \"Some of the required modules ("
        << var( "QMAKE_FAILED_REQUIREMENTS" ) << ") are not available.\"" << "\n\t"
        << "@echo \"Skipped.\"" << endl << endl;
        writeMakeQmake( t );
        return true;
    }

    if ( project->first( "TEMPLATE" ) == "app" ||
         project->first( "TEMPLATE" ) == "lib" ) {
        writeStandardParts( t );
        // precompiled header
        if ( usePCH ) {
            QString precompRule = QString( "-c -Yc -Fp%1 -Fo%2" ).arg( precompPch ).arg( precompObj );
            t << precompObj << ": " << precompH << " " << findDependencies( precompH ).join( " \\\n\t\t" )
            << "\n\t" << ( "$(CXX) " + precompRule + " $(CXXFLAGS) $(INCPATH) -TP " ) << precompH << endl << endl;
        }
        return MakefileGenerator::writeMakefile( t );
    } else if ( project->first( "TEMPLATE" ) == "subdirs" ) {
        writeSubDirs( t );
        return true;
    }
    return false;
}

void NmakeMakefileGenerator::init()
{
    if ( init_flag )
        return ;
    init_flag = true;

    /* this should probably not be here, but I'm using it to wrap the .t files */
    if ( project->first( "TEMPLATE" ) == "app" )
        project->variables() [ "QMAKE_APP_FLAG" ].append( "1" );
    else if ( project->first( "TEMPLATE" ) == "lib" )
        project->variables() [ "QMAKE_LIB_FLAG" ].append( "1" );
    else if ( project->first( "TEMPLATE" ) == "subdirs" ) {
        MakefileGenerator::init();
        if ( project->variables() [ "MAKEFILE" ].isEmpty() )
            project->variables() [ "MAKEFILE" ].append( "Makefile" );
        if ( project->variables() [ "QMAKE_QMAKE" ].isEmpty() )
            project->variables() [ "QMAKE_QMAKE" ].append( "qmake" );
        return ;
    }

    project->variables() [ "TARGET_PRL" ].append( project->first( "TARGET" ) );

    processVars();
#ifdef MSVC_USE_MSC_VER
    // otherwise we'll get Compiler errors with MSVC 2003.net and above in some cases
    switch ( getCurrentMsvcVersion( project->first( "MAKEFILE_GENERATOR" ) ) ) {
    case 80:
        project->variables() [ "DEFINES" ] += "_MSC_VER=1400";
        break;
    case 71:
        project->variables() [ "DEFINES" ] += "_MSC_VER=1310";
        break;
    case 70:
    default:
        project->variables() [ "DEFINES" ] += "_MSC_VER=1300";
        break;
    case 60:
        project->variables() [ "DEFINES" ] += "_MSC_VER=1200";
        break;
    }
#endif
    if ( project->first( "MAKEFILE_GENERATOR" ) == "MSVC2005" &&
        !project->isActiveConfig( "staticlib" ) ) {
        project->variables() [ "POST_TARGETDEPS" ] += "$(DESTDIR_TARGET).manifest";
        project->variables() [ "QMAKE_POST_LINK" ] += "@if exist $(DESTDIR_TARGET).manifest mt.exe /manifest $(DESTDIR_TARGET).manifest /outputresource:$(DESTDIR_TARGET);#2";
        project->variables() [ "CLEAN_FILES" ] += "$(DESTDIR_TARGET).manifest";
    }
    project->variables() [ "DEFINES" ] += "WIN32";

    if ( !project->variables() [ "RES_FILE" ].isEmpty() ) {
        project->variables() [ "QMAKE_LIBS" ] += project->variables() [ "RES_FILE" ];
    }

    // LIBS defined in Profile comes first for msvc
    project->variables() [ "QMAKE_LIBS" ] += project->variables() [ "LIBS" ];

    QString targetfilename = project->variables() [ "TARGET" ].first();

    if ( project->isActiveConfig( "dll" ) ) {
        QString destDir = "";
        if ( !project->first( "DESTDIR" ).isEmpty() )
            destDir = project->first( "DESTDIR" ) + Option::dir_sep;
        QString preTarget = destDir + project->first( "TARGET" ) + project->first( "TARGET_VERSION_EXT" );
        project->variables() [ "MSVC_IMPORT_LIB" ] += Option::fixPathToTargetOS( preTarget + ".lib", false, false );
        project->variables() [ "MSVC_EXTRA_LIBS" ] += Option::fixPathToTargetOS( preTarget + ".exp", false, false );
        project->variables() [ "MSVC_EXTRA_LIBS" ] += Option::fixPathToTargetOS( preTarget + ".pdb", false, false );
    }

    if ( !project->variables() [ "DEF_FILE" ].isEmpty() )
        project->variables() [ "QMAKE_LFLAGS" ].append( QString( "/DEF:" ) + project->first( "DEF_FILE" ) );

    // Base class init!
    MakefileGenerator::init();

    // Setup PCH variables
    precompH = project->first( "PRECOMPILED_HEADER" );
    usePCH = !precompH.isEmpty() && project->isActiveConfig( "precompile_header" );
    if ( usePCH ) {
        // Created files
        precompObj = var( "OBJECTS_DIR" ) + project->first( "TARGET" ) + "_pch" + Option::obj_ext;
        precompPch = var( "OBJECTS_DIR" ) + project->first( "TARGET" ) + "_pch.pch";
        // Add linking of precompObj (required for whole precompiled classes)
        project->variables() [ "OBJECTS" ] += precompObj;
        // Add pch file to cleanup
        project->variables() [ "QMAKE_CLEAN" ] += precompPch;
    }

    if ( project->isActiveConfig( "dll" ) ) {
        project->variables() [ "QMAKE_CLEAN" ] += project->first( "MSVC_IMPORT_LIB" );
        project->variables() [ "QMAKE_CLEAN" ] += project->variables() [ "MSVC_EXTRA_LIBS" ];
    }

    if ( project->isActiveConfig( "incremental" ) && !project->isActiveConfig( "incremental_off" ) )
        project->variables() [ "QMAKE_LFLAGS" ].append( QString( "/incremental:yes" ) );
    else
        project->variables() [ "QMAKE_LFLAGS" ].append( QString( "/incremental:no" ) );

    // fix QMAKE_LFLAGS and QMAKE_CFLAGS/QMAKE_CXXFLAGS (qmake bug)
    removeDuplicates( project->variables() [ "QMAKE_LFLAGS" ] );
    removeDuplicates( project->variables() [ "QMAKE_CFLAGS" ] );
    removeDuplicates( project->variables() [ "QMAKE_CXXFLAGS" ] );
}

void NmakeMakefileGenerator::fixTargetExt()
{
    if ( project->isActiveConfig( "staticlib" ) ) {
        project->variables() [ "TARGET_EXT" ].append( ".lib" );
        project->variables() [ "TARGET" ].first() = project->first( "TARGET" );
    } else {
        Win32MakefileGenerator::fixTargetExt();
    }
}

void NmakeMakefileGenerator::writeLibDirPart( QTextStream &t )
{
    t << varGlue( "QMAKE_LIBDIR", "/LIBPATH:\"", "\" /LIBPATH:\"", "\"" ) << " ";
}

void NmakeMakefileGenerator::writeImplicitRulesPart( QTextStream &t )
{
    if ( !project->isActiveConfig( "no-batch" ) ) {
        // Batchmode doesn't use the non implicit rules QMAKE_RUN_CXX & QMAKE_RUN_CC
        project->variables().remove( "QMAKE_RUN_CXX" );
        project->variables().remove( "QMAKE_RUN_CC" );

        QStringList source_directories;
        QString directories[] = { QString( "MOC_DIR" ), QString( "UI_SOURCES_DIR" ), QString( "UI_DIR" ), QString( "RCC_DIR" ), QString::null };
        for ( int y = 0; !directories[ y ].isNull(); y++ ) {
            QString dirTemp = project->first( directories[ y ] );
            if ( dirTemp.endsWith( "\\" ) )
                dirTemp.truncate( dirTemp.length() - 1 );
            if ( !dirTemp.isEmpty() )
                source_directories += dirTemp;
        }
        QString srcs[] = { QString( "SOURCES" ), QString( "UICIMPLS" ), QString( "SRCMOC" ), QString::null };
        for ( int x = 0; !srcs[ x ].isNull(); x++ ) {
            QStringList &l = project->variables() [ srcs[ x ] ];
            for ( QStringList::Iterator sit = l.begin(); sit != l.end(); ++sit ) {
                QString sep = "\\";
                if ( ( *sit ).indexOf( sep ) == -1 )
                    sep = "/";
                QString dir = ( *sit ).section( sep, 0, -2 );
                if ( !dir.isEmpty() && !source_directories.contains( dir ) )
                    source_directories += dir;
            }
        }

        // behind all others... if first, you'll get incorrect uic3.exe
        // because nmake uses wrong main.cpp !?
        source_directories.removeAll( "." );
        removeDuplicates( source_directories );
        removeDuplicates( Option::cpp_ext );
        source_directories += ".";

        QStringList::const_iterator it;
        for ( it = source_directories.begin(); it != source_directories.end(); ++it ) {
            QString str = *it;
            if ( str.isEmpty() )
                continue;
            QStringList::Iterator cppit;
            for ( cppit = Option::cpp_ext.begin(); cppit != Option::cpp_ext.end(); ++cppit )
                t << "{" << str << "}" << ( *cppit ) << "{" << var( "OBJECTS_DIR" ) << "}" << Option::obj_ext << "::\n\t"
                << var( "QMAKE_RUN_CXX_IMP_BATCH" ).replace( QRegExp( "\\$@" ), var( "OBJECTS_DIR" ) ) << endl << "\t$<" << endl << "<<" << endl << endl;
            t << "{" << str << "}" << ".c{" << var( "OBJECTS_DIR" ) << "}" << Option::obj_ext << "::\n\t"
            << var( "QMAKE_RUN_CC_IMP_BATCH" ).replace( QRegExp( "\\$@" ), var( "OBJECTS_DIR" ) ) << endl << "\t$<" << endl << "<<" << endl << endl;
        }
    } else {
        Win32MakefileGenerator::writeImplicitRulesPart( t );
    }
}

void NmakeMakefileGenerator::writeBuildRulesPart( QTextStream &t )
{
    t << "first: all" << endl;
    t << "all: " << fileFixify( Option::output.fileName() ) << " " << varGlue( "ALL_DEPS", " ", " ", " " ) << " $(DESTDIR_TARGET)" << endl << endl;
    if ( project->first( "MAKEFILE_GENERATOR" ) == "MSVC2005" &&
            !project->isActiveConfig( "staticlib" ) ) {
        t << "$(DESTDIR_TARGET).manifest:\n\n";
    }
    t << "$(DESTDIR_TARGET): " << var( "PRE_TARGETDEPS" ) << " $(OBJECTS) " << var( "POST_TARGETDEPS" );

    if ( project->isActiveConfig( "staticlib" ) ) {
        t << "\n\t" << "$(LIB) /OUT:\"$(DESTDIR_TARGET)\" @<<";
        t << "\n\t" << "$(OBJECTS)";
        t << "\n<<\n";
    } else {
        t << "\n\t" << "$(LINK) $(LFLAGS) /OUT:\"$(DESTDIR_TARGET)\" @<<";
        t << "\n\t" << "$(OBJECTS) $(LIBS)";
        t << "\n<<\n";
    }
}

QString
NmakeMakefileGenerator::var( const QString &value )
{
    if ( usePCH ) {
        if ( ( value == "QMAKE_RUN_CXX_IMP_BATCH"
                || value == "QMAKE_RUN_CXX_IMP"
                || value == "QMAKE_RUN_CXX" ) ) {
            QFileInfo precompHInfo( precompH );
            QString precompRule = QString( "-c -FI%1 -Yu%2 -Fp%3" )
                                  .arg( precompHInfo.fileName() )
                                  .arg( precompHInfo.fileName() )
                                  .arg( precompPch );
            QString p = MakefileGenerator::var( value );
            p.replace( "-c", precompRule );
            // Cannot use -Gm with -FI & -Yu, as this gives an
            // internal compiler error, on the newer compilers
            p.remove( "-Gm" );
            return p;
        } else if ( value == "QMAKE_CXXFLAGS" ) {
            // Remove internal compiler error option
            return MakefileGenerator::var( value ).remove( "-Gm" );
        }
    }
    // Normal val
    return MakefileGenerator::var( value );
}

QStringList &NmakeMakefileGenerator::findDependencies( const QString &file )
{
    QStringList newList;
    QStringList &l = MakefileGenerator::findDependencies( file );
    // just remove some unneded deps for clearness :)
    for ( QStringList::const_iterator it = l.begin(); it != l.end(); ++it ) {
        QRegExp re( "^(.*)\\\\include\\\\Qt(3Support|Core|Designer|Gui|Network|NsPlugin|OpenGL|Sql|Xml)\\\\(((private|arch)\\\\.*\\.h)|(Q.*)|(q.*\\.h))$" );
        if ( !re.exactMatch( *it ) && newList.indexOf( *it ) == -1 )
            //            newList += fileFixify( *it, QString(), QString(), FileFixifyAbsolute );
            newList += *it;
    }
    newList.sort();
    l = newList;
    return l;
}
