#include "discread.h"
#include <QDebug>
#include <QDBusReply>
#include <QDBusConnection>
#include <QDBusObjectPath>


DiscRead::DiscRead(QString device, QObject* parent):QObject(parent){
    mDevice = device;
    //work();
}

DiscRead::~DiscRead(){
    if((nullptr!=mBlockInf) && mBlockInf->isValid())
        delete mBlockInf;
    if((nullptr!=mDriveInf) && mDriveInf->isValid())
        delete mDriveInf;
    if((nullptr!=mFilesystemInf) && mFilesystemInf->isValid())
        delete mFilesystemInf;

    mDriveInf = mBlockInf = mFilesystemInf = nullptr;
}

void DiscRead::work(){
    //1. 初始化成员变量、检测光盘是否准备就绪
    workForInitMembers();
    //2. 检测是否是空盘
    //workFromDrive();
    //3. 检测盘片是否可能已损坏
    //workFromFilesystem();
    //4. 检测是否可追加
    //workForAppend();
    //5. 具体化光盘类型
    //workForMediaType();
    //6. 获取光盘文件系统格式和卷标
    //workFromBlock();
}

void DiscRead::workForInitMembers(){
    mIsGood  = true;    //假设所有的盘均未损坏
    mIsBlank = false;   //非空盘
    mIsReady = false;   //初始时光盘未准备就绪(没有插入光盘或未检测到光盘)
    mCanAppend = false; //假设光盘不可追加
    mDriveInf = mBlockInf = mFilesystemInf = mPropertyInf = nullptr;

    //1. 检测光盘是否已插入
    workForIsReady();
    if(mDevice.isEmpty() || !mIsReady)      //没有设备时不做后续处理
        return;

//    QString discBusPath;
//    QDBusObjectPath busObjectPath;
    QString busDevicePath = mDevice.mid(5);
    //2. 获取Filesystem dbus对象
    mFilesystemInf = new QDBusInterface(DiscBusName,DiscBusObjectPathPrefix+busDevicePath,
                                        DiscBusFilesystem,QDBusConnection::systemBus());
    qDebug()<<"["<<mFilesystemInf<<"] new Filesystem object!";
    if(!mFilesystemInf->isValid())
        qDebug()<<"["<<mDevice<<"] failed to new Filesystem object!";

    //3. 获取Block dbus对象
    mBlockInf = new QDBusInterface(DiscBusName,DiscBusObjectPathPrefix+busDevicePath,
                                   DiscBusBlock,QDBusConnection::systemBus());
    if(!mBlockInf->isValid())
        qDebug()<<"["<<mDevice<<"] failed to new Block object!";

    //4. 根据Property dbus对象
    mPropertyInf = new QDBusInterface(DiscBusName,DiscBusObjectPathPrefix+busDevicePath,
                                      DiscBusProperty,QDBusConnection::systemBus());
    if(!mPropertyInf->isValid()){
        qDebug()<<"["<<mDevice<<"] failed to new Property object!";
        return;
    }

    //5. 异步获取设备的Drive dbus对象
    QDBusPendingCall propertyCall = mPropertyInf->asyncCall("Get",DiscBusBlock,"Drive");
    QDBusPendingCallWatcher* propertyCallWatcher = new QDBusPendingCallWatcher(propertyCall);
    connect(propertyCallWatcher,&QDBusPendingCallWatcher::finished,this,&DiscRead::busPendingCallSlot);
}

/* 异步回调，用于从 "org.freedesktop.UDisks2.Block"中获取"Drive"属性
 * 进而下一步获取"org.freedesktop.UDisks2.Drive"，确定光盘是否为空
 */
void DiscRead::busPendingCallSlot(QDBusPendingCallWatcher* watcher){
    //qDebug()<<".............";
    QDBusPendingReply<QDBusVariant> callReplay = *watcher;
    watcher->deleteLater();
    if(callReplay.isError()){
        qDebug()<<"["<<mDevice<<"] failed query dbus object path!";
        qDebug()<<"["<<mDevice<<"]"<<callReplay.error();
        return;
    }

    //获取设备的Drive object path
    QDBusVariant variantReplay = callReplay.argumentAt<0>();
    QDBusObjectPath* busObjectPath =  (QDBusObjectPath*)(variantReplay.variant().data());

    QString discBusPath = busObjectPath->path();
    if(discBusPath.isEmpty()){
        qDebug()<<"["<<mDevice<<"] cannot new Drive object!";
        return;
    }

    //qDebug()<<"["<<mDevice<<"]"<<discBusPath;
    //5. 根据Drive Object Path获取Drive dbus对象
    mDriveInf = new QDBusInterface(DiscBusName,discBusPath,
                                   DiscBusDrive,QDBusConnection::systemBus());

    workFromDrive();
    workFromBlock();
    workFromFilesystem();
    workForAppend();
    Q_EMIT workFinished();
}

void DiscRead::workForIsReady(){
    if(mDevice.isEmpty())
        return;

    //检测是否插入了光盘
    DiscScsi scsi(mDevice);
    scsi[0] = GPCMD_TEST_UNIT_READY;//0x00
    scsi[5] = 0;
    mIsReady =  scsi.transport();

    qDebug()<<"["<<mDevice<<"]"<<(mIsReady?"is ready!":"is not ready!");
}

/* 从Drive中获取光盘的类型以及是否是空盘
 * 注释：通过scsi读取光盘信息的方式也可以读取isBlank信息，但是获取到的DVD+RW盘结果不准确
 */
void DiscRead::workFromDrive(){
    if (mDevice.isEmpty() || !mIsReady) //没有设备时不做后续处理
        return;

    //qDebug()<<__func__<<__LINE__;
    if((nullptr != mDriveInf) && mDriveInf->isValid()){
        mIsBlank = mDriveInf->property("OpticalBlank").toBool(); //获取"OpticalBlank"属性值
        mMediaType = mDriveInf->property("Media").toString();
        if(mIsBlank)
            mIsGood = true;             //假设所有的空光盘均未损坏

        workForMediaType();
    }else
        qDebug()<<"["<<mDevice<<"] cannot query media type and state!";
}

/* 从Block中获取光盘的卷标与文件系统类型
 */
void DiscRead::workFromBlock(){
    if (mDevice.isEmpty() || !mIsReady) //没有设备时不做后续处理
        return;

    if(mIsBlank)                        //空光盘没有卷标与文件系统，不需要查询
        return;

    if((nullptr==mBlockInf) || !mBlockInf->isValid())
        return;

    mLabel = mBlockInf->property("IdLabel").toString();
    mFilesystemType = mBlockInf->property("IdType").toString();
    //qDebug()<<mFilesystemType.isEmpty()<<(nullptr!=mFilesystemInf)<<(mFilesystemInf->isValid());
}

/* 检测盘盘片可能损坏的逻辑:
 * 光盘没有filesystem(文件系统)的两种状态：空盘和已损坏盘
 * 因此这里我们只检测非空盘是否可能已损坏。
 *
 * 注释：内部存有数据的光盘从udisk2的角度看有两种维度: block(块)和filesystem(文件系统)
 */
void DiscRead::workFromFilesystem(){
    if (mDevice.isEmpty() || !mIsReady)
        return;
    qDebug()<<"["<<mDevice<<"] is"<<(mIsBlank? "empty!":"not empty!");
    if(mIsBlank)
        return;

    /** Filesystem Object 有效但是无法获取到光盘内文件系统的类型(指udf或iso9660)，
     * 此时可能是以下3种情况：
     *   1)linux未正确识别(在自测过程中确实存在光盘未被系统识别的情况，重新插入即可)
     *   2)udf格式光盘加载较慢(自测过程中使用udf盘可以出现该现象；如果使用GVolumeMonitor的信号检测光盘插入，则应该不存在这种可能)
     *   3)光盘可能已损坏
     * 应对手段：建议重新插入光盘
    */
    qDebug()<<"["<<mDevice<<"]"<<mFilesystemType;
    if(mFilesystemType.isEmpty() && (nullptr !=mFilesystemInf && mFilesystemInf->isValid())){
        mIsGood = false;
        qDebug()<<"["<<mDevice<<"] is not good!";
        qDebug()<<"["<<mDevice<<"] may not be correctly identified or damaged, plaease reinsert it!";
        return;
    }
    qDebug()<<"["<<mDevice<<"] is good!";
    qDebug()<<"["<<mDevice<<"]"<<mLabel;
    qDebug()<<"["<<mDevice<<"]"<<mFilesystemType;
}

void DiscRead::workForAppend(){
    uchar* infoData = nullptr;
    disc_info_t* discInfo = nullptr;

    if (mDevice.isEmpty() || !mIsReady || !mIsGood) //没有设备或设备损坏时不做后续处理
        return;

    if(readDiscInformation(&infoData)){
        discInfo = (disc_info_t*)infoData;
        mCanErase = discInfo->erasable;             //
        qDebug()<<"["<<mDevice<<"]"<<(mCanErase?"can erase!":"can not erase!");
        if(1 == discInfo->status)
            mCanAppend = true;

        free(infoData);
        infoData = nullptr;
    }

    if(mIsBlank)                                    //空盘一般说"首次刻录"或"首次会话"这个概念
        return;                                     //空盘一般不说"是否可追加"这个概念

    /** DVD+RW盘从Disc Information中获取到的mCanAppend可能不准确
     *  需要使用profile修正
    */
    readDiscProfile();
    if( (mProfile & MEDIA_DVD_ALL) && (mProfile & (MEDIA_DVD_PLUS_RW | MEDIA_DVD_RW_OVWR)) ){
    //if( (mProfile & MEDIA_DVD_ALL) && (mProfile & MEDIA_DVD_PLUS_RW) ){
        qDebug()<<"["<<mDevice<<"] fix appendable property with profile for DVD+RW/DVD-RW.";
        mCanAppend |= true;
    }

    /* 目前无法处理udf盘的追加刻录功能，所以设定udf不可追加刻录 */
    if(mFilesystemType=="udf")
        mCanAppend = false;
    qDebug()<<"["<<mDevice<<"]"<<(mCanAppend? "can append!":"can not append!");
}

/* 从udisk2获取到光盘介质类型信息后，将其转变为用户可读的表达方式
*/
void DiscRead::workForMediaType(){
    if(mDevice.isEmpty() || !mIsReady || mMediaType.isEmpty())
        return;
    //optical_cd_
    //optical_dvd_r
    //optical_dvd_plus_
    //qDebug()<<__func__<<__LINE__<<mMediaType;
    char ch = mMediaType.at(8).toLatin1();
    //char ch2 = mMediaType.at(12).toLatin1();
    switch(ch){
    case 'c':{   //CD盘
        if(mMediaType == "optical_cd_r"){
            mMediaType = "CD-R";
//            if(!mCanAppend)
//                mMediaType = "CD-ROM";
        }else{
            mMediaType = "CD-RW";
        }
    }
        break;
    case 'd':   //DVD盘
        mMediaType = dvdMediaType(mMediaType);
        break;
    default:
        break;
    }
    qDebug()<<"["<<mDevice<<"]"<<mMediaType;
}

bool DiscRead::readDiscInformation(uchar** infoData){
    uchar* data;
    uint dataLen;
    uchar header[2];
    bool transferRet;

    if(mDevice.isEmpty())
        return false;

    ::memset(header, 0, 2);
    DiscScsi scsi(mDevice);
    scsi[0] = GPCMD_READ_DISC_INFO;//0x51
    scsi[8] = 2;
    scsi[9] = 0;

    dataLen = 0;
    transferRet = scsi.transport(SCSI_READ, header, 2);
    if(transferRet)
        dataLen = from2Byte(header) + 2u;
    else
        qDebug()<<"["<<mDevice<<"] READ DISC INFORMATION length det failed!";

    if(dataLen < 32){
        //qDebug()<<"Device reports bogus disc information length of " <<dataLen<<" for "<<mDevice<<" ...";
        dataLen = 32;
    }

    data          = (uchar*)malloc(dataLen);
    *infoData     = data;
    scsi[7] = dataLen >> 8;
    scsi[8] = dataLen;

    transferRet = scsi.transport(SCSI_READ, data, dataLen);
    if(transferRet){
        quint16 tmp = from2Byte((const uchar*)data) + 2;
        uchar* newData = (uchar*)realloc(data,qMin<uint>(dataLen,tmp));
        if(newData)         //防止失败时内存泄露
            data = newData;
        return true;
    }else{
        qDebug()<<"["<<mDevice<<"] READ DISC INFORMATION with real length "<<dataLen<<" failed!";
        return false;
    }
}

quint16 from2Byte(const uchar *str){
    if(nullptr == str)
        return 0;
    return (str[0]<<8 & 0xFF00) | (str[1] & 0xFF);
}

/* 具体化DVD盘的类型
 */
QString dvdMediaType(const QString& type){
    QString dvdType;

    if(type.isEmpty() || ('d' != type.at(8).toLatin1()))
        return type;

    //DVD-ROM盘不可再追加文件，从udisks2内部读出来该类盘的标识为"optical_dvd"
    if(11==type.length() && (type=="optical_dvd"))
        return QString("DVD-ROM");

    //optical_dvd_r optical_dvd_rw
    //optical_dvd_plus_r optical_dvd_plus_rw
    //qDebug()<<__func__<<__LINE__<<type;
    char ch = type.at(12).toLatin1();

    switch(ch){
    case 'p':{  //DVD+R DVD+RW
        if(type == "optical_dvd_plus_r")
            dvdType = "DVD+R";
        else
            dvdType = "DVD+RW";
    }
        break;
    case 'r':{  //DVD-R DVD-RW
        if(type == "optical_dvd_r")
            dvdType = "DVD-R";
        else
            dvdType = "DVD-RW";
    }
        break;
    default:
        dvdType = type;
        break;
    }
    return dvdType;
}

/* profile可分类CD与DVD, 并严格区分 R RW +-R +-RW */
int DiscRead::readDiscProfile(){
    bool transferRet;
    uchar profileBuffer[8];
    DiscScsi scsi(mDevice);

    ::memset(profileBuffer,0,8);
    scsi[0] = GPCMD_GET_CONFIGURATION;//0x46
    scsi[1] = 1;
    scsi[8] = 8;
    scsi[9] = 0;

    transferRet = scsi.transport(SCSI_READ, profileBuffer, 8);
    if(transferRet){
        short profile = from2Byte((const uchar*)&profileBuffer[6]);
        switch(profile){
        case 0x00: mProfile = MEDIA_NONE;          break;
        case 0x08: mProfile = MEDIA_CD_ROM;        break;
        case 0x09: mProfile = MEDIA_CD_R;          break;
        case 0x0A: mProfile = MEDIA_CD_RW;         break;
        case 0x10: mProfile = MEDIA_DVD_ROM;       break;
        case 0x11: mProfile = MEDIA_DVD_R_SEQ;     break;
        case 0x12: mProfile = MEDIA_DVD_RAM;       break;
        case 0x13: mProfile = MEDIA_DVD_RW_OVWR;   break;
        case 0x14: mProfile = MEDIA_DVD_RW_SEQ;    break;
        case 0x15: mProfile = MEDIA_DVD_R_DL_SEQ;  break;
        case 0x16: mProfile = MEDIA_DVD_R_DL_JUMP; break;
        case 0x1A: mProfile = MEDIA_DVD_PLUS_RW;   break;
        case 0x1B: mProfile = MEDIA_DVD_PLUS_R;    break;
        case 0x2B: mProfile = MEDIA_DVD_PLUS_R_DL; break;
        default: break;
        }
    }else{
        qDebug()<<"[<<"<<mDevice<<"] GET CONFIGURATION failed.";
    }
    return 0;
}

bool DiscRead::isBlank() const{
    return mIsBlank;
}

bool DiscRead::isReady() const{
    return mIsReady;
}

bool DiscRead::isGood() const{
    return mIsGood;
}

bool DiscRead::canAppend() const{
    return mCanAppend;
}

QString DiscRead::discLabel() const{
    return mLabel;
}

QString DiscRead::discMediaType() const{
    return mMediaType;
}

QString DiscRead::discFilesystemType() const{
    return mFilesystemType;
}
