#include "gitmanager.h"
#include "../utils.h"
#include "../settings.h"

#include <QDir>
#include <QFileInfo>

GitManager::GitManager(QObject *parent) : QObject(parent)
{
}

void GitManager::createRepository(const QString &folder)
{
    QString currentBranch;
    if (hasRepository(folder,currentBranch))
        throw GitError(tr("Folder \"%1\" already has a repository!"));
    QStringList args;
    args.append("init");
    runGit(folder,args);

    QStringList contents;
    contents.append(".git");
    contents.append("*.o");
    contents.append("*.exe");
    contents.append("*.layout");
#ifdef Q_OS_LINUX
    contents.append("*.");
#endif

    QDir dir(folder);
    stringsToFile(contents,dir.filePath(".gitignore"));
    QString output;
    add(folder,".gitignore",output);
}

bool GitManager::hasRepository(const QString &folder, QString& currentBranch)
{
    if (folder.isEmpty())
        return false;
    QStringList args;
    args.append("status");
    args.append("-b");
    args.append("-u");
    args.append("no");
    args.append("--ignored=no");
    QString output = runGit(folder,args);
    bool result = output.startsWith("On branch");
    if (result) {
        int pos = QString("On branch").length();
        while (pos<output.length() && output[pos].isSpace())
            pos++;
        int endPos = pos;
        while (endPos<output.length() && !output[endPos].isSpace())
            endPos++;
        currentBranch = output.mid(pos,endPos-pos);
    }
    return result;
}

QString GitManager::rootFolder(const QString &folder)
{
    QStringList args;
    args.append("rev-parse");
    args.append("--show-toplevel");
    return runGit(folder,args).trimmed();
}

bool GitManager::isFileInRepository(const QFileInfo& fileInfo)
{
    QStringList args;
    args.append("ls-files");
    args.append(fileInfo.fileName());
    QString output = runGit(fileInfo.absolutePath(),args);
    return output.trimmed() == fileInfo.fileName();
}

bool GitManager::isFileStaged(const QFileInfo &fileInfo)
{
    QStringList args;
    args.append("diff");
    args.append("--staged");
    args.append("--name-only");
    args.append(fileInfo.fileName());
    QString output = runGit(fileInfo.absolutePath(),args);
    return output.trimmed() == fileInfo.fileName();
}

bool GitManager::isFileChanged(const QFileInfo &fileInfo)
{
    QStringList args;
    args.append("diff");
    args.append("--name-only");
    args.append(fileInfo.fileName());
    QString output = runGit(fileInfo.absolutePath(),args);
    return output.trimmed() == fileInfo.fileName();
}

bool GitManager::add(const QString &folder, const QString &path, QString& output)
{
    QStringList args;
    args.append("add");
    args.append(path);
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::remove(const QString &folder, const QString &path, QString& output)
{
    QStringList args;
    args.append("rm");
    args.append(path);
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::rename(const QString &folder, const QString &oldName,
                        const QString &newName, QString& output)
{
    QStringList args;
    args.append("mv");
    args.append(oldName);
    args.append(newName);
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::restore(const QString &folder, const QString &path, QString& output)
{
    QStringList args;
    args.append("restore");
    if (path.isEmpty())
        args.append(".");
    else
        args.append(path);
    output = runGit(folder,args);
    return isSuccess(output);
}

int GitManager::logCounts(const QString &folder, const QString &branch)
{
    QStringList args;
    args.append("rev-list");
    args.append("--count");
    if (branch.isEmpty())
        args.append("HEAD");
    else
        args.append(branch);
    QString s = runGit(folder,args).trimmed();
    bool ok;
    int result = s.toInt(&ok);
    if (!ok)
        result = 0;
    return result;
}

QList<PGitCommitInfo> GitManager::log(const QString &folder, int start, int count, const QString &branch)
{
    QStringList args;
    args.append("log");
    args.append("--skip");
    args.append(QString("%1").arg(start));
    args.append("-n");
    args.append(QString("%1").arg(count));
    args.append("--format=medium");
    args.append("--date=iso-strict");
    if (branch.isEmpty())
        args.append("HEAD");
    else
        args.append(branch);
    QString output = runGit(folder,args);
    QStringList lines = textToLines(output);
    QList<PGitCommitInfo> result;
    int pos = 0;
    PGitCommitInfo commitInfo;
    while (pos<lines.length()) {
        if (lines[pos].startsWith("commit ")) {
            commitInfo = std::make_shared<GitCommitInfo>();
            commitInfo->commitHash=lines[pos].mid(QString("commit ").length()).trimmed();
            result.append(commitInfo);
        } else if(!commitInfo) {
            break;
        } else if (lines[pos].startsWith("Author:")) {
            commitInfo->author=lines[pos].mid(QString("Author:").length()).trimmed();
        } else if (lines[pos].startsWith("Date:")) {
            commitInfo->authorDate=QDateTime::fromString(lines[pos].mid(QString("Date:").length()).trimmed(),Qt::ISODate);
        } else if (!lines[pos].trimmed().isEmpty()) {
            if (commitInfo->title.isEmpty()) {
                commitInfo->title = lines[pos].trimmed();
            } else {
                commitInfo->fullCommitMessage.append(lines[pos].trimmed()+"\n");
            }
        }
        pos++;
    }
    return result;
}

QStringList GitManager::listFiles(const QString &folder)
{
    QStringList args;
    args.append("ls-files");
    return textToLines(runGit(folder,args));
}

QStringList GitManager::listStagedFiles(const QString &folder)
{
    QStringList args;
    args.append("diff");
    args.append("--staged");
    args.append("--name-only");
    return textToLines(runGit(folder,args));
}

QStringList GitManager::listChangedFiles(const QString &folder)
{
    QStringList args;
    args.append("diff");
    args.append("--name-only");
    return textToLines(runGit(folder,args));
}

QStringList GitManager::listConflicts(const QString &folder)
{
    QStringList args;
    args.append("diff");
    args.append("--name-only");
    args.append("--diff-filter=U");
    return textToLines(runGit(folder,args));
}

QStringList GitManager::listRemotes(const QString &folder)
{
    QStringList args;
    args.append("remote");
    return textToLines(runGit(folder,args));
}

bool GitManager::removeRemote(const QString &folder, const QString &remoteName, QString& output)
{
    QStringList args;
    args.append("remote");
    args.append("remove");
    args.append(remoteName);

    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::renameRemote(const QString &folder, const QString &oldName, const QString &newName, QString &output)
{
    QStringList args;
    args.append("remote");
    args.append("rename");
    args.append(oldName);
    args.append(newName);

    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::addRemote(const QString &folder, const QString &name, const QString &url, QString &output)
{
    QStringList args;
    args.append("remote");
    args.append("add");
    args.append(name);
    args.append(url);

    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::setRemoteURL(const QString &folder, const QString &name, const QString &newURL, QString &output)
{
    QStringList args;
    args.append("remote");
    args.append("set-url");
    args.append(name);
    args.append(newURL);

    output = runGit(folder,args);
    return isSuccess(output);
}

QString GitManager::getRemoteURL(const QString &folder, const QString &name)
{
    QStringList args;
    args.append("remote");
    args.append("get-url");
    args.append(name);
    return runGit(folder,args).trimmed();
}

QString GitManager::getBranchRemote(const QString &folder, const QString &branch)
{
    QStringList args;
    args.append("config");
    args.append("--get");
    args.append(QString("branch.%1.remote").arg(branch));
    return runGit(folder,args).trimmed();
}

QString GitManager::getBranchMerge(const QString &folder, const QString &branch)
{
    QStringList args;
    args.append("config");
    args.append("--get");
    args.append(QString("branch.%1.merge").arg(branch));
    return runGit(folder,args).trimmed();
}

bool GitManager::setBranchUpstream(
        const QString &folder,
        const QString &branch,
        const QString &remoteName,
        QString& output)
{
    QStringList args;
    args.append("branch");
    args.append(QString("--set-upstream-to=%1/%2").arg(remoteName,branch));
    args.append(branch);
    output = runGit(folder,args).trimmed();
    return isSuccess(output);
}

bool GitManager::fetch(const QString &folder, QString &output)
{
    QStringList args;
    args.append("fetch");
    output = runGit(folder,args).trimmed();
    return isSuccess(output);
}

bool GitManager::pull(const QString &folder, QString &output)
{
    QStringList args;
    args.append("pull");
    output = runGit(folder,args).trimmed();
    return isSuccess(output);
}

bool GitManager::push(const QString &folder, QString &output)
{
    QStringList args;
    args.append("push");
    output = runGit(folder,args).trimmed();
    return isSuccess(output);
}

bool GitManager::push(const QString &folder, const QString &remoteName, const QString &branch, QString &output)
{
    QStringList args;
    args.append("push");
    args.append("--set-upstream");
    args.append(remoteName);
    args.append(branch);
    output = runGit(folder,args).trimmed();
    return isSuccess(output);
}

bool GitManager::removeConfig(const QString &folder, const QString &name, QString &output)
{
    QStringList args;
    args.append("config");
    args.append("--unset-all");
    args.append(name);
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::setConfig(const QString &folder, const QString &name, const QString &value, QString &output)
{
    removeConfig(folder,name,output);
    QStringList args;
    args.append("config");
    args.append("--add");
    args.append(name);
    args.append(value);
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::setUserName(const QString &folder, const QString &userName, QString &output)
{
    return setConfig(folder,"user.name",userName,output);
}

bool GitManager::setUserEmail(const QString &folder, const QString &userEmail, QString &output)
{
    return setConfig(folder,"user.email",userEmail,output);
}

QString GitManager::getConfig(const QString& folder, const QString &name)
{
    QStringList args;
    args.append("config");
    args.append("--get");
    args.append(name);
    return runGit(folder,args).trimmed();
}

QString GitManager::getUserName(const QString& folder)
{
    return getConfig(folder, "user.name");
}

QString GitManager::getUserEmail(const QString& folder)
{
    return getConfig(folder, "user.email");
}

QStringList GitManager::listBranches(const QString &folder, int &current)
{
    QStringList args;
    args.append("branch");
    args.append("-a");
    args.append("-l");
    QStringList temp = textToLines(runGit(folder,args));
    current = -1;
    for (int i=0;i<temp.length();i++) {
        QString s = temp[i];
        if (s.startsWith('*')) {
            current = i;
            temp[i] = s.mid(1).trimmed();
        } else if (s.startsWith('+')) {
            temp[i] = s.mid(1).trimmed();
        } else {
            temp[i] = s.trimmed();
        }
    }
    return temp;
}

bool GitManager::switchToBranch(const QString &folder, const QString &branch,
                                bool create, bool force, bool merge, bool track,
                                bool noTrack, bool forceCreation, QString& output)
{
    QStringList args;
    args.append("switch");
    if (forceCreation)
        args.append("-C");
    else if (create)
        args.append("-c");
    if (merge)
        args.append("-m");
    if (force)
        args.append("-f");
    if (track)
        args.append("--track");
    else if (noTrack)
        args.append("--no-track");
    args.append(branch);
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::merge(const QString &folder, const QString &commit, bool squash,
                       bool fastForwardOnly, bool noFastForward, bool noCommit,
                       QString& output,
                       const QString& commitMessage)
{
    QStringList args;
    args.append("merge");
    if (squash)
        args.append("--squash");
    if (fastForwardOnly)
        args.append("--ff-only");
    else if (noFastForward)
        args.append("--no-ff");
    if (noCommit)
        args.append("--no-commit");
    if (!commitMessage.isEmpty()
            && commitMessage != QObject::tr("<Auto Generated by Git>")){
        args.append("-m");
        args.append(commitMessage);
    }
    args.append(commit);
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::continueMerge(const QString &folder)
{
    QStringList args;
    args.append("merge");
    args.append("--continue");
    QString output = runGit(folder,args);
    return isSuccess(output);

}

void GitManager::abortMerge(const QString &folder)
{
    QStringList args;
    args.append("merge");
    args.append("--abort");
    runGit(folder,args);
}

bool GitManager::isSuccess(const QString &output)
{
    QStringList lst = textToLines(output);
    if (!lst.isEmpty()) {
        foreach (const QString& s, lst) {
            QString last= s.trimmed();
            if (last.startsWith("error:") || last.startsWith("fatal:"))
                return false;
        }
        return true;
    }
    return true;
}

bool GitManager::clone(const QString &folder, const QString &url, QString& output)
{
    QStringList args;
    args.append("clone");
    args.append(url);
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::commit(const QString &folder, const QString &message, bool autoStage, QString& output)
{
    QStringList args;
    args.append("commit");
    if (autoStage)
        args.append("-a");
    args.append("-m");
    args.append(message);
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::revert(const QString &folder, QString& output)
{
    QStringList args;
    args.append("revert");
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::reset(const QString &folder, const QString &commit,
                       GitResetStrategy strategy,
                       QString& output)
{
    //todo reset type
    QStringList args;
    args.append("reset");
    switch(strategy) {
    case GitResetStrategy::Soft:
        args.append("--soft");
        break;
    case GitResetStrategy::Hard:
        args.append("--hard");
        break;
    case GitResetStrategy::Mixed:
        args.append("--mixed");
        break;
    case GitResetStrategy::Merge:
        args.append("--merge");
        break;
    case GitResetStrategy::Keep:
        args.append("--keep");
        break;
    }
    args.append(commit);
    output = runGit(folder,args);
    return isSuccess(output);
}

bool GitManager::isValid()
{
    return pSettings->vcs().gitOk();
}

QString GitManager::runGit(const QString& workingFolder, const QStringList &args)
{
    if (!isValid())
        return "";
    QFileInfo fileInfo(pSettings->vcs().gitPath());
    if (!fileInfo.exists())
        return "fatal: git doesn't exist";
    emit gitCmdRunning(QString("Running in \"%1\": \n \"%2\" \"%3\"")
                       .arg(workingFolder,
                            pSettings->vcs().gitPath(),
                            args.join("\" \"")));
//    qDebug()<<"---------";
//    qDebug()<<args;
    QProcessEnvironment env;
#ifdef Q_OS_WIN
    env.insert("PATH",pSettings->dirs().appDir());
    env.insert("GIT_ASKPASS",includeTrailingPathDelimiter(pSettings->dirs().appDir())+"redpanda-win-git-askpass.exe");
#elif defined(Q_OS_LINUX)
    env.insert(QProcessEnvironment::systemEnvironment());
    env.insert("LANG","en");
    env.insert("LANGUAGE","en");
    env.insert("GIT_ASKPASS",includeTrailingPathDelimiter(pSettings->dirs().appLibexecDir())+"redpanda-git-askpass");
#endif
    QString output = runAndGetOutput(
                fileInfo.absoluteFilePath(),
                workingFolder,
                args,
                "",
                false,
                env);
    output = escapeUTF8String(output.toUtf8());
//    qDebug()<<output;
    emit gitCmdFinished(output);
//    if (output.startsWith("fatal:"))
//        throw GitError(output);
    return output;
}

QString GitManager::escapeUTF8String(const QByteArray &rawString)
{
    QByteArray stringValue;
    int p = 0;
    while (p<rawString.length()) {
        char ch = rawString[p];
        if (ch =='\\' && p+1 < rawString.length()) {
            p++;
            ch = rawString[p];
            switch (ch) {
            case '\'':
                stringValue+=0x27;
                p++;
                break;
            case '"':
                stringValue+=0x22;
                p++;
                break;
            case '?':
                stringValue+=0x3f;
                p++;
                break;
            case '\\':
                stringValue+=0x5c;
                p++;
                break;
            case 'a':
                stringValue+=0x07;
                p++;
                break;
            case 'b':
                stringValue+=0x08;
                p++;
                break;
            case 'f':
                stringValue+=0x0c;
                p++;
                break;
            case 'n':
                stringValue+=0x0a;
                p++;
                break;
            case 'r':
                stringValue+=0x0d;
                p++;
                break;
            case 't':
                stringValue+=0x09;
                p++;
                break;
            case 'v':
                stringValue+=0x0b;
                p++;
                break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            {
                int i=0;
                for (i=0;i<3;i++) {
                    if (p+i>=rawString.length() ||
                             rawString[p+i]<'0' || rawString[p+i]>'7')
                        break;
                }
                bool ok;
                unsigned char ch = rawString.mid(p,i).toInt(&ok,8);
                stringValue+=ch;
                p+=i;
                break;
            }
            }
        } else {
            if (ch!='\"')
                stringValue+=ch;
            p++;
        }
    }
    return QString::fromUtf8(stringValue);
}

GitError::GitError(const QString &reason):BaseError(reason)
{

}