#include "affinity.hh" // FIXME
#include "command.hh"
#include "progress-bar.hh"
#include "shared.hh"
#include "store-api.hh"
#include "thread-pool.hh"

#include <atomic>

using namespace nix;

struct CmdCopySigs : StorePathsCommand
{
    Strings substituterUris;

    CmdCopySigs()
    {
        mkFlag('s', "substituter", {"store-uri"}, "use signatures from specified store", 1,
            [&](Strings ss) { substituterUris.push_back(ss.front()); });
    }

    std::string name() override
    {
        return "copy-sigs";
    }

    std::string description() override
    {
        return "copy path signatures from substituters (like binary caches)";
    }

    void run(ref<Store> store, Paths storePaths) override
    {
        restoreAffinity(); // FIXME

        if (substituterUris.empty())
            throw UsageError("you must specify at least one substituter using ‘-s’");

        // FIXME: factor out commonality with MixVerify.
        std::vector<ref<Store>> substituters;
        for (auto & s : substituterUris)
            substituters.push_back(openStoreAt(s));

        ProgressBar progressBar;

        ThreadPool pool;

        std::atomic<size_t> done{0};
        std::atomic<size_t> added{0};

        auto showProgress = [&]() {
            return (format("[%d/%d done]") % done % storePaths.size()).str();
        };

        progressBar.updateStatus(showProgress());

        auto doPath = [&](const Path & storePath) {
            auto activity(progressBar.startActivity(format("getting signatures for ‘%s’") % storePath));

            checkInterrupt();

            auto info = store->queryPathInfo(storePath);

            StringSet newSigs;

            for (auto & store2 : substituters) {
                if (!store2->isValidPath(storePath)) continue;
                auto info2 = store2->queryPathInfo(storePath);

                /* Don't import signatures that don't match this
                   binary. */
                if (info.narHash != info2.narHash ||
                    info.narSize != info2.narSize ||
                    info.references != info2.references)
                    continue;

                for (auto & sig : info2.sigs)
                    if (!info.sigs.count(sig))
                        newSigs.insert(sig);
            }

            if (!newSigs.empty()) {
                store->addSignatures(storePath, newSigs);
                added += newSigs.size();
            }

            done++;
            progressBar.updateStatus(showProgress());
        };

        for (auto & storePath : storePaths)
            pool.enqueue(std::bind(doPath, storePath));

        pool.process();

        progressBar.done();

        printMsg(lvlInfo, format("imported %d signatures") % added);
    }
};

static RegisterCommand r1(make_ref<CmdCopySigs>());

struct CmdQueryPathSigs : StorePathsCommand
{
    CmdQueryPathSigs()
    {
    }

    std::string name() override
    {
        return "query-path-sigs";
    }

    std::string description() override
    {
        return "print store path signatures";
    }

    void run(ref<Store> store, Paths storePaths) override
    {
        for (auto & storePath : storePaths) {
            auto info = store->queryPathInfo(storePath);
            std::cout << storePath << " ";
            if (info.ultimate) std::cout << "ultimate ";
            for (auto & sig : info.sigs)
                std::cout << sig << " ";
            std::cout << "\n";
        }
    }
};

static RegisterCommand r2(make_ref<CmdQueryPathSigs>());

struct CmdSignPaths : StorePathsCommand
{
    Path secretKeyFile;

    CmdSignPaths()
    {
        mkFlag('k', "key-file", {"file"}, "file containing the secret signing key", &secretKeyFile);
    }

    std::string name() override
    {
        return "sign-paths";
    }

    std::string description() override
    {
        return "sign the specified paths";
    }

    void run(ref<Store> store, Paths storePaths) override
    {
        if (secretKeyFile.empty())
            throw UsageError("you must specify a secret key file using ‘-k’");

        SecretKey secretKey(readFile(secretKeyFile));

        size_t added{0};

        for (auto & storePath : storePaths) {
            auto info = store->queryPathInfo(storePath);

            auto info2(info);
            info2.sigs.clear();
            info2.sign(secretKey);
            assert(!info2.sigs.empty());

            if (!info.sigs.count(*info2.sigs.begin())) {
                store->addSignatures(storePath, info2.sigs);
                added++;
            }
        }

        printMsg(lvlInfo, format("added %d signatures") % added);
    }
};

static RegisterCommand r3(make_ref<CmdSignPaths>());