/**
 * This file is part of the KAudioCreator package
 * Copyright (C) 2003 Benjamin C Meyer (ben+kaudiocreator at meyerhome dot net)
 *
 * 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 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 Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */


#include <dcopref.h>
#include <fixx11h.h>

#include <tdeapplication.h>
#include <tdeconfig.h>
#include <kcombobox.h>
#include <kdebug.h>
#include <kinputdialog.h>
#include <tdelistview.h>
#include <kprocess.h>
#include <tdemessagebox.h>
#include <kurl.h>

#include <tqfileinfo.h>
#include <tqlabel.h>
#include <tqptrlist.h>
#include <tqpushbutton.h>
#include <tqregexp.h>
#include <tqspinbox.h>
#include <tqtimer.h>

#include "job.h"
#include "kcompactdisc.h"
#include "libkcddb/cdinfodialogbase.h"
#include "prefs.h"
#include "tracksimp.h"

/**
 * Constructor, connect up slots and signals.
 */
TracksImp::TracksImp( TQWidget* parent, const char* name) :
    Tracks(parent,name),
    cddbInfo() 
{
    cd = new TDECompactDisc;

    connect(cd,TQ_SIGNAL(discChanged(unsigned)),this,TQ_SLOT(newDisc(unsigned)));

    connect(trackListing, TQ_SIGNAL(clicked( TQListViewItem * )), this, TQ_SLOT(selectTrack(TQListViewItem*)));
    connect(trackListing, TQ_SIGNAL(doubleClicked(TQListViewItem *)), this, TQ_SLOT(editInformation()));
    connect(trackListing, TQ_SIGNAL(returnPressed(TQListViewItem *)), this, TQ_SLOT(editInformation()));
    connect(selectAllTracksButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(selectAllTracks()));
    connect(deselectAllTracksButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(deselectAllTracks()));

    connect(deviceCombo, TQ_SIGNAL(textChanged(const TQString &)), this, TQ_SLOT(changeDevice(const TQString &)));

    selectAllTracksButton->setEnabled( false );
    deselectAllTracksButton->setEnabled( false );

    cddb = new KCDDB::Client();
    cddb->setBlockingMode(false);
    connect(cddb, TQ_SIGNAL(finished(CDDB::Result)), this, TQ_SLOT(lookupCDDBDone(CDDB::Result)));
    trackListing->setSorting(-1, false);
    loadSettings();
}

/**
 * store the current device from the combo.
 */
TracksImp::~TracksImp() {
    TQStringList list;
    if( deviceCombo->count() != 0)
        list.append(deviceCombo->currentText());
    for ( int i=0; i<deviceCombo->count();i++ ) {
        TQString text = deviceCombo->text(i);
        if( list.find(text) == list.end())
            list.append(text);
        if( list.count() == 5)
            break;
    }

    Prefs::setDevice(list);
    Prefs::writeConfig();
}

/**
 * Load the class settings.
 */
void TracksImp::loadSettings() {
    TQStringList list;

    // Add the saved list, no dups
    TQStringList prefsList = Prefs::device();
    TQStringList::Iterator it;
    for ( it = prefsList.begin(); it != prefsList.end(); ++it ) {
        if( list.find( *it ) == list.end())
            list.append(*it);
    }
    // Get current list, no dups
    for ( int i=0; i<deviceCombo->count();i++ ) {
        TQString text = deviceCombo->text(i);
        if( list.find(text) == list.end())
            list.append(text);
    }

    // Set list, get top one
    deviceCombo->clear();
    deviceCombo->insertStringList(list);

    changeDevice(deviceCombo->currentText());
}

void TracksImp::newDisc(unsigned discId)
{
    if (discId == TDECompactDisc::missingDisc)
    {
        kdDebug() << "newDisc - No disc" << endl;
        cddbInfo.clear();
        cddbInfo.title = i18n("No disc");
        newAlbum();
        emit(hasCD(false));

        selectAllTracksButton->setEnabled( false );
        deselectAllTracksButton->setEnabled( false );

        return;
    }

    kdDebug() << "newDisc - " << discId << endl;
    emit(hasCD(true));

    selectAllTracksButton->setEnabled( true );
    deselectAllTracksButton->setEnabled( true );

    cddbInfo.clear();

    cddbInfo.id = TQString::number(discId, 16).rightJustify(8,'0');
    cddbInfo.length = cd->discLength();

    cddbInfo.artist = cd->discArtist();
    cddbInfo.title = cd->discTitle();

    // If it's a sampler, we'll do artist/title.
    bool isSampler = (cddbInfo.title.compare("Various") == 0);
    KCDDB::TrackInfo track;
    for (unsigned i = 1; i <= cd->tracks(); i++) {
        if (isSampler)
            track.title = cd->trackArtist(i) + " / " + cd->trackTitle(i);
        else
            track.title = cd->trackTitle(i);
        cddbInfo.trackInfoList.append(track);
    }

    newAlbum();

    if (Prefs::performCDDBauto())
        lookupCDDB();
}

/**
 * @return if there is a cd inserted or not.
 */
bool TracksImp::hasCD(){
    return cd->discId() != TDECompactDisc::missingDisc;
}

/**
 * The device text has changed.
 * @param file - the new text to check.
 */
void TracksImp::changeDevice(const TQString &file ) {
    TQString newDevice = TDECompactDisc::urlToDevice(file);

    if( newDevice == cd->device() ) {
        //tqDebug("Device names match, returning");
        return;
    }

    TQFileInfo fileInfo(newDevice);
    if( !fileInfo.exists() || fileInfo.isDir()) {
        //tqDebug("Device file !exist or isDir or !file");
        return;
    }

    if (!cd->setDevice(newDevice, 50, false))
    {
        TQString errstring =
          i18n("CDROM read or access error (or no audio disk in drive).\n"\
            "Please make sure you have access permissions to:\n%1")
            .arg(file);
        KMessageBox::error(this, errstring, i18n("Error"));
    }
}

/**
 * Helper function (toolbar button) for users.
 **/
void TracksImp::performCDDB() {
    if (!hasCD()) {
        KMessageBox::sorry(this, i18n("Please insert a disk."),
               i18n("CDDB Failed"));
        return;
    }

    lookupCDDB();
}

/**
 * See if we can't get the cddb value for this cd.
 */
void TracksImp::lookupCDDB() {
    cddb->config().reparse();
    cddb->lookup(cd->discSignature());
}

/**
 * The non blocking CDDB function calling has finished.    Report an error or
 * continue.
 * @param result the success or failure of the cddb retrieval.
 */
void TracksImp::lookupCDDBDone(CDDB::Result result ) {
    if ((result != KCDDB::CDDB::Success) &&
        (result != KCDDB::CDDB::MultipleRecordFound))
    {
        KMessageBox::sorry(this, i18n("Unable to retrieve CDDB information."), i18n("CDDB Failed"));
        return;
    }

    // Choose the cddb entry
    KCDDB::CDInfo info = cddb->bestLookupResponse();
    // TODO Why doesn't libcddb not return MultipleRecordFound?
    //if( result == KCDDB::CDDB::MultipleRecordFound ) {
    if( Prefs::promptIfIncompleteInfo() && cddb->lookupResponse().count() > 1 ) {
        TQString searchedCDId = cddbInfo.id;
        CDInfoList cddb_info = cddb->lookupResponse();
        CDInfoList::iterator it;
        TQStringList list;
        for ( it = cddb_info.begin(); it != cddb_info.end(); ++it  ) {
            list.append( TQString("%1, %2, %3").arg((*it).artist).arg((*it).title)
              .arg((*it).genre));
        }

        bool ok(false);
        TQString res = KInputDialog::getItem(
                        i18n("Select CDDB entry"),
                        i18n("Select a CDDB entry:"), list, 0, false, &ok,
                        this );
        if ( ok ) {
            // The user selected and item and pressed OK
            uint c = 0;
            for ( TQStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
                if( *it == res)    break;
                c++;
            }
            if( c < cddb_info.size() )
                info = cddb_info[c];
        } else {
            return;
            // user pressed Cancel
        }
    // Check that the CD we looked up is the one now loaded.
    // The user might have ejected the CD while we were in the
    // KInputDialog event loop, and replaced it with another one.
        if ( searchedCDId != cddbInfo.id )
            return;
    }

    // Some sanity provisions to ensure that the number of records matches what
    // the CD actually contains.
    while (info.trackInfoList.count() < cddbInfo.trackInfoList.count())
    {
        info.trackInfoList.append(KCDDB::TrackInfo());
    }
    while (info.trackInfoList.count() > cddbInfo.trackInfoList.count())
    {
        info.trackInfoList.pop_back();
    }
    cddbInfo = info;
    newAlbum();

    // See if the user wishes to automaticly rip after successfully retrieving
    if( Prefs::autoRip())
        ripWholeAlbum();
}

/**
 * Bring up the dialog to edit the information about this album.
 * If there is not currently selected track return.
 * If ok is pressed then store the information and update track name.
 */
void TracksImp::editInformation( )
{
    if( !hasCD() ) return;
    // Create dialog.
    KDialogBase *dialog = new KDialogBase( this, "name", false, i18n( "CD Editor" ),
                                           KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true );
    CDInfoDialogBase *base = new CDInfoDialogBase(dialog, "Album info editor dialog");
    // Workaround the fact that CDInfoDialogBase doesn't take
    // a const TrackOffsetList
    TQValueList<unsigned> discSig = cd->discSignature();
    base->setInfo(cddbInfo, discSig);
    dialog->setMainWidget(base);

    // Show dialog->and save results.
    bool okClicked = dialog->exec();
    if( okClicked ) {
        cddbInfo = base->info();
        newAlbum();
        KCDDB::Cache::store(cddbInfo);
    }
    delete dialog;
}

TQString TracksImp::formatTime(unsigned ms)
{
    TQTime time;

    time = time.addMSecs((int)ms);

    // Use ".zzz" for milliseconds...
    TQString temp2;
    if (time.hour() > 0)
        temp2 = time.toString("hh:mm:ss");
    else
        temp2 = time.toString("mm:ss");
    return temp2;
}

/**
 * Helper function.
 * Selects all tracks and then calls startSession to rip them all.
 */
void TracksImp::ripWholeAlbum() {
    selectAllTracks();
    startSession();
}

/**
 * Start of the "ripping session" by emiting signals to rip the selected tracks.
 * If any album information is not set, notify the user first.
 */
void TracksImp::startSession( int encoder )
{
    TQPtrList<TracksItem> selected = selectedTracks();

    if( selected.isEmpty() )
    {
        int i = KMessageBox::questionYesNo( this, i18n("No tracks have been selected.  Would you like to rip the entire CD?"),
                                            i18n("No Tracks Selected"), i18n("Rip CD"), KStdGuiItem::cancel() );
        if( i == KMessageBox::No )
            return;

        selectAllTracks();
        selected = selectedTracks();
    }

    TQStringList list;
    if( cddbInfo.genre == "Unknown" )
        list += "Genre";
    if( cddbInfo.year == 0 )
        list += "Year";
    if( cddbInfo.artist == "Unknown Artist")
        list += "Artist";
    if( cddbInfo.title == "Unknown Album")
        list += "Album";

    if( Prefs::promptIfIncompleteInfo() && list.count() > 0 )
    {
        int r = KMessageBox::questionYesNo( this, 
                 i18n( "Part of the album is not set: %1.\n (To change album information click the \"Edit Information\" button.)\n Would you like to rip the selected tracks anyway?").arg(list.join(", ")), i18n("Album Information Incomplete"), i18n("Rip"), KStdGuiItem::cancel() );

        if( r == KMessageBox::No )
            return;
    }

    if ( encoder == -1 )
        encoder = Prefs::currentEncoder();

    Job *lastJob = 0;
    TracksItem *item = selected.first();

    for( ; item ; item = selected.next() )
    {
        Job *newJob = new Job();
        newJob->encoder = encoder;
        newJob->device  = cd->device();
        newJob->album   = cddbInfo.title;
        newJob->genre   = cddbInfo.genre;
        if( newJob->genre.isEmpty() )
            newJob->genre = "Pop";

        newJob->group   = cddbInfo.artist;
        newJob->comment = cddbInfo.extd;
        newJob->year    = cddbInfo.year;
        newJob->track   = item->track();

//         newJob->track_title   = item->title();
        newJob->track_title   = item->text( HEADER_TRACK_NAME );
        newJob->track_artist  = item->artist();
        newJob->track_comment = item->comment();
        lastJob = newJob;
        emit( ripTrack(newJob) );
    }
    if( lastJob)
        lastJob->lastSongInAlbum = true;

    KMessageBox::information(this,
    i18n("%1 Job(s) have been started.  You can watch their progress in the "
         "jobs section.").arg( selected.count() ),
    i18n("Jobs have started"), i18n("Jobs have started"));
}

/**
 * Selects and unselects the tracks.
 * @param currentItem the track to swich the selection choice.
 */
void TracksImp::selectTrack( TQListViewItem *item )
{
    if( !item )
        return;

#define item static_cast<TracksItem*>(item)
    item->setChecked( !item->checked() );
#undef item
}

TQPtrList<TracksItem> TracksImp::selectedTracks()
{
    TQPtrList<TracksItem> selected;
    TracksItem *item = static_cast<TracksItem*>(trackListing->firstChild());

    while( item )
    {
        if( item->checked() ) 
            selected.append( item );
        item = static_cast<TracksItem*>(item->nextSibling());
    }
    return selected;
}

/**
 * Turn on all of the tracks.
 */
void TracksImp::selectAllTracks()
{
    TracksItem *currentItem = static_cast<TracksItem*>(trackListing->firstChild());
    while( currentItem )
    {
        currentItem->setChecked( true );
        currentItem = static_cast<TracksItem*>(currentItem->nextSibling());
    }
}

/**
 * Turn off all of the tracks.
 */
void TracksImp::deselectAllTracks()
{
    TracksItem *currentItem = static_cast<TracksItem*>(trackListing->firstChild());
    while( currentItem )
    {
        currentItem->setChecked( false );
        currentItem = static_cast<TracksItem*>(currentItem->nextSibling());
    }
}

/**
 * Set the current stats for the new album being displayed.
 */
void TracksImp::newAlbum()
{
    TQString albumText = cddbInfo.title;
    if( !cddbInfo.artist.isEmpty() )
        albumText = cddbInfo.artist + i18n( " - " ) + albumText;

    albumName->setText( albumText );
    trackListing->clear();
    selectAllTracksButton->setEnabled(false);
    deselectAllTracksButton->setEnabled(false);

    emit( hasTracks(false) );

    KCDDB::TrackInfoList t = cddbInfo.trackInfoList;

    bool isSampler = true;
    for( unsigned i = 0; i < t.count(); i++ )
    {
        if (t[i].title.find(" / ") == -1)
        {
            isSampler = false;
            break;
        }
    }

    TracksItem *last = 0;
    for( unsigned i = 0; i < t.count(); i++ )
    {
        TQString trackArtist;
        TQString title;

        if( isSampler )
        {
            // Support for multiple artists stripping.
            int delimiter = t[i].title.find(" / ");
            Q_ASSERT( delimiter != -1 );
            trackArtist = t[i].title.left(delimiter);
            title = t[i].title.mid(delimiter + 3);
        }
        else {
            trackArtist = cddbInfo.artist;
            title = t[i].title;
        }

        // There is a new track for this title.  Add it to the list of tracks.
        TQString trackLength = formatTime(cd->trackLength(i+1));
        last = new TracksItem( trackListing, last, title, trackArtist, i+1, trackLength, t[i].extt );
    }

    if( t.count() )
    {
        // Set the current selected track to the first one.
        trackListing->setCurrentItem(trackListing->firstChild());
        selectAllTracksButton->setEnabled(true);
        deselectAllTracksButton->setEnabled(true);
        emit(hasTracks(true));
    }
}


/**
 * If the user presses the F2 key, trigger renaming of the title.
 * @param event the TQKeyEvent passed to this event handler.
 */
void TracksImp::keyPressEvent(TQKeyEvent *event)
{
    TQListViewItem *item = trackListing->selectedItem();
    if( !item ) return;

    if( event->key() == TQt::Key_F2 ) 
    {
        item->setRenameEnabled( HEADER_TRACK_NAME, true );
        item->startRename( HEADER_TRACK_NAME );
        event->accept();
    }
    else
        Tracks::keyPressEvent(event);
}

/**
 * Eject the current cd device
 */
void TracksImp::eject() {
    ejectDevice(cd->device());
}

/**
 * Eject a device
 * @param deviceToEject the device to eject.
 */
void TracksImp::ejectDevice(const TQString &deviceToEject) {
    changeDevice(deviceToEject);

    cd->eject();
}

#include "tracksimp.moc"

