#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include "gt.h"
#include "gterror.h"
#include "gttoolkitext.h"
#include "gtmem.h"
#include "gtfiletransfer.h"
#include "gtencrypt.h"

GtFileTransfer::GtFileTransfer() {
    _ftNetwork = new GtFTNetwork();
}

GtFileTransfer::~GtFileTransfer() {
    if (_ftNetwork != NULL) {
        delete _ftNetwork;
        _ftNetwork = NULL;
    }
}

/************************************************************
*功能：验证下载文件的MD5是否一致
*输入：fileName=下载文件名
*输入：md5Sum=文件MD5校验码
*输出：无
*返回：!=0不一致
*/
int GtFileTransfer::compareMD5TmpFile(char *fileName, unsigned char md5Sum[16]) {
    int result = 0;
    char path[256];
    unsigned char tmpBuf[16];

    memset(path, 0, sizeof(path));
    sprintf(path, "%s.md5", fileName);
    memset(tmpBuf, 0, sizeof(tmpBuf));
    result = GtToolkitExt::readFile(path, tmpBuf, 16);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件读取错误", result);
        return result;
    }
    if (memcmp(tmpBuf, md5Sum, 16) != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]MD5校验错误", GtError::ERROR_INVALID_FILE_MD5);
        return GtError::ERROR_INVALID_FILE_MD5;
    }
    return 0;
}

/************************************************************
*功能：保存下载文件的MD5码到本地文件
*输入：fileName=下载文件名
*输入：md5Sum=文件MD5校验码
*输出：无
*返回：!=0错误
*/
int GtFileTransfer::writeMD5TmpFile(char *fileName, unsigned char md5[16]) {
    FILE *fileH = NULL;
    int result = 0;
    char path[256];

    memset(path, 0, sizeof(path));
    sprintf(path, "%s.md5", fileName);
    fileH = fopen(path, "wb+");
    if (fileH == NULL) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%s]写入MD5文件错误<%d>", path,
                               GtError::ERROR_FILE_WRITE);
        return GtError::ERROR_FILE_WRITE;
    }
    result = fwrite(md5, 16, 1, fileH);
    if (result != 1) {
        fclose(fileH);
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]写入MD5文件错误", GtError::ERROR_FILE_WRITE);
        return GtError::ERROR_FILE_WRITE;
    }
    fflush(fileH);
    fclose(fileH);
    return 0;
}

/************************************************************
*功能：删除下载文件的MD5临时文件
*输入：fileName=下载文件名
*输出：无
*返回：!=0错误
*/
int GtFileTransfer::delMD5TmpFile(char *fileName) {
    char path[256];

    memset(path, 0, sizeof(path));
    sprintf(path, "%s.md5", fileName);
    unlink(path);
    return 0;
}

/************************************************************
*功能：获取断点下载的起始块
*输入：file=文件名
*输入：blockSize=块大小
*输出：block=块序号
*返回：!=0错误
*/
int GtFileTransfer::getBreakPointBlock(char *file, int blockSize, int *block) {
    int result = 0;
    int count = 0;

    result = GtToolkitExt::getFileSize(file);
    if (result < 0) {
        return result;
    }
    count = result / blockSize;
    *block = count;
    return 0;
}

bool GtFileTransfer::isRepeatFile(char *file, int size) {
    int result = 0;

    result = GtToolkitExt::getFileSize(file);
    if (result < 0) {
        return false;
    }
    if (result == size) {
        return true;
    }
    return false;
}

/************************************************************
*功能：文件下载网络接口
*输入：tradingCode=交易码
*输入：inBuf=请求数据
*输入：inLen=请求数据长度
*输出：outBuf=应答数据
*输出：outBufLen=应答数据长度
*返回：!=0错误
*/
int GtFileTransfer::callNetwork(unsigned long tradingCode, char *inBuf, int inLen, char *outBuf,
                                int *outBufLen) {
    if (_ftNetwork == NULL) {
        return GtError::ERROR_INVALID_VALUE;
    }
    return _ftNetwork->callFTNetwork(tradingCode, inBuf, inLen, outBuf, outBufLen);
}

/************************************************************
*功能：获取下载文件信息
*输入：tradingCode=交易码
*输入：fileAttr=文件属性
*输出：fileAttr=文件属性
*返回：!=0错误
*/
int GtFileTransfer::getFileInfo(unsigned long tradingCode, stFileAttr *fileAttr) {
    int result = 0;
    int length = 0;
    char inData[BLOCK_MTU];
    char outData[BLOCK_MTU];
    int outLen = sizeof(outData);

    memset(inData, 0, sizeof(inData));
    memset(outData, 0, sizeof(outData));
    length = sprintf(inData, "%s%c", fileAttr->strFileName, Gt::_delimiter1);
    result = callNetwork(tradingCode, inData, length, outData, &outLen);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "文件下载错误<%d>", result);
        return result;
    }
    memset(inData, 0, sizeof(inData));
    //文件名称
    result = GtToolkitExt::parseNChar(outData, inData, sizeof(inData), Gt::_delimiter1, 1);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件名称字段解析错误", result);
        return result;
    }
    //文件大小
    result = GtToolkitExt::parseNChar(outData, inData, sizeof(inData), Gt::_delimiter1, 2);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件大小字段解析错误", result);
        return result;
    }
    fileAttr->ulFileSize = atol(inData);
    if (fileAttr->ulFileSize == 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "文件大小字段解析错误");
        return GtError::ERROR_INVALID_VALUE;
    }
    //文件MD5码
    result = GtToolkitExt::parseNChar(outData, inData, sizeof(inData), Gt::_delimiter1, 3);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件校验码字段解析错误", result);
        return result;
    }
    GtToolkitExt::strToHex((unsigned char *) inData, strlen(inData), fileAttr->strFileMD5);
    //文件传输块大小
    result = GtToolkitExt::parseNChar(outData, inData, sizeof(inData), Gt::_delimiter1, 4);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件传输块大小字段解析错误", result);
        return result;
    }
    fileAttr->unFileBlockSize = atoi(inData);
    if (fileAttr->unFileBlockSize == 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "文件传输块大小字段解析错误");
        return GtError::ERROR_INVALID_VALUE;
    }
    return 0;
}

/************************************************************
*功能：获取下载文件块信息
*输入：tradingCode=交易码
*输入：fileAttr=文件属性
*输出：buf=文件数据
*输出：bufLen=数据长度
*返回：!=0错误
*/
int GtFileTransfer::getFileBlock(unsigned long tradingCode, stFileAttr *fileAttr, char *buf,
                                 int bufLen) {
    int result = 0;
    int length = 0;
    unsigned long tmpSN = 0;
    char inData[BLOCK_MTU];
    char outData[BLOCK_MTU];
    int outLen = sizeof(outData);

    memset(inData, 0, sizeof(inData));
    memset(outData, 0, sizeof(outData));
    length = sprintf(inData, "%s%c%d%c%lu%c", fileAttr->strFileName, Gt::_delimiter1,
                     fileAttr->unFileBlockSize, Gt::_delimiter1,
                     fileAttr->ulFileBlockSN, Gt::_delimiter1);
    result = callNetwork(tradingCode, inData, length, outData, &outLen);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件下载网络通信错误", result);
        return result;
    }
    memset(inData, 0, sizeof(inData));
    //数据块序号
    result = GtToolkitExt::parseNChar(outData, inData, sizeof(inData), Gt::_delimiter1, 1);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]数据块序号字段解析错误", result);
        return result;
    }
    tmpSN = atol(inData);
    if (fileAttr->ulFileBlockSN != tmpSN) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]数据块序号校验错误", GtError::ERROR_INVALID_VALUE);
        return GtError::ERROR_INVALID_VALUE;
    }
    //数据块大小
    result = GtToolkitExt::parseNChar(outData, inData, sizeof(inData), Gt::_delimiter1, 2);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]数据块大小字段解析错误", result);
        return result;
    }
    fileAttr->unFileBlockSize = atoi(inData);
    //数据块CRC校验
    result = GtToolkitExt::parseNChar(outData, inData, sizeof(inData), Gt::_delimiter1, 3);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]数据块校验字段解析错误", result);
        return result;
    }
    fileAttr->lFileBlockCRC = atol(inData);
    //数据块内容
    GtToolkitExt::logError(__FILE__, __LINE__, "strlen = %d sizeof = %d", strlen(outData),
                           sizeof(outData));
    memcpy(buf, &outData[strlen(outData) - fileAttr->unFileBlockSize], fileAttr->unFileBlockSize);
    if (strlen(buf) == 0) {
        memcpy(buf, &outData[sizeof(outData) - fileAttr->unFileBlockSize],
               fileAttr->unFileBlockSize);
    }
    return 0;
}

/************************************************************
*功能：下载文件
*输入：env=JVM环境
*输入：serverFile=下载文件服务器路径
*输入：localFile=本地文件存储路径
*输入：isBP=0-不支持断点续传，1-支持断点续传
*输出：无
*返回：!=0错误
*/
int GtFileTransfer::downloadFile(JNIEnv *env, char *serverFile, char *localFile, int isBP) {
    int i = 0;
    int fileNo = 0;
    int result = 0;
    int savedCount = 0;
    int count = 0;
    int leftCount = 0;
    float progress = 0;
    stFileAttr fileDown;
    FILE *fileH = NULL;
    char fileData[BLOCK_MTU];
    unsigned char md5Sum[16];
    char path[256];
    char localTmpFile[256];
    char localRealFile[256];

    //获取下载文件的属性
    memset(&fileDown, 0, sizeof(stFileAttr));
    strcpy(fileDown.strFileName, serverFile);
    result = getFileInfo(999101, &fileDown);
    if (result != 0) {
        return result;
    }
    //设置本地存储路径及文件名
    memset(path, 0, sizeof(path));
    Gt::instance()->getResourcePath(path);
    memset(localTmpFile, 0, sizeof(localTmpFile));
    sprintf(localTmpFile, "%s.tmp", localFile);
    memset(localRealFile, 0, sizeof(localRealFile));
    sprintf(localRealFile, "%s", localFile);

#if 0
    if(strstr(localFile, ".apk") != NULL)
    {
        //Gt::instance()->getApkUpgradeFile(localRealFile, sizeof(localRealFile));
    }
    else
    {
        sprintf(localRealFile, "%s", localFile);
        //检测是否重复下载的文件
        if(isRepeatFile(localRealFile, fileDown.ulFileSize))
        {
            GtToolkitExt::logError(__FILE__, __LINE__, "重复下载的文件，已跳过");
            return 0;
        }
    }
#endif

    //断点续传检测
    if (isBP == 1) {
        getBreakPointBlock(localTmpFile, fileDown.unFileBlockSize, &savedCount);
        if (savedCount > 0) {
            result = compareMD5TmpFile(localTmpFile, fileDown.strFileMD5);
            if (result != 0) {
                savedCount = 0;
            }
        }
    }
    if (savedCount > 0) {
        fileH = fopen(localTmpFile, "rb+");
    } else {
        result = writeMD5TmpFile(localTmpFile, fileDown.strFileMD5);
        if (result != 0) {
            return result;
        }
        fileH = fopen(localTmpFile, "wb+");
    }
    if (fileH == NULL) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件打开错误", GtError::ERROR_FILE_OPEN);
        return GtError::ERROR_FILE_OPEN;
    }
    fileNo = fileno(fileH);
    if (fileNo >= 0) {
        fchmod(fileNo, S_IRWXU | S_IRGRP | S_IWGRP | S_IRWXO);
    }
    //计算下载数据总块数
    count = fileDown.ulFileSize / fileDown.unFileBlockSize;
    leftCount = fileDown.ulFileSize % fileDown.unFileBlockSize;
    if (count < savedCount) {
        fclose(fileH);
        return GtError::ERROR_FILE_OPEN;
    }
    if (leftCount > 0) {
        count++;
    }
    result = fseek(fileH, savedCount * fileDown.unFileBlockSize, SEEK_SET);
    if (result == -1) {
        fclose(fileH);
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件打开错误", GtError::ERROR_FILE_OPEN);
        return GtError::ERROR_FILE_OPEN;
    }
    GtToolkitExt::logInfo(__FILE__, __LINE__, "Downloading %s->%s", fileDown.strFileName,
                          localRealFile);
    for (i = savedCount; i < count; i++) {
        memset(fileData, 0, sizeof(fileData));
        fileDown.ulFileBlockSN = i;
        result = getFileBlock(999102, &fileDown, fileData, sizeof(fileData));
        GtToolkitExt::logInfo(__FILE__, __LINE__, "fileData=%s", fileData);
        if (result != 0) {
            return result;
        }
        result = fwrite(fileData, fileDown.unFileBlockSize, 1, fileH);
        if (result != 1) {
            fclose(fileH);
            GtToolkitExt::logError(__FILE__, __LINE__, "[%d]写文件错误", GtError::ERROR_FILE_OPEN);
            return GtError::ERROR_FILE_OPEN;
        }
        progress = (float) (i + 1) / (float) count;
        //callbackDownloadProgress(env, serverFile, progress * 100);
    }
    fflush(fileH);
    fclose(fileH);
    sync();
    GtToolkitExt::logInfo(__FILE__, __LINE__, "Download complete %s->%s", fileDown.strFileName,
                          localRealFile);
    //校验文件MD5
    memset(md5Sum, 0, sizeof(md5Sum));
    result = GtEncrypt::fileMd5(localTmpFile, md5Sum);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "文件MD5校验错误");
        return GtError::ERROR_FILE_WRITE;
    } else {
        if (memcmp(md5Sum, fileDown.strFileMD5, 16) != 0) {
            GtToolkitExt::logError(__FILE__, __LINE__, "文件MD5校验错误");
            return GtError::ERROR_FILE_WRITE;
        } else {
            delMD5TmpFile(localTmpFile);
            if (rename(localTmpFile, localRealFile) != 0) {
                return GtError::ERROR_FILE_WRITE;
            }
        }
    }
    return 0;
}

/************************************************************
*功能：文件上传请求
*输入：tradingCode=交易码
*输入：fileAttr=文件属性
*输出：无
*返回：!=0错误
*/
int GtFileTransfer::uploadFileRequest(unsigned long tradingCode, stFileAttr *fileAttr) {
    int result = 0;
    int length = 0;
    char tmpBuf[256];
    char inData[BLOCK_MTU];
    char outData[BLOCK_MTU];
    int outLen = sizeof(outData);

    memset(inData, 0, sizeof(inData));
    memset(outData, 0, sizeof(outData));
    memset(tmpBuf, 0, sizeof(tmpBuf));
    GtToolkitExt::hexToStr(fileAttr->strFileMD5, sizeof(fileAttr->strFileMD5), tmpBuf,
                           sizeof(tmpBuf));
    length = sprintf(inData, "%s%c%lu%c%s%c", fileAttr->strFileName, Gt::_delimiter1,
                     fileAttr->ulFileSize, Gt::_delimiter1,
                     tmpBuf, Gt::_delimiter1);
    result = callNetwork(tradingCode, inData, length, outData, &outLen);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件上传网络通信错误", result);
        return result;
    }
    //文件传输块大小
    memset(tmpBuf, 0, sizeof(tmpBuf));
    result = GtToolkitExt::parseNChar(outData, tmpBuf, sizeof(tmpBuf), Gt::_delimiter1, 1);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件传输块大小字段解析错误", result);
        return result;
    }
    fileAttr->unFileBlockSize = atoi(tmpBuf);
    return 0;
}

/************************************************************
*功能：上传文件数据
*输入：tradingCode=交易码
*输入：fileAttr=文件属性
*输入：buf=文件数据
*输入：dataLen=数据长度
*输出：无
*返回：!=0错误
*/
int GtFileTransfer::uploadFileData(unsigned long tradingCode, stFileAttr *fileAttr, char *buf,
                                   int dataLen) {
    int result = 0;
    int length = 0;
    char inData[BLOCK_MTU];
    char outData[BLOCK_MTU];
    int outLen = sizeof(outData);

    memset(inData, 0, sizeof(inData));
    memset(outData, 0, sizeof(outData));
    length = sprintf(inData, "%s%c%lu%c%u%c%d%c%ld%c", fileAttr->strFileName, Gt::_delimiter1,
                     fileAttr->ulFileBlockSN, Gt::_delimiter1,
                     fileAttr->unFileBlockSize, Gt::_delimiter1,
                     dataLen, Gt::_delimiter1,
                     fileAttr->lFileBlockCRC, Gt::_delimiter1);
    memcpy(&inData[length], buf, dataLen);
    length += dataLen;
    result = callNetwork(tradingCode, inData, length, outData, &outLen);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件上传错误", result);
        return result;
    }
    return 0;
}

/************************************************************
*功能：文件上传完成请求
*输入：tradingCode=交易码
*输入：fileAttr=文件属性
*输出：无
*返回：!=0错误
*/
int GtFileTransfer::uploadFileCompleted(unsigned long tradingCode, stFileAttr *fileAttr) {
    int result = 0;
    int length = 0;
    char tmpBuf[256];
    char inData[BLOCK_MTU];
    char outData[BLOCK_MTU];
    int outLen = sizeof(outData);

    memset(inData, 0, sizeof(inData));
    memset(outData, 0, sizeof(outData));
    memset(tmpBuf, 0, sizeof(tmpBuf));
    GtToolkitExt::hexToStr(fileAttr->strFileMD5, sizeof(fileAttr->strFileMD5), tmpBuf,
                           sizeof(tmpBuf));
    length = sprintf(inData, "%s%c%lu%c%s%c%u%c", fileAttr->strFileName, Gt::_delimiter1,
                     fileAttr->ulFileSize, Gt::_delimiter1,
                     tmpBuf, Gt::_delimiter1,
                     fileAttr->unFileBlockSize, Gt::_delimiter1);
    result = callNetwork(tradingCode, inData, length, outData, &outLen);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "[%d]文件上传网络通信错误", result);
        return result;
    }
    return 0;
}

/************************************************************
*功能：上传文件
*输入：env=JVM环境
*输入：serverFile=上传文件服务器存储路径
*输入：localFile=本地文件路径
*输出：无
*返回：!=0错误
*/
int GtFileTransfer::uploadFile(JNIEnv *env, char *serverFile, char *localFile) {
    int i = 0;
    int result = 0;
    int count = 0;
    int leftCount = 0;
    int dataLen = 0;
    float progress = 0;
    stFileAttr fileUp;
    FILE *fileH = NULL;
    char fileData[BLOCK_MTU];

    memset(&fileUp, 0, sizeof(stFileAttr));
    strcpy(fileUp.strFileName, serverFile);
    result = GtToolkitExt::getFileSize(localFile);
    if (result <= 0) {
        return GtError::ERROR_FILE_OPEN;
    }
    fileUp.ulFileSize = result;
    result = GtEncrypt::fileMd5(localFile, fileUp.strFileMD5);
    if (result != 0) {
        GtToolkitExt::logError(__FILE__, __LINE__, "文件MD5校验错误");
        return GtError::ERROR_FILE_OPEN;
    }
    result = uploadFileRequest(999103, &fileUp);
    if (result != 0) {
        return result;
    }
    fileH = fopen(localFile, "rb");
    if (fileH == NULL) {
        return GtError::ERROR_FILE_OPEN;
    }
    count = fileUp.ulFileSize / fileUp.unFileBlockSize;
    leftCount = fileUp.ulFileSize % fileUp.unFileBlockSize;
    if (leftCount > 0) {
        count++;
    }
    GtToolkitExt::logInfo(__FILE__, __LINE__, "Uploading %s->%s", localFile, fileUp.strFileName);
    for (i = 0; i < count; i++) {
        if (leftCount > 0 && (i == count - 1)) {
            dataLen = leftCount;
        } else {
            dataLen = fileUp.unFileBlockSize;
        }
        memset(fileData, 0, sizeof(fileData));
        result = fread(fileData, dataLen, 1, fileH);
        if (result != 1) {
            fclose(fileH);
            return GtError::ERROR_FILE_READ;
        }
        fileUp.lFileBlockCRC = GtEncrypt::crc32((unsigned char *) fileData, dataLen);
        fileUp.ulFileBlockSN = i;
        result = uploadFileData(999104, &fileUp, fileData, dataLen);
        if (result != 0) {
            fclose(fileH);
            return result;
        }
        progress = (float) (i + 1) / (float) count;
        //callbackDownloadProgress(env, serverFile, progress * 100);
    }
    fclose(fileH);
    GtToolkitExt::logInfo(__FILE__, __LINE__, "Upload complete %s->%s", localFile,
                          fileUp.strFileName);
    result = uploadFileCompleted(999105, &fileUp);
    if (result != 0) {
        return result;
    }
    return 0;
}

/************************************************************
*功能：连接文件服务器
*输入：serverIP=服务器IP
*输入：port=服务器端口
*输出：无
*返回：!=0错误
*/
int GtFileTransfer::fileServerLink(char *serverIP, unsigned short port) {
    if (serverIP == NULL) {
        return GtError::ERROR_INVALID_FUNC_PARAM;
    }
    if (_ftNetwork == NULL) {
        return GtError::ERROR_INVALID_VALUE;
    }
    return _ftNetwork->connectFileServer(serverIP, port);
}

/************************************************************
*功能：关闭文件服务器
*输入：无
*输出：无
*返回：!=0错误
*/
void GtFileTransfer::closeFileServerLink() {
    if (_ftNetwork != NULL) {
        _ftNetwork->closeFileServer();
    }
}