/***************************************************************************
 * copyright            : (C) 2005-2006 Seb Ruiz <me@sebruiz.net>          *
 *                                                                         *
 * With some code helpers from TDEIO_IFP                                     *
 *                        (c) 2004 Thomas Loeber <ifp@loeber1.de>          *
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

 /**
  *  iRiver ifp media device code
  *  @author Seb Ruiz <me@sebruiz.net>
  *  @see http://ifp-driver.sourceforge.net/libifp/docs/ifp_8h.html
  *  @note ifp uses a backslash '\' as a directory delimiter for _remote_ files
  */

#define DEBUG_PREFIX "IfpMediaDevice"

#include "ifpmediadevice.h"

AMAROK_EXPORT_PLUGIN( IfpMediaDevice )

#include "debug.h"
#include "metabundle.h"
#include "collectiondb.h"
#include "statusbar/statusbar.h"
#include "transferdialog.h"

#include <tdeapplication.h>
#include <tdeconfig.h>           //download saveLocation
#include <kiconloader.h>       //smallIcon
#include <tdemessagebox.h>
#include <tdepopupmenu.h>
#include <kurlrequester.h>     //downloadSelectedItems()
#include <kurlrequesterdlg.h>  //downloadSelectedItems()

#include <tqfile.h>
#include <tqcstring.h>
#include <tqregexp.h>

namespace Amarok {
    extern TDEConfig *config( const TQString& );
    extern TQString cleanPath( const TQString&, bool );
}

/**
 * IfpMediaItem Class
 */

class IfpMediaItem : public MediaItem
{
    public:
        IfpMediaItem( TQListView *parent, TQListViewItem *after = 0 )
            : MediaItem( parent, after )
        {}

        IfpMediaItem( TQListViewItem *parent, TQListViewItem *after = 0 )
            : MediaItem( parent, after )
        {}

        void
        setEncodedName( TQString &name )
        {
            m_encodedName = TQFile::encodeName( name );
        }

        void
        setEncodedName( TQCString &name ) { m_encodedName = name; }

        TQCString
        encodedName() { return m_encodedName; }

        // List directories first, always
        int
        compare( TQListViewItem *i, int col, bool ascending ) const
        {
            #define i static_cast<IfpMediaItem *>(i)
            switch( type() )
            {
                case MediaItem::DIRECTORY:
                    if( i->type() == MediaItem::DIRECTORY )
                        break;
                    return -1;

                default:
                    if( i->type() == MediaItem::DIRECTORY )
                        return 1;
            }
            #undef i

            return MediaItem::compare(i, col, ascending);
        }

    private:
        bool     m_dir;
        TQCString m_encodedName;
};


/**
 * IfpMediaDevice Class
 */

IfpMediaDevice::IfpMediaDevice()
    : MediaDevice()
    , m_dev( 0 )
    , m_dh( 0 )
    , m_connected( false )
    , m_last( 0 )
    , m_tmpParent( 0 )
    , m_td( 0 )
{
    m_name = "iRiver";
    m_hasMountPoint = false;

    m_spacesToUnderscores = configBool("spacesToUnderscores");
    m_firstSort           = configString( "firstGrouping", i18n("None") );
    m_secondSort          = configString( "secondGrouping", i18n("None") );
    m_thirdSort           = configString( "thirdGrouping", i18n("None") );
}

void
IfpMediaDevice::init( MediaBrowser* parent )
{
    MediaDevice::init( parent );
}

IfpMediaDevice::~IfpMediaDevice()
{
    setConfigString( "firstGrouping"    , m_firstSort );
    setConfigString( "secondGrouping"   , m_secondSort );
    setConfigString( "thirdGrouping"    , m_thirdSort );
    setConfigBool( "spacesToUnderscores", m_spacesToUnderscores );

    closeDevice();
}

bool
IfpMediaDevice::checkResult( int result, TQString message )
{
    if( result == 0 )
        return true;

    error() << result << ": " << message << endl;
    return false;
}


bool
IfpMediaDevice::openDevice( bool /*silent*/ )
{
    DEBUG_BLOCK

    usb_init();

    m_dh = (usb_dev_handle*)ifp_find_device();

    TQString genericError = i18n( "Could not connect to iFP device" );

    if( m_dh == NULL )
    {
        error() << "A suitable iRiver iFP device couldn't be found" << endl;
        Amarok::StatusBar::instance()->shortLongMessage( genericError,
                                        i18n("iFP: A suitable iRiver iFP device could not be found")
                                        , KDE::StatusBar::Error );
        return false;
    }

    m_dev = usb_device( m_dh );
    if( m_dev == NULL )
    {
        error() << "Could not get usb_device()" << endl;
        Amarok::StatusBar::instance()->shortLongMessage( genericError,
                                        i18n("iFP: Could not get a USB device handle"), KDE::StatusBar::Error );
        if( ifp_release_device( m_dh ) )
            error() << "warning: release_device failed." << endl;
        return false;
    }

    /* "must be called" written in the libusb documentation */
    if( usb_claim_interface( m_dh, m_dev->config->interface->altsetting->bInterfaceNumber ) )
    {
        error() << "Device is busy.  (I was unable to claim its interface.)" << endl;
        Amarok::StatusBar::instance()->shortLongMessage( genericError,
                                        i18n("iFP: Device is busy"), KDE::StatusBar::Error );
        if( ifp_release_device( m_dh ) )
            error() << "warning: release_device failed." << endl;
        return false;
    }

    int i = ifp_init( &m_ifpdev, m_dh );
    if( i )
    {
        error() << "iFP device: Device cannot be opened." << endl;
        Amarok::StatusBar::instance()->shortLongMessage( genericError,
                                        i18n("iFP: Could not open device"), KDE::StatusBar::Error );
        usb_release_interface( m_dh, m_dev->config->interface->altsetting->bInterfaceNumber );
        return false;
    }

    m_connected = true;

    char info[20];
    ifp_model( &m_ifpdev, info, 20 );
    m_transferDir = TQString(info);
    debug() << "Successfully connected to: " << info << endl;

    listDir( "" );

    return true;
}

bool
IfpMediaDevice::closeDevice()  //SLOT
{
    DEBUG_BLOCK

    if( m_connected )
    {
        if( m_dh )
        {
            usb_release_interface( m_dh, m_dev->config->interface->altsetting->bInterfaceNumber );

            if( ifp_release_device( m_dh ) )
                error() << "warning: release_device failed." << endl;

            ifp_finalize( &m_ifpdev );
            m_dh = 0;
        }

        m_view->clear();

        m_connected = false;
    }

    return true;
}

void
IfpMediaDevice::runTransferDialog()
{
    m_td = new TransferDialog( this );
    m_td->exec();
}

/// Renaming

void
IfpMediaDevice::renameItem( TQListViewItem *item ) // SLOT
{
    if( !item )
        return;

    #define item static_cast<IfpMediaItem*>(item)

    TQCString src  = TQFile::encodeName( getFullPath( item, false ) );
    src.append( item->encodedName() );

     //the rename line edit has already changed the TQListViewItem text
    TQCString dest = TQFile::encodeName( getFullPath( item ) );

    debug() << "Renaming " << src << " to: " << dest << endl;

    if( ifp_rename( &m_ifpdev, src, dest ) ) //success == 0
        //rename failed
        item->setText( 0, item->encodedName() );

    #undef item
}

/// Creating a directory

MediaItem *
IfpMediaDevice::newDirectory( const TQString &name, MediaItem *parent )
{
    if( !m_connected || name.isEmpty() ) return 0;

    TQString cleanedName = cleanPath( name );

    const TQCString dirPath = TQFile::encodeName( getFullPath( parent ) + "\\" + cleanedName );
    debug() << "Creating directory: " << dirPath << endl;
    int err = ifp_mkdir( &m_ifpdev, dirPath );

    if( err ) //failed
        return 0;

    m_tmpParent = parent;
    addTrackToList( IFP_DIR, cleanedName );
    return m_last;
}

MediaItem *
IfpMediaDevice::newDirectoryRecursive( const TQString &name, MediaItem *parent )
{
    TQStringList folders = TQStringList::split( '\\', name );
    TQString progress = "";

    if( parent )
        progress += getFullPath( parent ) + "\\";
    else
        progress += "\\";

    foreach( folders )
    {
        debug() << "Checking folder: " << progress << endl;
        progress += *it;
        const TQCString dirPath = TQFile::encodeName( progress );

        if( ifp_exists( &m_ifpdev, dirPath ) == IFP_DIR )
        {
            m_tmpParent = parent;
            parent = findChildItem( *it, parent );
            if( !parent )
            {
                addTrackToList( IFP_DIR, *it );
                parent = m_last;
            }
        }
        else
        {
            parent = newDirectory( *it, parent );
            if( !parent ) //failed
                return 0;
        }
        progress += "\\";
    }
    return parent;
}

MediaItem *
IfpMediaDevice::findChildItem( const TQString &name, MediaItem *parent )
{
    TQListViewItem *child;

    parent ?
        child = parent->firstChild():
        child = m_view->firstChild();

    while( child )
    {
        if( child->text(0) == name )
            return static_cast<MediaItem*>(child);
        child = child->nextSibling();
    }
    return 0;
}

void
IfpMediaDevice::addToDirectory( MediaItem *directory, TQPtrList<MediaItem> items )
{
    if( !directory || items.isEmpty() ) return;

    m_tmpParent = directory;
    for( TQPtrListIterator<MediaItem> it(items); *it; ++it )
    {
        TQCString src  = TQFile::encodeName( getFullPath( *it ) );
        TQCString dest = TQFile::encodeName( getFullPath( directory ) + "\\" + (*it)->text(0) );
        debug() << "Moving: " << src << " to: " << dest << endl;

        int err = ifp_rename( &m_ifpdev, src, dest );
        if( err ) //failed
            continue;

        m_view->takeItem( *it );
        directory->insertItem( *it );
    }
}

/// Uploading

MediaItem *
IfpMediaDevice::copyTrackToDevice( const MetaBundle& bundle )
{
    if( !m_connected ) return 0;
    m_transferring = true;

    const TQCString src  = TQFile::encodeName( bundle.url().path() );

    TQString directory = "\\"; //root
    bool cleverFilename = false;
    bool addFileToView = true;
    if( m_firstSort != i18n("None") )
    {
        addFileToView = false;
        directory += bundle.prettyText( bundle.columnIndex(m_firstSort) ) + "\\";

        if( m_secondSort != i18n("None") )
        {
            directory += bundle.prettyText( bundle.columnIndex(m_secondSort) ) + "\\";

            if( m_thirdSort != i18n("None") )
                directory += bundle.prettyText( bundle.columnIndex(m_thirdSort) ) + "\\";
        }
        if( m_firstSort == i18n("Album") || m_secondSort == i18n("Album") || m_thirdSort == i18n("Album") )
            cleverFilename = true;
    }

    m_tmpParent = newDirectoryRecursive( directory, 0 ); // recursively create folders as required.

    TQString newFilename;
    // we don't put this in cleanPath because of remote directory delimiters
    const TQString title = bundle.title().replace( '\\', '-' );
    if( cleverFilename && !title.isEmpty() )
    {
        if( bundle.track() > 0 )
            newFilename = cleanPath( TQString::number(bundle.track()) + " - " + title ) + '.' + bundle.type();
        else
            newFilename = cleanPath( title ) + '.' + bundle.type();
    }
    else
        newFilename = cleanPath( bundle.prettyTitle() ) + '.' + bundle.type();

    const TQCString dest = TQFile::encodeName( cleanPath(directory + newFilename) );

    tdeApp->processEvents( 100 );
    int result = uploadTrack( src, dest );

    if( !result ) //success
    {
        addTrackToList( IFP_FILE, cleanPath( newFilename ) );
        return m_last;
    }
    return 0;
}

/// File transfer methods

int
IfpMediaDevice::uploadTrack( const TQCString& src, const TQCString& dest )
{
    debug() << "Transferring " << src << " to: " << dest << endl;

    return ifp_upload_file( &m_ifpdev, src, dest, filetransferCallback, this );
}

int
IfpMediaDevice::downloadTrack( const TQCString& src, const TQCString& dest )
{
    debug() << "Downloading " << src << " to: " << dest << endl;

    return ifp_download_file( &m_ifpdev, src, dest, filetransferCallback, this );
}

void
IfpMediaDevice::downloadSelectedItems()
{
//     TDEConfig *config = Amarok::config( "MediaDevice" );
//     TQString save = config->readEntry( "DownloadLocation", TQString() );  //restore the save directory
    TQString save = TQString();

    KURLRequesterDlg dialog( save, 0, 0 );
    dialog.setCaption( tdeApp->makeStdCaption( i18n( "Choose a Download Directory" ) ) );
    dialog.urlRequester()->setMode( KFile::Directory | KFile::ExistingOnly );
    dialog.exec();

    KURL destDir = dialog.selectedURL();
    if( destDir.isEmpty() )
        return;

    destDir.adjustPath( 1 ); //add trailing slash

//     if( save != destDir.path() )
//         config->writeEntry( "DownloadLocation", destDir.path() );

    TQListViewItemIterator it( m_view, TQListViewItemIterator::Selected );
    for( ; it.current(); ++it )
    {
        TQCString dest = TQFile::encodeName( destDir.path() + (*it)->text(0) );
        TQCString src = TQFile::encodeName( getFullPath( *it ) );

        downloadTrack( src, dest );
    }

    hideProgress();
}

int
IfpMediaDevice::filetransferCallback( void *pData, struct ifp_transfer_status *progress )
{
    // will be called by 'ifp_upload_file' by callback

    tdeApp->processEvents( 100 );

    IfpMediaDevice *that = static_cast<IfpMediaDevice *>(pData);

    if( that->isCanceled() )
    {
        debug() << "Canceling transfer operation" << endl;
        that->setCanceled( false );
        that->setProgress( progress->file_bytes, progress->file_bytes );
        return 1; //see ifp docs, return 1 for user cancel request
    }

    return that->setProgressInfo( progress );
}
int
IfpMediaDevice::setProgressInfo( struct ifp_transfer_status *progress )
{
    setProgress( progress->file_bytes, progress->file_total );
    return 0;
}


/// Deleting

int
IfpMediaDevice::deleteItemFromDevice( MediaItem *item, int /*flags*/ )
{
    if( !item || !m_connected ) return -1;

    TQString path = getFullPath( item );

    TQCString encodedPath = TQFile::encodeName( path );
    int err;
    int count = 0;

    switch( item->type() )
    {
        case MediaItem::DIRECTORY:
            err = ifp_delete_dir_recursive( &m_ifpdev, encodedPath );
            debug() << "Deleting folder: " << encodedPath << endl;
            checkResult( err, i18n("Directory cannot be deleted: '%1'").arg(encodedPath.data()) );
            break;

        default:
            err = ifp_delete( &m_ifpdev, encodedPath );
            debug() << "Deleting file: " << encodedPath << endl;
            count += 1;
            checkResult( err, i18n("File does not exist: '%1'").arg(encodedPath.data()) );
            break;
    }
    if( err == 0 ) //success
        delete item;

    return (err == 0) ? count : -1;
}

/// Directory Reading

void
IfpMediaDevice::expandItem( TQListViewItem *item ) // SLOT
{
    if( !item || !item->isExpandable() || m_transferring ) return;

    while( item->firstChild() )
        delete item->firstChild();

    m_tmpParent = item;

    TQString path = getFullPath( item );
    listDir( path );

    m_tmpParent = 0;
}

void
IfpMediaDevice::listDir( const TQString &dir )
{
    int err = ifp_list_dirs( &m_ifpdev, TQFile::encodeName( dir ), listDirCallback, this );
    checkResult( err, i18n("Cannot enter directory: '%1'").arg(dir) );
}

// will be called by 'ifp_list_dirs'
int
IfpMediaDevice::listDirCallback( void *pData, int type, const char *name, int size )
{
    TQString qName = TQFile::decodeName( name );
    return static_cast<IfpMediaDevice *>(pData)->addTrackToList( type, qName, size );
}

int
IfpMediaDevice::addTrackToList( int type, TQString name, int /*size*/ )
{
    m_tmpParent ?
            m_last = new IfpMediaItem( m_tmpParent ):
            m_last = new IfpMediaItem( m_view );

    if( type == IFP_DIR ) //directory
        m_last->setType( MediaItem::DIRECTORY );

    else if( type == IFP_FILE ) //file
    {
        if( name.endsWith( "mp3", false ) || name.endsWith( "wma", false ) ||
            name.endsWith( "wav", false ) || name.endsWith( "ogg", false ) ||
            name.endsWith( "asf", false ) )

            m_last->setType( MediaItem::TRACK );

        else
            m_last->setType( MediaItem::UNKNOWN );
    }
    m_last->setEncodedName( name );
    m_last->setText( 0, name );
    return 0;
}

/// Capacity, in kB

bool
IfpMediaDevice::getCapacity( TDEIO::filesize_t *total, TDEIO::filesize_t *available )
{
    if( !m_connected ) return false;

    int totalBytes = ifp_capacity( &m_ifpdev );
    int freeBytes = ifp_freespace( &m_ifpdev );

    *total = totalBytes;
    *available = freeBytes;

    return totalBytes > 0;
}

/// Helper functions

TQString
IfpMediaDevice::getFullPath( const TQListViewItem *item, const bool getFilename )
{
    if( !item ) return TQString();

    TQString path;

    if( getFilename ) path = item->text(0);

    TQListViewItem *parent = item->parent();
    while( parent )
    {
        path.prepend( "\\" );
        path.prepend( parent->text(0) );
        parent = parent->parent();
    }
    path.prepend( "\\" );

    return path;
}


void
IfpMediaDevice::rmbPressed( TQListViewItem* qitem, const TQPoint& point, int )
{
    enum Actions { DOWNLOAD, DIRECTORY, RENAME, DELETE };

    MediaItem *item = static_cast<MediaItem *>(qitem);
    if ( item )
    {
        TDEPopupMenu menu( m_view );
        menu.insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "Download" ), DOWNLOAD );
        menu.insertSeparator();
        menu.insertItem( SmallIconSet( Amarok::icon( "folder" ) ), i18n("Add Directory" ), DIRECTORY );
        menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "Rename" ), RENAME );
        menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "Delete" ), DELETE );

        int id =  menu.exec( point );
        switch( id )
        {
            case DOWNLOAD:
                downloadSelectedItems();
                break;

            case DIRECTORY:
                if( item->type() == MediaItem::DIRECTORY )
                    m_view->newDirectory( static_cast<MediaItem*>(item) );
                else
                    m_view->newDirectory( static_cast<MediaItem*>(item->parent()) );
                break;

            case RENAME:
                m_view->rename( item, 0 );
                break;

            case DELETE:
                deleteFromDevice();
                break;
        }
        return;
    }

    if( isConnected() )
    {
        TDEPopupMenu menu( m_view );
        menu.insertItem( SmallIconSet( Amarok::icon( "folder" ) ), i18n("Add Directory" ), DIRECTORY );
        int id =  menu.exec( point );
        switch( id )
        {
            case DIRECTORY:
                m_view->newDirectory( 0 );
                break;
        }
    }
}

TQString IfpMediaDevice::cleanPath( const TQString &component )
{
    TQString result = Amarok::asciiPath( component );

    result.simplifyWhiteSpace();

    result.remove( "?" ).replace( "*", " " ).replace( ":", " " );

//     if( m_spacesToUnderscores )
//         result.replace( TQRegExp( "\\s" ), "_" );

    result.replace( "/", "-" );

    return result;
}

#include "ifpmediadevice.moc"
