#include "QtFtp.h"

#include "LightLog.h"

#include <QEventLoop>

/* 构造函数 */
QtFtp::QtFtp(QObject *parent)
{
    /* 设置协议 */
    m_url.setScheme("ftp");
    m_manager.setParent(this);
    /* 设置超时定时器 */
    m_timer.setTimerType(Qt::PreciseTimer);
    m_timer.setSingleShot(true);
    connect(&m_timer,SIGNAL(timeout()),this,SLOT(do_timeout()));
}

QtFtp::~QtFtp()
{
    if(m_file.isOpen())
    {
        m_file.close();
    }
    if(m_reply != nullptr)
    {
        m_reply->deleteLater();
        m_reply = nullptr;
    }
}


/* 设置目标主机IP和端口 */
void QtFtp::setHostAndPort(const QString &host, int port)
{
    m_url.setHost(host);
    m_url.setPort(port);
}
/* 设置目标设备的用户名和密码 */
void QtFtp::setUserPasswd(const QString &user, const QString &pw)
{
    m_url.setUserName(user);
    m_url.setPassword(pw);
}
/**
 * @brief 上传文件
 * @param file 要上传的文件名
 * @param path 目标计算机存放文件的路径
 */
void QtFtp::putFile(const QString &fileName, const QString &farPath)
{
    m_isFinished = false;
    m_result = false;

    m_fileUpload.setFileName(fileName);
    if(!m_fileUpload.open(QFile::ReadOnly))
    {
        QLOG_WARN("open file failed , file path : " + fileName);
        return;
    }


    m_url.setPath(farPath);
    m_reply = m_manager.put(QNetworkRequest(m_url),&m_fileUpload);
    /* 将信号进行一次转发 */
    connect(m_reply,SIGNAL(uploadProgress(qint64,qint64)),this,SIGNAL(signal_uploadProgress(qint64,qint64)));
    /* 连接信号和槽 */
    connect(m_reply,SIGNAL(finished()),this,SLOT(do_uploadFinished()));
    connect(m_reply,SIGNAL(uploadProgress(qint64,qint64)),this,SLOT(do_uploadProgress(qint64,qint64)));

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
    connect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SIGNAL(signal_error(QNetworkReply::NetworkError)));
#endif
}


/**
 * @brief 重载函数,上传文件,直接上传内存数据
 * 
 * @param data 需要上传的数据
 * @param farPath 远程文件名
 */
void QtFtp::putFile(const QByteArray& data,const QString& farPath)
{
    m_isFinished = false;
    m_result = false;

    m_url.setPath(farPath);
    m_reply = m_manager.put(QNetworkRequest(m_url),data);
    /* 将信号进行一次转发 */
    connect(m_reply,SIGNAL(uploadProgress(qint64,qint64)),this,SIGNAL(signal_uploadProgress(qint64,qint64)));
    /* 连接信号和槽 */
    connect(m_reply,SIGNAL(finished()),this,SLOT(do_uploadFinished()));
    connect(m_reply,SIGNAL(uploadProgress(qint64,qint64)),this,SLOT(do_uploadProgress(qint64,qint64)));

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
    connect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SIGNAL(signal_error(QNetworkReply::NetworkError)));
#endif
}



/**
 * @brief QtFtp::getFile
 * @param fileName 本机文件路径和文件名
 * @param path 目标文件路径
 */
void QtFtp::getFile(const QString &fileName,const QString &srcPath)
{
    m_isFinished = false;
    m_result = false;

    m_file.setFileName(fileName);
    if(!m_file.open(QFile::WriteOnly | QFile::Truncate))
    {
        QLOG_WARN("open file failed , file path : " + fileName);
        return;
    }

    m_url.setPath(srcPath);
    m_reply = m_manager.get(QNetworkRequest(m_url));

    /* 转发信号 */
    connect(m_reply,SIGNAL(downloadProgress(qint64,qint64)),this,SIGNAL(signal_downloadProgress(qint64,qint64)));
    /* 连接信号和槽 */
    connect(m_reply,SIGNAL(finished()),this,SLOT(do_downloadFinished()));
    connect(m_reply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(do_downloadProgress(qint64,qint64)));

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
    connect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SIGNAL(signal_error(QNetworkReply::NetworkError)));
#endif
}

/* 下载文件,直接下载到内存,路径是相对路径,不包含前面的IP地址和端口 */
void QtFtp::getFile(QByteArray& data,const QString& srcPath)
{
    m_isFinished = false;
    m_result = false;

    m_downloadData = &data;
    m_url.setPath(srcPath);
    m_reply = m_manager.get(QNetworkRequest(m_url));

    /* 转发信号 */
    connect(m_reply,SIGNAL(downloadProgress(qint64,qint64)),this,SIGNAL(signal_downloadProgress(qint64,qint64)));
    /* 连接信号和槽 */
    connect(m_reply,SIGNAL(finished()),this,SLOT(do_downloadFinishedToData()));
    connect(m_reply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(do_downloadProgress(qint64,qint64)));

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
    connect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SIGNAL(signal_error(QNetworkReply::NetworkError)));
#endif
}

/**
 * @brief 等待完成,同时设置超时时间,超时后,断开reply的信号和槽的连接
 *        参数设置为0表示查询是否完成,设置为-1表示一直等待
 * 
 * @param msecs 超时时间
 *  @arg -1 一直等待,将会阻塞在这里
 *  @arg 0 查询是否完成
 *  @arg 正整数,等待完成的时间,设置完成之后再次设置,会覆盖之前的设置
 *  @arg < -1,阻塞等待传入的值的绝对值时间
 * @return true 上传或下载完成
 * @return false 上传或下载进行中
 */
bool QtFtp::waitFinished(int msecs)
{
    if(msecs == 0)
    {
        return m_isFinished;
    }
    else if(msecs == -1)
    {
        QEventLoop loop;
        connect(this, &QtFtp::signal_uploadState, &loop, &QEventLoop::quit);
        connect(this, &QtFtp::signal_downloadState, &loop, &QEventLoop::quit);
        loop.exec();
        return true;
    }
    else if (msecs > 0)
    {
        m_timer.start(msecs);
        return m_isFinished;
    }
    else if(msecs < -1)
    {
        QEventLoop loop;
        connect(this, &QtFtp::signal_uploadState, &loop, &QEventLoop::quit);
        connect(this, &QtFtp::signal_downloadState, &loop, &QEventLoop::quit);
        m_timer.start(-msecs);
        connect(&m_timer, &QTimer::timeout, &loop, &QEventLoop::quit);
        loop.exec();
        
        return false;
    }
    return m_isFinished;
}

/**
 * @brief 获取结果
 * 
 * @return int 
 *  @arg -1 上传或下载失败
 *  @arg 0 上传或下载成功
 */
bool QtFtp::getResult()
{
    return m_result;
}

/* 上传完成 */
void QtFtp::do_uploadFinished()
{
    if(m_timer.isActive())
    {
        m_timer.stop();
    }
    if(m_reply->error() == QNetworkReply::NoError)
    {
        m_result = true;
        emit signal_uploadState(true);
        QLOG_INFO("----- FTP upload success -----");
    } else
    {
        QLOG_WARN("FTP upload failed : " + m_reply->errorString());
        emit signal_uploadState(false);
    }
    m_fileUpload.close();
    m_reply->deleteLater();
    m_reply = nullptr;
    m_isFinished = true;
}


/* 创建目录 */
bool QtFtp::createDir(const QString& path)
{
    m_isFinished = false;
    m_result = false;

    QNetworkRequest request;
    QUrl url;
    url.setHost(m_url.host());
    url.setPort(m_url.port());
    url.setUserName(m_url.userName());
    url.setPassword(m_url.password());
    url.setPath(path);
    QLOG_DEBUG("create dir : " + url.path());
    
    request.setUrl(url);
    request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
    
    QByteArray data;
    data.append("command=MKD");

    m_reply = m_manager.post(request,data);

    /* 连接信号和槽 */
    connect(m_reply,SIGNAL(finished()),this,SLOT(do_finished()));

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
    connect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SIGNAL(signal_error(QNetworkReply::NetworkError)));
#endif

    return true;
}


/**
 * @brief 完成,保存文件
 *        这里采用逐步读取文件内容,因为QByteArray最多存储大约2GB的数据
 * 
 */
void QtFtp::do_downloadFinished()
{
    if(m_timer.isActive())
    {
        m_timer.stop();
    }
    if(!m_file.isOpen())
    {
        m_file.open(QFile::WriteOnly | QFile::Truncate);
    }
    if(m_reply->error() == QNetworkReply::NoError)
    {
        while(!m_reply->atEnd())
        {
            /* 每次读取10MB */
            QByteArray data = m_reply->read(10485760);
            quint64 writenBytes = m_file.write(data);
            QLOG_DEBUG("write file : " + QString::number(writenBytes));
            if(writenBytes != data.size())
            {
                QLOG_WARN("FTP download failed: write file failed");
                emit signal_downloadState(false);
                m_result = false;
                break;
            }
        }
        m_file.flush();
        emit signal_downloadState(true);
        m_result = true;
        QLOG_DEBUG("----- FTP download success -----");
    }else
    {
        QLOG_WARN("FTP download failed:" + m_reply->errorString());
        /* 下载失败就删除创建的文件 */
        m_file.remove();
        emit signal_downloadState(false);
    }
    m_file.close();

    m_reply->deleteLater();
    m_reply = nullptr;
    m_isFinished = true;
}

/* 下载完成,保存数据 */
void QtFtp::do_downloadFinishedToData()
{
    if(m_timer.isActive())
    {
        m_timer.stop();
    }
    if(m_reply->error() == QNetworkReply::NoError)
    {
        *m_downloadData = m_reply->readAll();
        
        emit signal_downloadState(true);
        m_result = true;
        QLOG_DEBUG("----- FTP download success -----");
    }else
    {
        QLOG_WARN("FTP download failed:" + m_reply->errorString());
        
        emit signal_downloadState(false);
    }
    m_downloadData = nullptr;

    m_reply->deleteLater();
    m_reply = nullptr;
    m_isFinished = true;
}

/* 上传进度 */
void QtFtp::do_uploadProgress(qint64 bytesSent, qint64 bytesTotal)
{
    QLOG_DEBUG("sent / total : " + QString::number(bytesSent) + " / " + QString::number(bytesTotal));
}

/* 下载进度 */
void QtFtp::do_downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
    QLOG_DEBUG("received / total : " + QString::number(bytesReceived) + " / " + QString::number(bytesTotal));
}

/* ftp其他任务完成槽函数 */
void QtFtp::do_finished()
{
    if(m_timer.isActive())
    {
        m_timer.stop();
    }
    m_isFinished = true;
    if(m_reply->error() == QNetworkReply::NoError)
    {
        QLOG_INFO("----- FTP operation success -----");
        
        m_result = true;
    }else
    {
        QLOG_WARN("FTP operation failed : " + m_reply->errorString());

        m_result = false;
    }

    m_reply->deleteLater();
    m_reply = nullptr;
    m_isFinished = true;
}

/* 定时器超时函数 */
void QtFtp::do_timeout()
{
    if(m_reply != nullptr)
    {
        m_reply->abort();
        m_reply->deleteLater();
        m_reply = nullptr;
        m_result = false;
    }
    emit signal_uploadState(false);
    emit signal_downloadState(false);
}

/* ftp发送错误槽函数 */
#if defined (QT_VERSION) && QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
void QtFtp::do_ftpReplyError(QNetworkReply::NetworkError error)
{
   if(error == QNetworkReply::NoError)
   {
       QLOG_INFO("FTP发送成功");
   }
   else
   {
       QLOG_WARN("FTP发送失败:" + QString::number(error));
   }
}
#endif