/****************************************************************************
**
** $Id: xfree4probe.cpp,v 1.3 2002/04/15 09:40:17 hausmann Exp $
**
** Copyright (C) 1999 Calderasystems, Inc
** All rights reserved.
**
** This file is part of the Caldera OpenLinux Installer.
** Original Implementation: 1999 by Troll Tech AS
**
** This file may be distributed under the terms of the Q Public License
** as defined by Troll Tech AS of Norway and appearing in the file
** LICENSE included in the packaging of this file.
**
*****************************************************************************/


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>

#include <qapplication.h>
#include <qregexp.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qmessagebox.h>

#include <kprocess.h>
#include <kdebug.h>
#include <kstddirs.h>

#include "ddcprobe.h"
#include "ltemplate.h"

#include "xfree4probe.h"

void LXFree40Probe::probe( const QString &fileName )
{
    GraphicsCard c;
    Monitor m;
    probe( fileName, c, m );
}

void LXFree40Probe::probe( const QString &fileName, GraphicsCard &c, Monitor &m )
{
    printf("Probing hardware...\n");

    GraphicsCard card;
    Monitor monitor0;

    probeConfig( card, monitor0 );

//    kdDebug() << "----- Config probe:" << endl;
//    card.dump();
//    monitor0.dump();

    Monitor monitor;
    monitor.vendor = QString::fromLatin1( "Generic" );
    monitor.model = QString::fromLatin1( "Monitor" );

    bool ddcprobe = DdcProbe::probe( monitor );

    // Use values from ddcprobe, if available, otherwise use information from
    // "XFree86 -configure".
    if ( ddcprobe )
    {
        monitor.hSyncToken = QString::fromLatin1( "HorizSync " ) +
            QString::number( monitor.hMin ) + '-' + QString::number( monitor.hMax );
        monitor.vSyncToken = QString::fromLatin1( "VertRefresh " ) +
            QString::number( monitor.vMin ) + '-' + QString::number( monitor.vMax );

//        kdDebug() << "--- DDC probe successfull:" << endl;
//        monitor.dump();

        m = monitor;
    } else {
        m = monitor0;
    }

    // Write configuration for probeOutput()
    writeXF4config( fileName, card, m );

    GraphicsCard card2;
    LCDMonitor lcd;

    probeOutput( fileName, card2, lcd );

//    kdDebug() << "--- Output probe:" << endl;
//    card2.dump();
//    lcd.dump();

    // Use name and driver from probeConfig() and ram and clock from probeOutput()
    c = card;
    c.maxClock = card2.maxClock;
    c.fixedClock = card2.fixedClock;
    c.fixedClocks = card2.fixedClocks;
    c.ram = card2.ram;

    if (m.vendor.isEmpty()) {
        // If we still have no monitor info, use results from probeOutput()

        if ( !lcd.horizSync.isEmpty() ) {
            Monitor monitor2;
            monitor2.vendor = QString::fromLatin1( "LCD-Monitors" );
            monitor2.model = lcd.model;
            monitor2.hSyncToken = lcd.horizSync;
            monitor2.vSyncToken = QString::fromLatin1( "VertRefresh 60-61" );

            m = monitor2;
        }
    }

    // Default value
    if ( m.hMin == 0 && m.hMax == 0 && m.vMin == 0 && m.vMax == 0 &&
         m.hSyncToken.isEmpty() && m.vSyncToken.isEmpty() ) {
        m.vendor = "Typical Monitors";
        m.model = "1024x768, 70Hz";
        m.hMin = 31;
        m.hMax = 57;
        m.vMin = 50;
        m.vMax = 90;
	m.hSyncToken = "HorizSync 31-57";
	m.vSyncToken = "VertRefresh 50-90";
    }

    // Write final config file.
    writeXF4config( fileName, c, m );
}

void LXFree40Probe::writeXF4config( const QString &fileName,
                                    const GraphicsCard &card, const Monitor &monitor )
{
    KStandardDirs dirs;
    QString templateFile = dirs.findResource( "data", "kxconfig/XF86Config-4.template" );

    if ( templateFile.isEmpty() ) {
        kdDebug() << "Error! Didn't find XF86Config-4 template file." << endl;
        return;
    }

    LTemplate xconfig( templateFile );
    if ( card.driverName != "vga" && !card.deviceSection.isEmpty())
    {
        xconfig.addToken("DEVICE_SECTION", card.deviceSection.latin1());
        xconfig.addToken("MODULE_SECTION", card.moduleSection.latin1());
        xconfig.addToken("DEFAULTDEPTH", "8");
        QString ramLine;
        if ( card.ram > 0 ) {
            ramLine = QString("Option \"X-KXCONFIG-VideoRam\" \"%1\"")
                              .arg( QString::number( card.ram ) );
        }
        xconfig.addToken( "VIDEORAM_OPTION", ramLine.latin1() );
        QString clockLine;
        if ( card.fixedClock ) {
            // TODO: implement writing of fixed clock settings
        } else {
            clockLine = QString("Option \"X-KXCONFIG-MaxClock\" \"%1\"")
                                .arg( QString::number( card.maxClock ) );
        }
        xconfig.addToken( "MAXCLOCK_OPTION", clockLine.latin1() );
    }
    else
    {
        xconfig.addToken("DEVICE_SECTION", "Section \"Device\"\n\tDriver\t\"vga\"\n\tIdentifier\t\"Card0\"\n");
        xconfig.addToken("MODULE_SECTION", "Section \"Module\"\n");
        xconfig.addToken("DEFAULTDEPTH", "4");
    }

    QString modeLines = monitor.modeLines;

    if ( !modeLines.isEmpty() )
    {
        // filter out any 640x480 modeline
        QStringList splittedModeLines = QStringList::split( '\n', monitor.modeLines );
        QStringList::Iterator it = splittedModeLines.begin();
        while ( it != splittedModeLines.end() )
            if ( (*it).find( "640x480" ) != -1 )
                it = splittedModeLines.remove( it );
            else
                ++it;

        modeLines = splittedModeLines.join( "\n" );
    }

    xconfig.addToken( "VENDORNAME", monitor.vendor.latin1() );
    xconfig.addToken( "MODELNAME", monitor.model.latin1() );
    xconfig.addToken( "VSYNC", monitor.vSyncToken.latin1() );
    xconfig.addToken( "HSYNC", monitor.hSyncToken.latin1() );
    xconfig.addToken( "MODELINES", modeLines.latin1() );

    xconfig.write( fileName );
}

bool LXFree40Probe::probeOutput( const QString &xf86ConfigPath, GraphicsCard &card,
                                 LCDMonitor &lcd )
{
    kdDebug() << "LXFree40Probe::probeOutput()" << endl;

    bool parseok = false;

    KProcess proc;
    proc << "/usr/X11R6/bin/XFree86"
         << "-xf86config" << xf86ConfigPath.latin1()
         << "-probeonly" << ":7";
    proc.start( KProcess::Block );

    QString logFile = "/var/log/XFree86.7.log";

    QFile f( logFile );
    if ( f.open( IO_ReadOnly ) )
    {
        QTextStream t( &f );
        QString output = t.read();
        parseServerOutput( output, card, lcd );
        parseok = true;
    } else {
        kdDebug() << "Couldn't open file '" << logFile << "'" << endl;
    }

    return parseok;
}


bool LXFree40Probe::probeConfig( GraphicsCard &card, Monitor &monitor )
{
    kdDebug() << "LXFree40Probe::probeConfig()" << endl;

    bool parseok = false;

    QCString tmpPath;
    tmpPath.sprintf( "/tmp/kxconfig.%i", getpid() );
    if ( mkdir( tmpPath, 0700 ) != 0 )
    {
        perror( "cannot create temporary directory " + tmpPath );
        ::exit(1);
    }

    QCString home = getenv( "HOME" );

    QCString tmpHome = "HOME=" + tmpPath;
    putenv( strdup( tmpHome.data() ) );

    KProcess proc;
    proc << "/usr/X11R6/bin/XFree86"
         << "-configure" << ":7";
    proc.start( KProcess::Block );

    putenv( strdup( ("HOME=" + home).data() ) );

    QCString configFilePath = tmpPath + "/XF86Config.new";

    QFile f( configFilePath );
    if ( f.open(IO_ReadOnly) ) {    // file opened successfully
        QTextStream t( &f );      // use a text stream
        QString result = t.read();

        parseok = parseConfigFile( result, card, monitor );
    } else {
        kdDebug() << "Cant't open '" << configFilePath << "'" << endl;
    }

    f.close();

    KStandardDirs dirs;
    QString saveDir = dirs.saveLocation( "data", "kxconfig" );
    ::rename( QFile::encodeName( configFilePath ),
              QFile::encodeName( saveDir + "/XF86config.kxconfig.configure" ) );
    rmdir( tmpPath );

    return parseok;
}


bool LXFree40Probe::parseConfigFile( QString xf86config, GraphicsCard &card, Monitor &monitor )
{
    kdDebug() << "LXFree40Probe::parseConfigFile()" << endl;

    QString module, device, line, id, driver="vga";
    bool inModule=false, inDevice=false, inMonitor=false;

    QTextStream ts(&xf86config, IO_ReadOnly);

    while (!ts.eof())
    {
        line = ts.readLine();
        if (!inDevice && line.stripWhiteSpace()[0] == '#')
            continue;

        if (line.left(10) == "EndSection")
            inModule = inDevice = inMonitor = false;

        if (inModule)
            module += line + "\n";

        if (inDevice)
        {
            device += line + "\n";
            if (line.contains("Identifier"))
            {
                int pos = line.find('"');
                if (pos > 0)
                {
                    id = line.mid(pos+1);
                    id = id.left(id.length()-1);
                }
            }
            if (line.contains("Driver"))
            {
                int pos = line.find('"');
                if (pos > 0)
                {
                    driver = line.mid(pos+1);
                    driver = driver.left(driver.length()-1);
                }
            }
        }

        if (inMonitor) {
            if (line.contains("VendorName")) {
                int pos1 = line.find('"');
                int pos2 = line.find('"',pos1+1);
                monitor.vendor = line.mid(pos1+1,pos2-pos1-1);
            }
            if (line.contains("ModelName")) {
                int pos1 = line.find('"');
                int pos2 = line.find('"',pos1+1);
                monitor.model = line.mid(pos1+1,pos2-pos1-1);
            }
            if (line.contains("HorizSync")) {
                monitor.hSyncToken = line.stripWhiteSpace();
            }
            if (line.contains("VertRefresh")) {
                monitor.vSyncToken = line.stripWhiteSpace();
            }
        }

        if (line.left(16) == "Section \"Module\"")
        {
            module += line + "\n";
            inModule = true;
        }

        if (line.left(16) == "Section \"Device\"" && device.isEmpty())
        {
            device += line + "\n";
            inDevice = true;
        }

        if (line.left(17) == "Section \"Monitor\"")
        {
            inMonitor = true;
        }
    }

    card.deviceSection = device;
    card.moduleSection = module;
    card.driverName = driver;

    return true;
}

void LXFree40Probe::parseServerOutput( const QString &output, GraphicsCard &card, LCDMonitor &lcd )
{
    kdDebug() << "LXFree40Probe::parseServerOutput()" << endl;

    int pos, len;

    // Parse for server error

    bool serverError = false;
    QRegExp error("server error");
    pos = error.search( output, 0 );
    if (pos >= 0) serverError = true;
    QRegExp none("None of the configured devices were detected");
    pos = none.search( output, 0 );
    if (pos >= 0) serverError = true;


    // Parse for video RAM

    int ram = -1;
    int result = parseVideoRam( output, "[Vv]ideo *[Rr][Aa][Mm][ ]*[:=]?", false );
    if ( result > 0 ) ram = result;
    result = parseVideoRam( output, "[0-9]+ KB of[^\n]*RAM", false );
    if ( result > 0 ) ram = result;
    result = parseVideoRam( output, "VideoRAM:", true );
    if ( result > 0 ) ram = result;
    result = parseVideoRam( output, "I810CheckAvailableMemory: ", true );
    if ( result > 0 ) ram = result;
    result = parseVideoRam( output, "Will alloc AGP framebuffer:", true );
    if ( result > 0 ) ram = result;
    result = parseVideoRam( output, "(--).*mem:[ ]*", true );
    if ( result > 0 ) ram = result;
    card.ram = ram;


    // Parse for pixel clock

    // Generic XF4 DDC parsing
    parsePixelClock( output, " PixClock max ", card );
    // MGA XF4 parsing
    parsePixelClock( output, ": Max pixel clock ", card );
    // ATI 2 XF4 parsing
    parsePixelClock( output, ": Maximum pixel clock:[ ]*", card );
    // ATI XF4 parsing
    parsePixelClock( output, ": Maximum clock: ", card );
    // (II) NV(0): Clock range:  12.00 to 350.00 MHz
    parsePixelClock( output, "Clock range:  [^ ]* to ", card );

    if ( !parsePixelClock( output, "(--) [^ ]*: Maximum allowed dot-clock: ", card ) ) {
        QRegExp clocksExp( "(--) [^ ]*: clocks: " );
        pos = clocksExp.search( output, 0 );
        if ( pos >= 0 ) {
            // found other clock
            QString tmp = output.mid( pos + clocksExp.matchedLength(), 100 ).simplifyWhiteSpace();
            card.fixedClock = true;
            card.fixedClocks = tmp;
        }
    }


    // Parse for LCD

    // Generic matching.
    QRegExp gentft("\n[^\n]*TFT[^\n]*\n");
    QRegExp genpanel("\n[^\n]*[Pp]anel[^\n]*\n");
    QRegExp genres("[0-9]*x[0-9]*");
    pos = gentft.search(output,0);
    if (pos < 0) {
        pos = genpanel.search(output);
	len = genpanel.matchedLength();
    } else
        len = gentft.matchedLength();

    if (pos >= 0) {
        QString tftline = output.mid(pos+1,len-2);
        pos = genres.search(tftline,0);
        if (pos >= 0) {
            int x, y;
            QString tmp = tftline.mid(pos,genres.matchedLength());
            if ( sscanf( tmp.latin1(), "%dx%d", &x, &y ) == 2 )
                    setLCDMonitorModel( lcd, x, y );
        }
    }

    // Trident fb (Junior)
    // (--) SVGA: Detected an TFT  1024x768 Display
    parseLCD( output, "(--) [^ ]*: Detected an TFT ", "%dx%d", 100, lcd );
    // (--) TRIDENT(0): TFT Panel 1024x768 found
    parseLCD( output, "(--) [^ ]*: TFT Panel ", "%dx%d", 100, lcd );
    // ** vga256 / Chips
    parseLCD( output, "(--) [^ ]*: [^ ]*: Display Size: ", "x=%d; y=%d", 100, lcd );
    // ** NeoMagic SVGA
    parseLCD( output, "(--) [^ ]*: [^ ]*: Panel is a ", "%dx%d", 100, lcd );
    // ** mach64 driver (both Accel and SVGA)
    parseLCD( output, "[0-9x]* panel .ID .*detected", "%dx%d", 20, lcd );
    // ** spc8100 driver...
    parseLCD( output, "(--) [^ ]*: Panel size ", "%d x %d", 20, lcd );
}

int LXFree40Probe::parseVideoRam( const QString &serverOutput, const QString &regExp,
                                  bool caseSensitive )
{
    bool foundRam = false;
    int pos;
    int ram;

    QRegExp vramExp( regExp, caseSensitive );
    pos = vramExp.search( serverOutput, 0 );
    if ( pos >= 0 ) {
//        kdDebug() << "parseVideoRam(): trying to parse " << serverOutput.mid(pos+len,20) << "\n";
        if ( sscanf( serverOutput.mid( pos+vramExp.matchedLength(), 20 ).stripWhiteSpace().latin1(), "%d", &ram ) == 1 )
        {
            if (ram>=256) { // fscking S3V card says "videoram" sometimes later
//                kdDebug() << "parseVideoRam(): found video RAM: " << ram << "\n";
                foundRam = true;
            }
        }
    }

    if ( foundRam ) return ram;
    else return -1;
}

bool LXFree40Probe::parsePixelClock( const QString &serverOutput, const QString &regExp,
                                     GraphicsCard &card )
{
    bool foundClock = false;
    int pos;

    QRegExp clockExpDDC( regExp );
    pos = clockExpDDC.search( serverOutput, 0 );
    if ( pos >= 0 ) {
        // found programmable clock
        QString tmp = serverOutput.mid( pos+clockExpDDC.matchedLength(), 20 );
//        kdDebug() << "scanning for clock in |" << tmp << "|\n";
        float maxClock;
        (void)sscanf( tmp.latin1(), "%f", &maxClock );
        card.maxClock = maxClock;
        card.fixedClock = false;
        foundClock = true;
    }

    return foundClock;
}

bool LXFree40Probe::parseLCD( const QString &serverOutput, const QString &regExp,
                              const QCString &format, int maxLen, LCDMonitor &lcd)
{
    bool found = false;

    int pos;

    QRegExp td2Exp( regExp );
    pos = td2Exp.search( serverOutput, 0 );
    if ( pos >= 0 ) {
        // found max size of LCD display
        QString tmp = serverOutput.mid( pos+td2Exp.matchedLength(), maxLen );
        int x, y;
        if ( sscanf( tmp.latin1(), format, &x, &y ) == 2 ) {
            setLCDMonitorModel( lcd, x, y );
            found = true;
        }
    }

    return found;
}


void LXFree40Probe::setLCDMonitorModel( LCDMonitor &lcd, int xResolution, int yResolution )
{
    if ( ( xResolution == 1024 && yResolution == 768  ) ||
         ( xResolution == 800 && yResolution == 600 ) ||
         ( xResolution == 640 && yResolution == 480 ) )
    {
        lcd.model = QString::fromLatin1( "Generic %1x%2" )
                                         .arg( xResolution )
                                         .arg( yResolution );
        if ( xResolution == 1024 )
            lcd.horizSync = "HorizSync 31-50";
        else if ( xResolution == 800 )
            lcd.horizSync = "HorizSync 31-38";
        else if ( xResolution == 640 )
            lcd.horizSync = "HorizSync 31-32";
    }
}
