#include "updatecontroller.h"


UpdateController::UpdateController(QObject *parent) :
    QObject(parent),
    commonClientHandler(nullptr)
{
    buildPath = QDir::currentPath() + "/" + applicationFolderName;
    sharedDataPath = QDir::currentPath() + "/" + sharedDataFolderName;
    Logger::instance().log(buildPath);
    qDebug() << hashFileName;
}

void UpdateController::initialize(CommonClientHandler *commonClientHandler,DataParser *dataParser,AssetsManager *assetManager)
{
    this->commonClientHandler = commonClientHandler;
    this->dataParser = dataParser;
    this->assetManager = assetManager;

    if (!QDir(staticDataFolderName).exists())
    {
        QDir().mkdir(staticDataFolderName);
    }

    sizeToSend = 0;
    assetManager->initialize(this,dataParser);

    if (!checkRequiredFolder())
    {
        emit sigErrorRequired(100);
        return;
    }

    calculateFullHash();
    currentStreamingPath = assetManager->setVersion("base"); //TODO: сохрнаять предыдущую версию и загружать ее при включении
    setUpCurrentServerHash();

    mutex = new QMutex;
    qDebug() << "UpdateController init thread ID " << QThread::currentThreadId();
}

void UpdateController::changeAssetVersion(QString versionName)
{
    commonClientHandler->slot_sendPacketToAllClients(PacketType::BUSY);
    qDebug() << "UpdateController thread ID " << QThread::currentThreadId();
    currentStreamingPath = assetManager->setVersion(versionName);
    setUpCurrentServerHash();
    commonClientHandler->slot_sendPacketToAllClients(PacketType::HASH_READY);
    commonClientHandler->sendCurrentVersionToAllClient();

    commonClientHandler->slot_sendPacketToAllClients(PacketType::FREE);
}

void UpdateController::createCopyVersion(QString versionName,QString newVersionName,QString author)
{
    commonClientHandler->slot_sendPacketToAllClients(PacketType::BUSY);
    assetManager->createCopyVersion(versionName,newVersionName,author);
    commonClientHandler->slot_sendPacketToAllClients(PacketType::FREE);
}

void UpdateController::deleteAssetVersion(QString versionName)
{
    commonClientHandler->slot_sendPacketToAllClients(PacketType::BUSY);
    assetManager->deleteVersion(versionName);
    commonClientHandler->slot_sendPacketToAllClients(PacketType::FREE);
}

void UpdateController::compareFiles(ClientHandler* handler, QByteArray array)
{
    mutex->lock();
    serverDataList.clear();
    serverDataList = *loadHash(hashFileName);
    clientDataList.clear();
    xmlFileDataParse(array);
    checkNeedUpdate(handler);
    mutex->unlock();
}


void UpdateController::showHash()
{
    for(FileData& str : serverDataList){
        Logger::instance().log(str.hash);
    }
}

void UpdateController::calculateFullHash()
{
    Logger::instance().log("Calculate hash...");
    commonClientHandler->slot_sendPacketToAllClients(PacketType::BUSY);
    auto *list = calculateHash(buildPath);
    saveHash(buildHashName,list);
    calculateSharedHash();
    Logger::instance().log("Calculate hash complete");
    commonClientHandler->slot_sendPacketToAllClients(PacketType::FREE);
}

void UpdateController::calculateFullHashWithSetup()
{
    commonClientHandler->slot_sendPacketToAllClients(PacketType::HASH_CALCULATE_START);
    calculateCurrentSharedHash();
    setUpCurrentServerHash();
    commonClientHandler->slot_sendPacketToAllClients(PacketType::HASH_READY);
}

void UpdateController::saveHash(QString fileName,QList<FileData> *fileList)
{
    QFile hashFile(fileName);
    hashFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
    QXmlStreamWriter xmlWriter(&hashFile);
    QListIterator<FileData> fileDataIterator(*fileList);

    xmlWriter.setAutoFormatting(true);
    xmlWriter.writeStartDocument();
    xmlWriter.writeStartElement("FileDataList");

    while (fileDataIterator.hasNext())
    {
        FileData data = fileDataIterator.next();
        xmlWriter.writeStartElement("FileData");

        xmlWriter.writeAttribute("Path",data.path);
        xmlWriter.writeAttribute("Hash",data.hash);

        xmlWriter.writeEndElement();
    }

    xmlWriter.writeEndElement();
    xmlWriter.writeEndDocument();

    hashFile.close();

}

QList<FileData>* UpdateController::loadHash(QString filename)
{
    QList<FileData> *files = new QList<FileData>();

    QFile hashFile(filename);
    QByteArray array;
    if(hashFile.open(QIODevice::ReadOnly)){
        array = hashFile.readAll();
        hashFile.close();
    }

    QXmlStreamReader xmlReader(array);

    while (!xmlReader.atEnd())
    {
        if(xmlReader.isStartElement())
        {
            if(xmlReader.name().toUtf8() == "FileData")
            {
                FileData data;

                foreach(const QXmlStreamAttribute &attr,xmlReader.attributes())
                {
                    QString name = attr.name().toString();
                    QString value = attr.value().toString();

                    if(name == "Path")
                        data.path = value;
                    else if(name == "Hash")
                        data.hash = value;
                }

                files->append(data);
            }

        }

        xmlReader.readNextStartElement();
    }

    Logger::instance().log("Hash load from file ");

    return files;

}

void UpdateController::calculateSize()
{
    QDirIterator iterator(buildPath);
    quint64 total = 0;

    while(iterator.hasNext()){
        if(iterator.fileInfo().isFile()){
            total += iterator.fileInfo().size();
        }

        iterator.next();
    }

    Logger::instance().log("Full size: ");
    Logger::instance().log(QString::number(total));
}

QString UpdateController::getCommands()
{
    QString commandsText;
    commandsText += "check - check version ";
    commandsText += "update - update files ";

    return commandsText;

}

void UpdateController::setUpCurrentServerHash()
{
    QList<FileData> *fileList = new QList<FileData>;
    fileList->append(*loadHash(buildHashName));
    FileData *streamingFolder = new FileData;
    streamingFolder->hash = "FOLDER";
    streamingFolder->path = buildDataPath + streamingAssetsFolderName;
    fileList->append(*streamingFolder);
    QString streamingHashFileName = Tools::createVersionHashFilepath(assetManager->getCurrentVersionData()->getViewName());
    fileList->append(*loadHash(streamingHashFileName));
    assetManager->prepareLocalPathList(fileList);

    saveHash(hashFileName,fileList);
}

void UpdateController::setDataInfo(DataInfo *value)
{
    dataInfo = value;
}

QString UpdateController::getCurrentStreamingPath() const
{
    return currentStreamingPath;
}

void UpdateController::setLocalFileData(QList<FileData> dataList)
{
    serverDataList.append(dataList);
}

bool UpdateController::checkNeedUpdate(ClientHandler *handler)
{
    QList<FileData> *forSend = new QList<FileData>;
    QList<FileData> *forDelete = new QList<FileData>;

    fileSendList.clear();
    fileDeleteList.clear();
    sizeToSend = 0;

    bool needUpdate = false;

    for (auto &item:clientDataList)                 //проверка на недостающие файлы по адресам
    {
        if(item.path.contains("Temp")) continue;

        if (!serverDataList.contains(item))
        {
            forDelete->append(item);
        }
    }

    for (auto &item:serverDataList)
    {
        if(item.path.contains("Temp")) continue;

        if (!clientDataList.contains(item))
        {
            forSend->append(item);
        }
    }

    if(forSend->length() > 0)                   //формирование сообщения об обновлении
    {
        QString log;
        log.append(" Client: " + handler->getClient()->getLogin());
        log.append(" Need updates: ");
        log.append(QString::number(forSend->length()));
        log.append(" objects");
        fileSendList = *forSend;

        Logger::instance().log(log);
        //printFileList(*forSend);

        //handler->sendMessageBlock(log);
        needUpdate = true;
    }
    else
    {
        QString log;
        log.append(" Client: " + handler->getClient()->getLogin());
        log.append(" no update required");
        Logger::instance().log(log);
        //handler->sendMessageBlock(log);
    }

    if(forDelete->length() > 0){
        QString log;
        log.append(" Client: " + handler->getClient()->getLogin());

        log.append(" Need delete: ");
        log.append(QString::number(forDelete->length()));
        log.append(" objects");
        fileDeleteList = *forDelete;

        Logger::instance().log(log);
        //printFileList(*forDelete);
        //handler->sendMessageBlock(log);
        needUpdate = true;
    }
    else
    {
        QString log;
        log.append(" Client: " + handler->getClient()->getLogin());
        log.append(" no delete required");

        Logger::instance().log(log);
        //handler->sendMessageBlock(log);
    }

    CalculateSizeToSend(*forSend);
    handler->sendNeedUpdate(needUpdate,sizeToSend,forSend->length(),forDelete->length());
    return needUpdate;
}

QList<FileData>* UpdateController::calculateHash(QString path)
{
    serverDataList.clear();

    if(!QDir(path).exists()){
        QDir().mkdir(path);
    }

    QString hashString;
    QStringList filter;
    filter << "*";
    QList<FileData> *files = new QList<FileData>;

    QDirIterator dirIterator(path,filter, QDir::AllEntries, QDirIterator::Subdirectories);

    while (dirIterator.hasNext())
    {
        QFileInfo fileInfo(dirIterator.next());
        FileData currentFile;
        if(fileInfo.isDir() && !fileInfo.fileName().startsWith(".") && fileInfo.fileName() != projectFolderName)
        {
            currentFile.path = Tools::createLocalPath(fileInfo.absoluteFilePath());
            currentFile.hash = "FOLDER";

            if(!files->contains(currentFile))
            {
                files->push_back(currentFile);
            }
        }
    }

    QDirIterator fileIterator(path,filter,QDir::Files | QDir::NoDotAndDotDot,QDirIterator::Subdirectories);

    while (fileIterator.hasNext())
        {
            fileIterator.next();
            QFileInfo fileInfo = fileIterator.fileInfo();
            FileData currentFile;
            QFile file(fileInfo.absoluteFilePath());

            quint64 fileSize = file.size();     //буффер для хэширования крупных файлов
            const quint64 bufferSize = 10240;

            if(fileInfo.isHidden()) continue;

            if(fileInfo.isFile() && file.open(QIODevice::ReadOnly) && !fileInfo.fileName().contains(".meta"))
            {
                char buffer[bufferSize];
                int bytesRead;
                int readSize = qMin(fileSize,bufferSize);

                QCryptographicHash hash(QCryptographicHash::Md5);

                while(readSize > 0 && (bytesRead = file.read(buffer,readSize)) > 0)
                {
                    fileSize -= bytesRead;
                    hash.addData(buffer,bytesRead);
                    readSize = qMin(fileSize,bufferSize);
                }

                hashString = QString(hash.result().toHex());
                currentFile.path = Tools::createLocalPath(fileInfo.absoluteFilePath());
                currentFile.hash = hashString;
                files->push_back(currentFile);
                file.close();
            }
    }
    serverDataList.append(*files);
    return files;
}

QString UpdateController::getPathAdditionalFile(QString name)
{
    QString path = Tools::createSharedPath("/" + assetManager->getCurrentVersionData()->getViewName() +  "/" + additionalFilesFolderName + name);
    return path;
}

QByteArray UpdateController::getLocalHash()
{
    QFile hashFile(hashFileName);
    QByteArray array;
    if(hashFile.open(QIODevice::ReadOnly)){
        array = hashFile.readAll();
        hashFile.close();
    }

    return array;
}

QString UpdateController::getCurrentVersionName()
{
    return assetManager->getCurrentVersionData()->getViewName();
}

void UpdateController::CalculateSizeToSend(QList<FileData> diffList)
{
    QListIterator<FileData> serverDiffIterator(diffList);

    while (serverDiffIterator.hasNext())
    {
        QString path;
        FileData pathForUpdate = serverDiffIterator.next();
        if(pathForUpdate.path.contains(streamingAssetsFolderName))
        {
            path = Tools::createStreamingToRealPath(pathForUpdate.path,assetManager->getCurrentVersionData());
        }
        else
        {
            path = Tools::createRootPath(pathForUpdate.path);
        }

        QFile file(path);

        sizeToSend += file.size();
    }
}

void UpdateController::calculateSharedHash()
{
    QDir sharedDir(sharedDataPath);

    if(!QDir(sharedDataPath).exists())
    {
         QDir().mkdir(sharedDataPath);
    }

    QDirIterator dirIterator(sharedDir);
    QList<FileData> *fileList = new QList<FileData>;
    QList<StreamingVersionData*> *versionList = new QList<StreamingVersionData*>;

    while (dirIterator.hasNext())
    {
        dirIterator.next();
        QFileInfo fileInfo = dirIterator.fileInfo();
        if (fileInfo.fileName() == "." || fileInfo.fileName() == "..")
            continue;

        QString fileName = Tools::createVersionHashFilepath(fileInfo.fileName());

        fileList = calculateHash(fileInfo.absoluteFilePath());
        saveHash(fileName,fileList);

        StreamingVersionData *version = new StreamingVersionData(
                    fileInfo.absoluteFilePath(),fileInfo.fileName(),
                    fileInfo.birthTime(),fileInfo.size());
        versionList->append(version);

    }

    assetManager->createFirstVersionListXML(*versionList);
}

void UpdateController::calculateCurrentSharedHash()
{
    QList<FileData> *fileList = new QList<FileData>;
    QString fileName = Tools::createVersionHashFilepath(assetManager->getCurrentVersionData()->getViewName());
    fileList = calculateHash(currentStreamingPath);
    saveHash(fileName,fileList);
}

void UpdateController::sendNewVersionList()
{
    commonClientHandler->sendNewVersionListToAllClient();
}

bool UpdateController::checkDuplicate(QString versionName)
{
    return assetManager->findDuplicate(versionName);
}

void UpdateController::xmlFileDataParse(QByteArray array)
{
    QXmlStreamReader xmlReader(array);
    xmlReader.readNext();

    //Крутимся в цикле до тех пор, пока не достигнем конца документа
    while(!xmlReader.atEnd())
    {
        //Проверяем, является ли элемент началом тега
        if(xmlReader.isStartElement())
        {
            if(xmlReader.name() == "FileData")
            {
                FileData data;

                foreach(const QXmlStreamAttribute &attr,xmlReader.attributes())
                {
                    QString name = attr.name().toString();
                    QString value = attr.value().toString();

                    if(name == "Path")
                        data.path = value;
                    else if(name == "Hash")
                        data.hash = value;
                }

                clientDataList.append(data);
            }
        }

        xmlReader.readNext();
    }
}

StreamingVersionData* UpdateController::getCurrentVersion()
{
    return assetManager->getCurrentVersionData();
}

void UpdateController::printFileList(QList<FileData> fileData)
{
   QListIterator<FileData> iterator(fileData);

   while (iterator.hasNext())
   {
       auto next = iterator.next();
       Logger::instance().log(next.path);
   }
}

QList<FileData> UpdateController::getFileDeleteList() const
{
    return fileDeleteList;
}

QList<FileData> UpdateController::getFileSendList()
{
    QList<FileData> *copy = new QList<FileData>(fileSendList);
    QList<FileData> sendList = *assetManager->prepareRealPathList(copy);
    return sendList;
}

QList<FileData> UpdateController::getClientDataList() const
{
    return clientDataList;
}

DataInfo *UpdateController::getCurrentDataInfo()
{
    return dataInfo;
}

void UpdateController::clearCurrentDataInfo()
{
    delete dataInfo;
}

bool UpdateController::checkRequiredFolder()
{
    bool required = true;
    QDir buildDir(buildPath + "/" + projectFolderName);
    if (!buildDir.exists()) required = false;

    QDir baseSharedDir(sharedDataPath + "/" + baseNameVersion);
    if (!baseSharedDir.exists()) required = false;

    return required;
}
UpdateController::~UpdateController()
{

}

