/*
  Copyright (c) 2000 Caldera Systems

  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) any later version.

  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, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <assert.h>

#include <qlabel.h>
#include <qtl.h>
#include <qpushbutton.h>

#include <klocale.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <kcombobox.h>
#include <kdebug.h>

#include "kxdata.h"
#include "screenpageimpl.h"
#include "screenpageimpl.moc"
#include "newresolutionimpl.h"
#include "tuneresolutionimpl.h"

ScreenPage::ScreenPage( QWidget *parent, const char *name )
    : ScreenPageBase( parent, name ), m_newDialog(0), m_tuneDialog(0)
{
    monitorLabel->setPixmap(KGlobal::iconLoader()->loadIcon( "kscreensaver" , KIcon::Desktop ));
    cardLabel->setPixmap(KGlobal::iconLoader()->loadIcon( "kcmpci" , KIcon::Desktop ));

    connect(newResolutionButton, SIGNAL(clicked()), SLOT(slotNewResolution()));
    connect(tuneResolutionButton, SIGNAL(clicked()), SLOT(slotTuneResolution()));
    connect(testResolutionButton, SIGNAL(clicked()), SLOT(slotTestResolution()));

    // ### hide for now
    newResolutionButton->hide();
    tuneResolutionButton->hide();
    testResolutionButton->hide();

    connect(mColorCombo, SIGNAL(activated(int)), SIGNAL(changed()));
    connect(mResolutionCombo, SIGNAL(activated(int)), SIGNAL(changed()));
    connect(mRefreshCombo, SIGNAL(activated(int)), SIGNAL(changed()));
    connect(mVirtualCombo, SIGNAL(activated(int)), SIGNAL(changed()));

    connect(mResolutionCombo, SIGNAL(activated(int)), SLOT(setupRefreshCombo()));
    connect(mResolutionCombo, SIGNAL(activated(int)), SLOT(setupColorCombo()));
//    connect(mResolutionCombo, SIGNAL(activated(int)), SLOT(setupVirtualCombo()));

    connect( mColorCombo, SIGNAL( activated(int) ), SLOT( setupVirtualCombo() ) );
}

ScreenPage::~ScreenPage()
{
}

QString ScreenPage::quickHelp() const
{
    return i18n( "<h1>Screen Setup</h1>Please choose a color depth, a resolution and optionally a refresh rate for your display setup." );
}

void ScreenPage::load()
{
    KXMonitorData monitor = m_data->currentMonitors()[ 0 ];
    KXVideoCardData videoCard = m_data->currentVideoCards()[ 0 ];

    monitorVendor->setText( monitor.vendor() );
    monitorModel->setText( monitor.model() );
    // ### hack!!!
    hSyncValue->setText( monitor.hSyncString() + " kHz" );
    vSyncValue->setText( monitor.vSyncString() + " Hz" );

    // Hide frequency displays, if they don't contain sensible information.
    if ( monitor.hSyncMin() == 0 && monitor.hSyncMax() == 0 )
    {
        hSyncLabel->hide();
        hSyncValue->hide();
    }
    else
    {
        hSyncLabel->show();
        hSyncValue->show();
    }
    if ( monitor.vSyncMin() == 0 && monitor.vSyncMax() == 0 )
    {
        vSyncLabel->hide();
        vSyncValue->hide();
    }
    else
    {
        vSyncLabel->show();
        vSyncValue->show();
    }

    cardVendor->setText( videoCard.vendor() );
    cardModel->setText( videoCard.model() );

    kdDebug() << "-- VideoRam: " << videoCard.videoRam() << endl;
    kdDebug() << "-- MaxClock: " << videoCard.maxClock() << endl;

    KXScreenData screen = m_data->currentScreens()[ 0 ];


#if 0
    switch ( screen.defaultDepth() )
    {
	    /* no longer used
        case 32:
	    mColorCombo->setCurrentItem( 0 );
            break;
	    */
        case 24:
	    mColorCombo->setCurrentItem( 0 );
            break;
        case 16:
	    mColorCombo->setCurrentItem( 1 );
            break;
        case 8:
	    mColorCombo->setCurrentItem( 2 );
            break;
    }
#endif

    bool success = createModeList();
    setupModeControls();

    emit enableForward( success );
}

void ScreenPage::save()
{
    int defaultDepth = stringToColorDepth( mColorCombo->currentText() );
    if ( defaultDepth < 0 ) {
        kdDebug() << "Warning! No valid color depth selected" << endl;
        defaultDepth = 24;
    }

//    kdDebug() << "ScreenPage::save(): defaultDepth: " << defaultDepth << endl;

    KXScreenData screen = m_data->currentScreens()[ 0 ];

    screen.setDefaultDepth(defaultDepth);

    KXDisplayDataList displays = screen.displays();
    QStringList modesList = displays[ 0 ].modes();

    // Set first mode of display to selected mode
    KXModeData mode = selectedMode();
    modesList[ 0 ] = mode.name();

    // If mode is not from configuration file, add it to node tree.
    if ( !mode.node() )
    {
        m_data->currentModes() += mode;
/*
        KXModeDataList modes = m_data->currentMonitors()[ 0 ].modes();
//        kdDebug() << "ScreenPage::save(): Adding Mode " << mode.name() << endl;
        modes << mode;
        m_data->currentMonitors()[ 0 ].setModes(modes);
*/
    }
    else
    {
//        kdDebug() << "ScreenPage::save(): Mode " << mode.name() << " already there ("
//                  << mode.node()->name() << " " << mode.node()->value().string() << ")" << endl;
    }

    // Set modes of first display
    displays[ 0 ].setModes( modesList );

    // Set virtual resolution of first display
    if ( mVirtualCombo->currentItem() == 0 )
        displays[ 0 ].setVirtualResolution( QSize( 0, 0 ) );
    else
        displays[ 0 ].setVirtualResolution( stringToResolution( mVirtualCombo->currentText() ) );

    // Write back data
    screen.setDisplays( displays );
    m_data->currentScreens()[ 0 ] = screen;
}

void ScreenPage::slotNewResolution()
{
    if (!m_newDialog) {
        m_newDialog = new NewResolutionDialog(this);
    }
    m_newDialog->exec();
    // TODO: get resolution data from new dialog

}
void ScreenPage::slotTuneResolution()
{
    if (!m_tuneDialog) {
        m_tuneDialog = new TuneResolutionDialog(this);
    }

    // TODO: set resolution data
    m_tuneDialog->exec();
    // TODO: get resolution data from tune dialog
}
void ScreenPage::slotTestResolution()
{

}

bool ScreenPage::createModeList()
{
    m_modes = m_data->currentModes();
    m_modes += KXData::vesaModes();

    sortModes();

    // TODO: remove duplicates
    // This might not be necessary, as duplicate mode entries do not result in duplicate
    // entries in the combo boxes. But that also means that only the first mode entry is
    // selectable, which probably doesn't matter.

    KXModeDataList::Iterator it = m_modes.begin();
    KXModeDataList::Iterator end = m_modes.end();
    while (it != end)
    {
        if ( !checkCompatibility ( (*it), m_data->currentMonitors()[ 0 ],
                                   m_data->currentVideoCards()[ 0 ] ) )
            it = m_modes.remove( it );
        else
            ++it;
    }

/*
    // Print out list of available modes.
    KXModeDataList::ConstIterator it2 = m_modes.begin();
    KXModeDataList::ConstIterator end2 = m_modes.end();
    for (; it2 != end2; ++it2 )
    {
        kdDebug() << "# " << (*it2).name() << endl;
    }
*/

    if ( m_modes.count() == 0 )
    {
        kdDebug() << "Warning! There are no selectable mode left." << endl;
        emit status( i18n("No modes found compatible with monitor.") );
        return false;
    }

    return true;
}

void ScreenPage::sortModes()
{
    KXModeDataList::Iterator start = m_modes.begin();
    KXModeDataList::Iterator end = m_modes.end();
    KXModeDataList::Iterator last = end;
    --last;

    if ( last == start )
        return;

    while( start != last ) {
        bool swapped = false;
        KXModeDataList::Iterator swap_pos = start;
        KXModeDataList::Iterator x = end;
        KXModeDataList::Iterator y = x;
        y--;
        do {
            --x;
            --y;
            if ( *x < *y ) {
                swapped = true;
                // Swap elements. Make sure that a zero node pointer
                // is copied. The assignment operator of KXModeData
                // does not do this (intentionally).
                KXModeData tmp = *x;
                if ( !(*x).node() ) tmp.setNode( 0 );
                *x = *y;
                if ( !(*y).node() ) (*x).setNode( 0 );
                *y = tmp;
                if ( !tmp.node() ) (*y).setNode( 0 );
	        swap_pos = y;
	    }
        } while( y != start );
        if ( !swapped )
            return;
        start = swap_pos;
        start++;
    }
}

void ScreenPage::setupModeControls()
{
    mResolutionCombo->clear();

    QStringList resolutions;

    KXModeDataList::ConstIterator it = m_modes.begin();
    KXModeDataList::ConstIterator end = m_modes.end();
    for (; it != end; ++it )
    {
        QString res = resolutionToString( QSize( (*it).xResolution(), (*it).yResolution() ) );
        if ( !resolutions.contains( res ) )
            resolutions << res;
    }

    mResolutionCombo->insertStringList( resolutions );

    selectCurrentMode();

//    setupVirtualCombo();
}

void ScreenPage::setupRefreshCombo()
{
//    kdDebug() << "ScreenPage::setupRefreshCombo()" << endl;

    mRefreshCombo->clear();

    QStringList refreshRates;

    KXModeDataList::ConstIterator it = m_modes.begin();
    KXModeDataList::ConstIterator end = m_modes.end();
    for (; it != end; ++it )
    {
        QString res = resolutionToString( QSize( (*it).xResolution(), (*it).yResolution() ) );
        if ( mResolutionCombo->currentText() ==  res )
        {
            QString ref = QString::number( (*it).vRefresh() ) + " Hz";
            if ( !refreshRates.contains( ref ) )
            {
                refreshRates << ref;
//                kdDebug() << "new item: " << res << ": " << ref << "(" << (*it).modeLine() << ")" << endl;
            }
        }
    }

    mRefreshCombo->insertStringList( refreshRates );
}

void ScreenPage::setupColorCombo()
{
    QSize res = stringToResolution( mResolutionCombo->currentText() );

    KXVideoCardData card = m_data->currentVideoCards()[ 0 ];

    mColorCombo->clear();
    if ( checkVideoMemory( card, res, 24 ) ) {
        mColorCombo->insertItem( colorDepthToString( 24 ) );
    }
    if ( checkVideoMemory( card, res, 16 ) ) {
        mColorCombo->insertItem( colorDepthToString( 16 ) );
    }
    if ( checkVideoMemory( card, res, 8 ) ) {
        mColorCombo->insertItem( colorDepthToString( 8 ) );
    }

    setupVirtualCombo();
}

void ScreenPage::setupVirtualCombo()
{
    kdDebug() << "ScreenPage::setupVirtualCombo(): colorDepth: " << selectedColorDepth() << endl;

    mVirtualCombo->clear();

    if ( mResolutionCombo->count() == 0 )
        return;

    QValueList<QSize> vResList;

    // setup list of virtual resolutions with default values
    vResList << QSize( 1800, 1440 ) << QSize( 1600, 1200 ) << QSize( 1280, 1024)
             << QSize( 1152, 864 ) << QSize( 1024, 768 ) << QSize( 800, 600 );

    // Get currently selected resolution
    QSize currentRes = stringToResolution( mResolutionCombo->currentText() );

//    kdDebug() << "------ currentRes: " << currentRes.width() << " " << currentRes.height() << endl;

    // Add current resolution to list, if it is not there yet.
    if ( !vResList.contains( currentRes ) )
        vResList << currentRes;

    // Add current virtual resolution to list, if it is not there yet.
    KXScreenData screen = m_data->currentScreens()[ 0 ];
    QSize vRes = screen.displays()[ 0 ].virtualResolution();
    QString vResText = resolutionToString( vRes );
    if ( !vResList.contains( vRes ) )
        vResList << vRes;

    KXVideoCardData card = m_data->currentVideoCards()[ 0 ];

    // Remove all virtual resolutions, which are smaller than the current real resolution or are too
    // large for the video memory
    QValueList<QSize>::Iterator it = vResList.begin();
    QValueList<QSize>::Iterator end = vResList.end();
    while( it != end )
    {
        if ( (*it).width() < currentRes.width() ||
             (*it).height() < currentRes.height() ||
             !checkVideoMemory( card, *it, selectedColorDepth() ) )
            it = vResList.remove( it );
        else
            ++it;
    }

    // Insert resolutions into combo box
    mVirtualCombo->insertItem( i18n( "Not Used" ) );
    QValueList<QSize>::ConstIterator it2 = vResList.begin();
    QValueList<QSize>::ConstIterator end2 = vResList.end();
    for(; it2 != end2; ++it2 )
    {
        QString res = resolutionToString( *it2 );
        mVirtualCombo->insertItem( res );
    }

//    kdDebug() << "--- vRes: " << vRes.width() << " " << vRes.height() << ": "
//              << vResText << endl;

    // Select current virtual resolution
    if ( vRes != QSize( 0, 0 ) )
        for(int i=0; i < mVirtualCombo->count(); ++i )
        {
            if ( mVirtualCombo->text( i ) == vResText )
            {
                mVirtualCombo->setCurrentItem( i );
                break;
            }
        }
}

KXModeData ScreenPage::selectedMode()
{
    KXModeDataList::ConstIterator it = m_modes.begin();
    KXModeDataList::ConstIterator end = m_modes.end();
    for (; it != end; ++it )
    {
        QString res = QString::number( (*it).xResolution() ) + 'x' + QString::number( (*it).yResolution() );
        if ( mResolutionCombo->currentText() ==  res )
        {
            QString ref = QString::number( (*it).vRefresh() ) + " Hz";
            if ( mRefreshCombo->currentText() == ref )
                return *it;
        }
    }

    kdDebug() << "Error! Could not find select mode in mode list." << endl;

    return KXModeData( "Illegal", 0, KXModeTiming(), KXModeTiming(), "");
}

int ScreenPage::selectedColorDepth()
{
    QString str = mColorCombo->currentText();

    return stringToColorDepth( str );
}

void ScreenPage::selectCurrentMode()
{
    KXScreenData screen = m_data->currentScreens()[ 0 ];
    KXDisplayData display = screen.displays()[ 0 ];
    QString currentModeName = display.modes()[ 0 ];

    kdDebug() << "resolutions: " << display.modes().join(" & ") << endl;
    kdDebug() << "currentResolution: " << currentModeName << endl;

    KXModeData currentMode;

    KXModeDataList::ConstIterator it = m_modes.begin();
    KXModeDataList::ConstIterator end = m_modes.end();
    for (; it != end; ++it )
    {
        if ( (*it).name() == currentModeName )
        {
            currentMode = (*it);
            break;
        }
    }
    if (it == end)
    {
        // Current mode is not in list of available modes. So select the mode
        // with the highest resolution and highest refresh rate and notify the
        // change.
        kdDebug() << "Warning! Current mode not in mode list." << endl;
        setupRefreshCombo();
        setupColorCombo();
//        setupVirtualCombo();
        emit changed();
        return;
    }

    QString res = resolutionToString( QSize( currentMode.xResolution(),
                                             currentMode.yResolution() ) );

    bool found = false;
    for (int i = 0; i < mResolutionCombo->count(); ++i)
    {
        if (mResolutionCombo->text(i) == res)
        {
            mResolutionCombo->setCurrentItem( i );
            found = true;
            break;
        }
    }
    if (!found)
    {
        kdDebug() << "Error! Current mode not in resolution combo box." << endl;
        emit changed();
    }

    setupRefreshCombo();
    setupColorCombo();
//    setupVirtualCombo();

    QString ref = QString::number( currentMode.vRefresh() ) + " Hz";

    found = false;
    for (int i = 0; i < mRefreshCombo->count(); ++i)
    {
        if (mRefreshCombo->text(i) == ref)
        {
            mRefreshCombo->setCurrentItem( i );
            found = true;
            break;
        }
    }
    if (!found)
    {
        kdDebug() << "Error! Current mode not in refresh combo box." << endl;
        emit changed();
    }

    QString colorDepth = colorDepthToString( screen.defaultDepth() );
    found = false;
    for (int i = 0; i < mColorCombo->count(); ++i)
    {
        if ( mColorCombo->text(i) == colorDepth )
        {
            mColorCombo->setCurrentItem( i );
            found = true;
            break;
        }
    }
    if (!found)
    {
        kdDebug() << "Error! Color depth not available." << endl;
        emit changed();
    }
}

/**
    Check if mode is compatible with monitor and graphics card. If the maximum
    frequencies of the monitor are set to 0, the corresponding mode attributes
    are not restricted.
*/
bool ScreenPage::checkCompatibility( const KXModeData &mode, const KXMonitorData &monitor,
                                     const KXVideoCardData &card )
{
    if ( mode.hFrequency() > monitor.hSyncMax() && monitor.hSyncMax() )
        return false;
    if ( mode.hFrequency() < monitor.hSyncMin() )
        return false;
    if ( mode.vRefresh() > monitor.vSyncMax() && monitor.vSyncMax() )
        return false;
    if ( mode.vRefresh() < monitor.vSyncMin() )
        return false;

//    kdDebug() << "-- MaxClock: " << card.maxClock() << "  DotClock: " << mode.dotClock() << endl;
    if ( card.maxClock() > 0 ) {
        if ( card.maxClock() < mode.dotClock() ) {
            return false;
        }
    }

    // Check, if mode works with lowest supported color depth
    if ( !checkVideoMemory( card, QSize( mode.xResolution(), mode.yResolution() ), 8 ) ) return false;

/*
    kdDebug() << "Monitor h: (" << monitor.hSyncMin() << ","
              << monitor.hSyncMax() << ") v: (" << monitor.vSyncMin() << ","
              << monitor.vSyncMax() << ")" << endl;
    kdDebug() << "Checking " << mode.name() << " h: " << mode.hFrequency() << " v: "
              << mode.vRefresh() << ": " << (result ? "yes" : "no" ) << endl;
*/

    return true;
}

bool ScreenPage::checkVideoMemory( const KXVideoCardData &card, const QSize &resolution, int colorDepth )
{
    int mem = card.videoRam() * 1024 * 8;
//    kdDebug() << "-- video mem: " << mem/1024/8 << endl;
    if ( mem <= 0 ) return true;

    int pixels = resolution.width() * resolution.height();
    int bits;
    if ( colorDepth == 24 )
        bits = pixels * 32;
    else
        bits = pixels * colorDepth;
    if ( bits > mem ) return false;

    return true;
}

QSize ScreenPage::stringToResolution(const QString &res)
{
    QStringList l = QStringList::split("x",res);
    return QSize( l[ 0 ].toInt(),l[ 1 ].toInt() );
}

QString ScreenPage::resolutionToString( const QSize &size )
{
  return QString::number( size.width() ) + 'x' + QString::number( size.height() );
}

QString ScreenPage::colorDepthToString( int depth )
{
    return i18n( "colordepth", QString("%1").arg( depth ).latin1() );
}

int ScreenPage::stringToColorDepth( const QString &str )
{
    if ( str == colorDepthToString ( 8 ) ) {
        return 8;
    }
    if ( str == colorDepthToString ( 16 ) ) {
        return 16;
    }
    if ( str == colorDepthToString ( 24 ) ) {
        return 24;
    }

    return -1;
}
