/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*     Zhiqiang Yang <zhiqiang.yang@nokia.com>
* 
* Description:
*     The source file of the file manager utilities
*/

#include "fmutils.h"

#include <QDir>
#include <QFileInfo>

#include <hbglobal.h>

/*!
    Used to get drive type for convenience.
*/
FmDriverInfo::DriveType FmDriverInfo::driveType()
{
    FmDriverInfo::DriveType driveType;
    if( mDriveState & FmDriverInfo::EDriveRemovable ) {
        // if drive is removable, it may be mass, usb, or remote drive
        if( mDriveState & FmDriverInfo::EDriveMassStorage ) {
            driveType = FmDriverInfo::EDriveTypeMassStorage;
        } else if( mDriveState & FmDriverInfo::EDriveUsbMemory ) {
            driveType = FmDriverInfo::EDriveTypeUsbMemory;
        } else if( mDriveState & FmDriverInfo::EDriveRemote ){
            driveType = FmDriverInfo::EDriveTypeRemote;
        } else {
            driveType = FmDriverInfo::EDriveTypeMemoryCard;
        }
    } else if( mDriveState & FmDriverInfo::EDriveRom ) {
        driveType = FmDriverInfo::EDriveTypeRom;
    } else if( mDriveState & FmDriverInfo::EDriveRam ) {
        driveType = FmDriverInfo::EDriveTypeRam;
    } else {
        // if drive type is not recognized in previous condition,
        // it should be an un-existed drive or phone memory
        if( mDriveState & EDriveNotPresent ){
            // if come here, means 2 conditions:
            //  1 this drive is not exist( FmUtils::queryDriverInfo will give EDriveNotPresent to mDriveState )
            //  2 un-mounted removable drive
            // removable drive filted in previous condifiton. So, give EDriveTypeNotExist 
            driveType = FmDriverInfo::EDriveTypeNotExist;
        } else {
            // drive is existed, only remain the type of EDriveTypePhoneMemory
            driveType = FmDriverInfo::EDriveTypePhoneMemory;
        }
    }
    
    return driveType;
}

/*!
    return drive name for \a path
*/
QString FmUtils::getDriveNameFromPath( const QString &path )
{
    // fillPathWithSplash make sure path length will be at least 3 if it is not empty.
    QString checkedPath( fillPathWithSplash( path ) );
    if( checkedPath.length() < 3 ) {
        return QString();
    }
    return checkedPath.left( 3 );
}

/*!
    return drive letter for \a path
*/
QString FmUtils::getDriveLetterFromPath( const QString &path )
{
	if( path.length() <2 ) {
        return QString();
    }
    return path.left( 1 );
}

/*!
    return formatted string for size.
*/
QString FmUtils::formatStorageSize( quint64 size )
{
	if ( size < 1000 ) {
		return QString::number( size ) + " B";
	} else if ( size < 1000 * 1000 ) {
		return QString::number( size / 1024.0, 'f', 2 ) + " KB";
	} else if ( size < 1000 * 1000 * 1000 ) {
		return QString::number( size / (1024.0 * 1024.0), 'f', 1 ) + " MB";
	} else {
	    return QString::number( size / ( 1024.0 * 1024.0 * 1024.0 ), 'f', 1 ) + " GB";	    
	}
}

/*!
    Judge if a path is drive path, not a folder or a file.
    For example, true for C:\ while false for C:\data
*/
bool FmUtils::isDrive( const QString &path )
{
   bool ret( false );
   if( path.length() <= 3 && path.length() >=2 ) {
       ret = true;
   }
   
   return ret;   
}

/*!
    All "/" and "\" in \a path will be changed to QDir::separator
    \sa fillPathWithSplash, fillPathWithSplash will append QDir::separator in the end
*/
QString FmUtils::formatPath( const QString &path  )
{
    QString formatPath;
    if( path.isEmpty() ) {
        return formatPath;
    }
    
    foreach( const QChar &ch, path ) {
        if( ch == QChar('\\') || ch == QChar('/') ) {
            formatPath.append( QDir::separator() );
        } else {
            formatPath.append( ch );
        }
    }

    return formatPath;
}

/*!
    Fill splash in the end of \a filePath. And all "/" and "\" will be changed to QDir::separator
    Please do not call this function if path is a file.
    Use \a formatPath instead, \a formatPath will not append QDir::separator in the end.
    \sa formatPath only changed "/" and "\" to QDir::separator
*/
QString FmUtils::fillPathWithSplash( const QString &filePath )
{
    QString newFilePath;
    if( filePath.isEmpty() ) {
        return newFilePath;
    }

    newFilePath = formatPath( filePath );
    
    if( newFilePath.right( 1 )!= QDir::separator() ){
        newFilePath.append( QDir::separator() );
    }
    return newFilePath;
}

/*!
    Remove splash in the end of \a filePath.
*/
QString FmUtils::removePathSplash( const QString &filePath )
{
    QString newFilePath( filePath );
    if( filePath.right( 1 ) == QChar( '/' ) || filePath.right(1) == QString( "\\" ) ) {
        newFilePath = filePath.left( filePath.length() - 1 );
    }
    return newFilePath;
}

/*!
    fill \a driveList of drives can be shown in driveListView
*/
void FmUtils::getDriveList( QStringList &driveList, bool isHideUnAvailableDrive )
{
    if( isHideUnAvailableDrive ) {
        FM_LOG( QString( "getDriveList HideUnAvailableDrive_true" ) );
    } else {
        FM_LOG( QString( "getDriveList HideUnAvailableDrive_false" ) );
    }
    QFileInfoList infoList = QDir::drives();

    foreach( const QFileInfo &fileInfo, infoList ) {
        QString driveName = fileInfo.absolutePath();
        if( checkDriveAccessFilter( driveName ) ) {
            if( !isHideUnAvailableDrive ) {
                driveList.append( driveName );
            }
            else if ( isDriveAvailable( driveName ) ) {
                driveList.append( driveName );
            }
        }
    }
    return;
}

/*!
    return the combined string of drive name and volume( e.g. C: Phone memory for C drive)
    \a driveName the drive name.
    If set \a isFillWithDefaultVolume true, fill result with default volume if drive volume is empty.
*/
QString FmUtils::fillDriveVolume( QString driveName, bool isFillWithDefaultVolume )
{
    QString ret;
    if( driveName.isEmpty() ) {
        return ret;
    }
    QString tempDriveName = fillPathWithSplash( driveName );

    QString checkedDriveName( removePathSplash( driveName ) );
    
    FmDriverInfo driverInfo = FmUtils::queryDriverInfo( tempDriveName );
    QString volumeName = driverInfo.volumeName();
    
    if( volumeName.isEmpty() && isFillWithDefaultVolume ){
        switch ( driverInfo.driveType() )
            {
            case FmDriverInfo::EDriveTypeMassStorage:
                ret = hbTrId( "txt_fmgr_dblist_1_mass_storage" ).arg( checkedDriveName );
                break;
            case FmDriverInfo::EDriveTypeUsbMemory:
                ret = hbTrId( "txt_fmgr_dblist_1_usb_memory" ).arg( checkedDriveName );
                break;
            case FmDriverInfo::EDriveTypeMemoryCard:
                ret = hbTrId( "txt_fmgr_dblist_1_memory_card" ).arg( checkedDriveName );
                break;
            case FmDriverInfo::EDriveTypePhoneMemory:
                ret = hbTrId( "txt_fmgr_dblist_1_device_memory" ).arg( checkedDriveName );
                break;
            case FmDriverInfo::EDriveTypeNotExist:// no such drive
            default:// do not need get volume for other drive types, e.g. Ram, Rom
                break;
            }    
    }
    
    if( ret.isEmpty() ) {
        // ret is not got. fill ret as default method
        // txt_fmgr_dblist_1_2 is not correct, can not use.
        ret = hbTrId( "%1 %2" ).arg( checkedDriveName ).arg( volumeName );
    }
    return ret;
}

/*!
    return the default volume for drive
    \a driveName the drive.
    \sa getVolumeNameWithDefaultNameIfNull
*/
QString FmUtils::getDefaultVolumeName( const QString &driveName )
{
    QString defaultVolumeName;
    FmDriverInfo driverInfo = FmUtils::queryDriverInfo( driveName );
          
    switch ( driverInfo.driveType() )
        {
        case FmDriverInfo::EDriveTypeMassStorage:
            defaultVolumeName = hbTrId("Mass storage"); 
            break;
        case FmDriverInfo::EDriveTypeUsbMemory:
            defaultVolumeName = hbTrId("USB memory"); 
            break;
        case FmDriverInfo::EDriveTypeMemoryCard:
            defaultVolumeName = hbTrId("Memory card");
            break;
        case FmDriverInfo::EDriveTypePhoneMemory:
            defaultVolumeName = hbTrId("Device memory");
            break;
        case FmDriverInfo::EDriveTypeNotExist:// no such drive, break and return empty string
        default:// do not have default volume for other drive types, e.g. Ram, Rom
            break;
        }   
    return defaultVolumeName;
}

/*!
    get the volume name of the drive, if it is null, then return the default name.
    \a driveName the drive letter.
    \a defaultName whether it is the default name.
    return the volume name.
*/
QString FmUtils::getVolumeNameWithDefaultNameIfNull( const QString &driveName, bool &defaultName )
{
    // do not re-use FmUtils::getDefaultVolumeName to save one time for call FmUtils::queryDriverInfo
    FmDriverInfo driverInfo = FmUtils::queryDriverInfo( driveName );
          
    QString volumeName = driverInfo.volumeName();    
    //save the volume status, whether it is default name
    defaultName = false;
    //volume name may be null if not set, it will be set at least for one time in the following while cycling.
    if ( volumeName.isEmpty() ) {
        switch ( driverInfo.driveType() )
            {
            case FmDriverInfo::EDriveTypeMassStorage:
                volumeName = hbTrId("Mass storage"); 
                break;
            case FmDriverInfo::EDriveTypeUsbMemory:
                volumeName = hbTrId("USB memory"); 
                break;
            case FmDriverInfo::EDriveTypeMemoryCard:
                volumeName = hbTrId("Memory card");
                break;
            case FmDriverInfo::EDriveTypePhoneMemory:
                volumeName = hbTrId("Device memory");
                break;
            case FmDriverInfo::EDriveTypeNotExist:// no such drive
            default:// do not need get volume for other drive types, e.g. Ram, Rom
                break;
            }   
        if( !volumeName.isEmpty() ) {
             defaultName = true;
        }
    }
    return volumeName;
}

/*!
    Check if two path are the same.
*/
bool FmUtils::isPathEqual( const QString &pathFst, const QString &pathLast )
{
    QString fst( fillPathWithSplash( pathFst ) );
    QString last( fillPathWithSplash( pathLast ) );
    if( fst.compare( last, Qt::CaseInsensitive ) == 0 ) {
        return true;
    }
    return false;
}

/*!
    Check if a name is illegal for folder/file naming rule.
*/
bool FmUtils::checkFolderFileName( const QString& name )
{
    // trim space firest, because there may be some spaces after "." ,  it is also not valid
    QString trimmedName( name.trimmed() );
	if( trimmedName.isEmpty() ) {
		return false;
	}
    if( trimmedName.endsWith( QChar('.'),  Qt::CaseInsensitive ) ) {
        return false;
    }
    if( trimmedName.contains( QChar('\\'), Qt::CaseInsensitive ) ||
        trimmedName.contains( QChar('/'),  Qt::CaseInsensitive ) ||
        trimmedName.contains( QChar(':'),  Qt::CaseInsensitive ) ||
        trimmedName.contains( QChar('*'),  Qt::CaseInsensitive ) ||
        trimmedName.contains( QChar('?'),  Qt::CaseInsensitive ) ||
        trimmedName.contains( QChar('\"'), Qt::CaseInsensitive ) ||
        trimmedName.contains( QChar('<'),  Qt::CaseInsensitive ) ||
        trimmedName.contains( QChar('>'),  Qt::CaseInsensitive ) ||
        trimmedName.contains( QChar('|'),  Qt::CaseInsensitive ) ){
        return false;
    }
    // use original name to exam max size of file name
    if( name.length() > getMaxFileNameLength() ) {
        return false;
    }
    return true;
}

/*!
    check file or folder path is illegal or not.
    \a fileName file/folder name, used to check illegal characters
    \a path file/folder path, used to check if path is available to use.
    \a errString if return false, errString will be set for error note.
    return true for not illegal and false for illegal path.
*/
bool FmUtils::checkNewFolderOrFile( const QString &fileName, const QString &path, QString &errString )
{
    // first check if fileName is valid, then check if path length is valid, and check if file/foler is existed at last
    QFileInfo fileInfo( path );
    bool ret( true );   
    if (!FmUtils::checkFolderFileName( fileName ) ) {
        errString = hbTrId( "Invalid file or folder name!" );
        ret = false;
    } else if( !FmUtils::checkMaxPathLength( path ) ) {
        errString = hbTrId( "the path you specified is too long!" );
        ret = false;
    } else if (fileInfo.exists()) {
        errString = hbTrId( "%1 already exist!" ).arg( fileInfo.fileName() );
        ret = false;
    }
    return ret;
}

/*!
    Check if \a dest is sub level path of \a src
    Used to check True/False when copy a folder to itself or its subfolder
    For example, c:\data\test is sub path of c:\data.
    But c:\data123\test is not sub path of c:\data.
    So after got right part of path, the first char must be \ or /
*/
bool FmUtils::isSubLevelPath( const QString &src, const QString &dest )
{
    FM_LOG("FmUtils::isSubFolder: src=" + src + " dest=" + dest);
    QString checkedSrc( FmUtils::fillPathWithSplash( src ) );
    QString checkedDest( FmUtils::fillPathWithSplash( dest ) );
    
    if( checkedDest.contains( checkedSrc, Qt::CaseInsensitive) &&
            checkedDest.length() > checkedSrc.length() ) {
        // for example c:\data\ vs c:\data\123\ 
        FM_LOG("FmUtils::isSubFolder: true");
        return true;
    }
    // for example c:\data\ vs c:\data\ 
    // for example c:\data\ vs c:\data123\ 

    FM_LOG("FmUtils::isSubFolder: false");
    return false;
}
