#include "datafile.h"
#include "datadirectory.h"
#include "src/frame/logger.h"
#include "src/po/translation.h"

#include <QFileInfo>
#include <QDateTime>
#include <QMimeType>
#include <QMimeDatabase>
#include <QDebug>

DataFile::DataFile(QString path, QObject *parent, bool fromDisk)
    : QObject(parent)
{
    DataDirectory *dir;
    DataFile *tmp;
    QFileInfo f;
    QMimeType type;

    connect(this, SIGNAL(info(QString)), LOGGER, SLOT(info(QString)));
    connect(this, SIGNAL(warn(QString)), LOGGER, SLOT(warn(QString)));
    connect(this, SIGNAL(error(QString)), LOGGER, SLOT(error(QString)));
    connect(this, SIGNAL(debug(QString)), LOGGER, SLOT(debug(QString)));

    m_oLinkTarget = nullptr;
    m_bIsDisk = fromDisk;
    m_u32Level = 0;

    dir = qobject_cast<DataDirectory *>(parent);
    f.setFile(path);
    m_bIsDirectory = f.isDir();
    m_oRealName = f.fileName();
    m_oDisplayName = m_oRealName;

    if (m_oDisplayName.length() > NAME_LENGTH)
    {
        if (f.suffix().length())
        {
            m_oDisplayName = f.fileName().mid(0, NAME_LENGTH - f.suffix().length() - 1);
            m_oDisplayName.append(".").append(f.suffix());
        }
        else
        {
            m_oDisplayName = f.fileName().mid(0, NAME_LENGTH);
        }
    }
    if (!m_bIsDirectory)
        LOGGER->registerName(this, _("FILE %1").arg(m_oDisplayName));
    else
        LOGGER->registerName(this, _("DIR %1").arg(m_oDisplayName));
    m_u64Size = f.size();
    debug(_("Size : %1").arg(m_u64Size));
    m_oCreateTime = f.birthTime().toString("yyyy-MMMM-dd dddd HH:mm:ss");
    debug(_("Create time : %1").arg(m_oCreateTime));
    m_oAddTime = QDateTime::currentDateTime().toString("yyyy-MMMM-dd dddd HH:mm:ss");
    debug(_("Add time : %1").arg(m_oAddTime));
    type = QMimeDatabase().mimeTypeForFile(f.absoluteFilePath());
    m_oIcon = QIcon::fromTheme(type.iconName(), QIcon::fromTheme("unknown"));
    m_oType = type.comment();
    debug(_("Type : %1").arg(m_oType));
    m_oSource = f.absoluteFilePath();
    debug(_("Source path : %1").arg(m_oSource));
    m_oDestination = m_oSource;
    m_bAttrHidden = f.isHidden();
    debug(_("is %1HIDDEN.").arg(m_bAttrHidden ? "" : "not "));
    m_bAttrLink = f.isSymLink();
    debug(_("is %1SymbolLink.").arg(m_bAttrLink ? "" : "not "));
    if (m_bAttrLink)
    {
        if (m_bIsDirectory)
        {
            m_oLinkTarget = new DataDirectory(f.symLinkTarget(), nullptr, fromDisk);
            while (true)
            {
                if (nullptr ==  m_oLinkTarget->linkTarget()) break;
                tmp = new DataDirectory(m_oLinkTarget->linkTarget()->source(), nullptr, fromDisk);
                delete m_oLinkTarget;
                m_oLinkTarget = tmp;
            }
        }
        else
        {
            m_oLinkTarget = new DataFile(f.symLinkTarget(), nullptr, fromDisk);
            while (true)
            {
                if (nullptr ==  m_oLinkTarget->linkTarget()) break;
                tmp = new DataFile(m_oLinkTarget->linkTarget()->source(), nullptr, fromDisk);
                delete m_oLinkTarget;
                m_oLinkTarget = tmp;
            }
        }
        m_oLinkTarget->setParent(parent);
        connect(m_oLinkTarget, SIGNAL(child(DataFile*,bool)), dir, SLOT(doChild(DataFile*,bool)));
        debug(_("Symbol link target : %1").arg(m_oLinkTarget->source()));
    }

    if (nullptr == dir) return;
    m_oDestination = dir->destination() + "/" + m_oDisplayName;

    if (m_oDisplayName == "..")
    {
        m_u64Size = 0;
        m_bIsDirectory = false;
    }

    connect(this, SIGNAL(size(ulong,bool)), dir, SLOT(doSize(ulong,bool)));
    connect(this, SIGNAL(directory(bool, DataFile *)), dir, SLOT(doDirectory(bool, DataFile *)));
    connect(this, SIGNAL(file(bool, DataFile *)), dir, SLOT(doFile(bool, DataFile *)));
    connect(this, SIGNAL(child(DataFile*,bool)), dir, SLOT(doChild(DataFile*,bool)));
    connect(this, SIGNAL(copied(QString,QString)), dir, SIGNAL(copied(QString,QString)));
    connect(dir, SIGNAL(update(QString)), this, SLOT(onUpdate(QString)));
    connect(dir, SIGNAL(hidden(bool)), this, SLOT(slotsDoHidde(bool)));
    connect(dir, SIGNAL(broken(bool)), this, SLOT(slotsDoBroken(bool)));
    connect(dir, SIGNAL(repleace(bool)), this, SLOT(slotsDoRepleace(bool)));
    if (m_oLinkTarget)
    {
        connect(dir, SIGNAL(update(QString)), m_oLinkTarget, SLOT(onUpdate(QString)));

        connect(dir, SIGNAL(hidden(bool)), m_oLinkTarget, SLOT(slotsDoHidde(bool)));
        connect(dir, SIGNAL(broken(bool)), m_oLinkTarget, SLOT(slotsDoBroken(bool)));
        connect(dir, SIGNAL(repleace(bool)), m_oLinkTarget, SLOT(slotsDoRepleace(bool)));
    }
    if (dir->linkTarget())
    {
        connect(dir->linkTarget(), SIGNAL(update(QString)), this, SLOT(onUpdate(QString)));
        connect(dir->linkTarget(), SIGNAL(hidden(bool)), this, SLOT(slotsDoHidde(bool)));
        connect(dir->linkTarget(), SIGNAL(broken(bool)), this, SLOT(slotsDoBroken(bool)));
        connect(dir->linkTarget(), SIGNAL(repleace(bool)), this, SLOT(slotsDoRepleace(bool)));
        connect(this, SIGNAL(size(ulong,bool)), dir->linkTarget(), SLOT(doSize(ulong,bool)));
        connect(this, SIGNAL(directory(bool, DataFile *)), dir->linkTarget(), SLOT(doDirectory(bool, DataFile *)));
        connect(this, SIGNAL(file(bool, DataFile *)), dir->linkTarget(), SLOT(doFile(bool, DataFile *)));
    }

    m_u32Level = dir->level();
    if (m_bIsDirectory) ++m_u32Level;

    size(m_u64Size, true);
    if (".." != m_oDisplayName)
    {
        if (m_bIsDirectory)
            directory(true, this);
        else
            file(true, this);
    }
    m_bIsValid = true;
    if (!m_bIsDirectory)
        child(this, true);
}

QString DataFile::stringSize()
{
    int unit;
    qreal rsize;
    QString ret;

    unit = -1;
    rsize = m_u64Size * 1.0;

    while (rsize > 1024.00)
    {
        ++unit; rsize /= 1024.00;
    }

    ret = QString("%1 ").arg(rsize, 0, 'f', 1);

    switch (unit)
    {
    case 0:
        ret.append(_("KB"));
        break;
    case 1:
        ret.append(_("MB"));
        break;
    case 2:
        ret.append(_("GB"));
        break;
    default:
        ret.append(_("B"));
        break;
    }
    return ret;

}

void DataFile::rename(QString name, bool add)
{
    DataDirectory *parent;

    parent = static_cast<DataDirectory *>(this->parent());
    if (nullptr == parent) return;

    //更新父节点关于子节点的hash表
    if (!add) parent->updateChild(m_oDisplayName, name);

    m_oDestination = parent->destination() + "/" + name;

    m_oDisplayName = name;

    if (isDirectory())
    {
        parent = static_cast<DataDirectory *>(this);
        parent->rename(name);
    }
}

void DataFile::copy()
{
    DataDirectory *dir;

    if (".." == m_oDisplayName) return;
    if (m_bIsDisk) return;

    if (m_bIsDirectory)
    {
        dir = static_cast<DataDirectory *>(this);
        dir->copy();
        return;
    }

    if (QFile::copy(m_oSource, m_oDestination))
    {
        copied(m_oSource, m_oDestination);
        debug(_("Copied file %1 to %2").arg(m_oSource).arg(m_oDestination));
    }
}

void DataFile::filter()
{
    DataDirectory *parent;

    parent = qobject_cast<DataDirectory *>(this->parent());
    if (nullptr == parent) return;
    if (parent->filterHidden() && m_bAttrHidden)
    {
        qDebug()<<"DataFile"<<__LINE__<<__func__;
        slotsDoHidde(true);
    }
    if (parent->filterBrokenSymbolLinks() && m_bAttrLink && nullptr == m_oLinkTarget)
        slotsDoBroken(true);
    if (parent->repleaceSymbolLinks() && m_oLinkTarget)
        slotsDoRepleace(true);
}

void DataFile::changeParent(DataFile *p)
{
    DataDirectory *oldParent;
    DataDirectory *newParent;

    if (!p->isDirectory()) return;

    newParent = static_cast<DataDirectory *>(p);
    oldParent = qobject_cast<DataDirectory *>(parent());

    size(m_u64Size, false);
    if (m_bIsDirectory) directory(false, this);
    else file(false, this);
    child(this, false);

    disconnect(this, SIGNAL(size(ulong,bool)), oldParent, SLOT(doSize(ulong,bool)));
    disconnect(this, SIGNAL(directory(bool, DataFile *)), oldParent, SLOT(doDirectory(bool, DataFile *)));
    disconnect(this, SIGNAL(file(bool, DataFile *)), oldParent, SLOT(doFile(bool, DataFile *)));
    disconnect(this, SIGNAL(child(DataFile*,bool)), oldParent, SLOT(doChild(DataFile*,bool)));
    disconnect(this, SIGNAL(copied(QString,QString)), oldParent, SIGNAL(copied(QString,QString)));
    disconnect(oldParent, SIGNAL(update(QString)), this, SLOT(onUpdate(QString)));
    disconnect(oldParent, SIGNAL(hidden(bool)), this, SLOT(slotsDoHidde(bool)));
    disconnect(oldParent, SIGNAL(broken(bool)), this, SLOT(slotsDoBroken(bool)));
    disconnect(oldParent, SIGNAL(repleace(bool)), this, SLOT(slotsDoRepleace(bool)));
    connect(this, SIGNAL(size(ulong,bool)), newParent, SLOT(doSize(ulong,bool)));
    connect(this, SIGNAL(directory(bool, DataFile *)), newParent, SLOT(doDirectory(bool, DataFile *)));
    connect(this, SIGNAL(file(bool, DataFile *)), newParent, SLOT(doFile(bool, DataFile *)));
    connect(this, SIGNAL(child(DataFile*,bool)), newParent, SLOT(doChild(DataFile*,bool)));
    connect(this, SIGNAL(copied(QString,QString)), newParent, SIGNAL(copied(QString,QString)));
    connect(newParent, SIGNAL(update(QString)), this, SLOT(onUpdate(QString)));
    connect(newParent, SIGNAL(hidden(bool)), this, SLOT(slotsDoHidde(bool)));
    connect(newParent, SIGNAL(broken(bool)), this, SLOT(slotsDoBroken(bool)));
    connect(newParent, SIGNAL(repleace(bool)), this, SLOT(slotsDoRepleace(bool)));

    setParent(newParent);
    size(m_u64Size, true);
    if (m_bIsDirectory) directory(true, this);
    else file(true, this);
    child(this, true);
    newParent->update(newParent->destination());
}

void DataFile::onUpdate(QString path)
{
    m_oDestination = path + "/" + m_oDisplayName;
    debug(_("Update destination to %1").arg(m_oDestination));
    if (m_bIsDirectory) update(m_oDestination);
}

void DataFile::slotsDoHidde(bool b)
{
    DataDirectory *parent;

    if (".." == m_oDisplayName) return;
    if (m_bIsDisk) return;
    parent = qobject_cast<DataDirectory *>(this->parent());
    if (m_bAttrHidden)
    {
        if (b)
        {
            qDebug()<<__func__<<__LINE__<<"Filter hidden file 文件"<<b;
            parent->take(this);
            size(this->m_u64Size, false);
            doHidden(this);
            disconnect(parent, SIGNAL(hidden(bool)), this, SLOT(slotsDoHidde(bool)));
            debug(_("Filter hidden file. %1").arg(m_oDisplayName));

        }
    }
    else
    {
        if (m_bIsDirectory)
        {
            parent = qobject_cast<DataDirectory *>(this);
            parent->execHidden(b);
            qDebug()<<"DataFile"<<__func__<<__LINE__<<b;

        }
    }
}

void DataFile::slotsDoBroken(bool b)
{
    DataDirectory *parent;
    QFileInfo f;

    if (".." == m_oDisplayName) return;
    if (m_bIsDisk) return;
    parent = qobject_cast<DataDirectory *>(this->parent());
    if (m_bAttrLink)
    {
        if (b)
        {
            f.setFile(m_oSource);
            if (!f.symLinkTarget().isEmpty())
            {
                f.setFile(f.symLinkTarget());
                if (f.exists())
                {
                    debug(_("Symbol link file %1 has target file %2, do not filter.")
                          .arg(m_oRealName).arg(f.absoluteFilePath()));
                    return;
                }
            }
            if (m_oLinkTarget) delete m_oLinkTarget;
            m_oLinkTarget = nullptr;
            parent->take(this);
            size(m_u64Size, false);
            doBroken(this);
            disconnect(parent, SIGNAL(broken(bool)), this, SLOT(slotsDoBroken(bool)));
            debug(_("Filter broken symbol link file. %1").arg(m_oDisplayName));
        }
    }
    else
    {
        if (m_bIsDirectory)
        {
            parent = qobject_cast<DataDirectory *>(this);
            parent->execBroken(b);
        }
    }
}

void DataFile::slotsDoRepleace(bool b)
{
    DataDirectory *parent;
    QFileInfo f;

    if (".." == m_oDisplayName) return;
    if (m_bIsDisk) return;
    parent = qobject_cast<DataDirectory *>(this->parent());
    if (m_bAttrLink && m_oLinkTarget && b)
    {
        f.setFile(m_oLinkTarget->source());
        if (!f.exists())
        {
            delete m_oLinkTarget;
            m_oLinkTarget = nullptr;
            return;
        }
        parent->take(this);
        parent->append(m_oLinkTarget);
        doRepleace(this);
        disconnect(parent, SIGNAL(repleace(bool)), this, SLOT(slotsDoRepleace(bool)));
        debug(_("Replace symbol link file. %1").arg(m_oDisplayName));
        return;
    }
    if (m_bIsDirectory)
    {
        parent = qobject_cast<DataDirectory *>(this);
        parent->execRepleace(b);
        if (parent->linkTarget())
        {
            parent = static_cast<DataDirectory *>(parent->linkTarget());
            parent->execRepleace(b);
        }
    }
}

DataFile::~DataFile()
{
    size(m_u64Size, false);
    if (m_bIsDirectory)
        directory(false, this);
    else
        file(false, this);
    child(this, false);
}
