#include "file_manager.h"

#include <library/cpp/regex/glob/glob_iterator.h>
#include <library/cpp/streams/factory/factory.h>

#include <util/folder/path.h>
#include <util/stream/null.h>
#include <util/system/fs.h>
#include <util/string/join.h>

namespace NH2Log {

    namespace {
        bool IsStdin(TStringBuf fname) {
            return !fname || fname == "-";
        }

        bool IsGzip(TStringBuf fname) {
            return fname.EndsWith(".gz");
        }

        bool StripSuffix(TStringBuf& fname, TStringBuf suffix) {
            return fname.ChopSuffix(TString::Join(".", suffix));
        }

        bool StripGzip(TStringBuf& fname) {
            return StripSuffix(fname, "gz");
        }

        TString GenerateInputName(TStringBuf inFile) {
            if (!inFile || inFile == "-") {
                return "(stdin)";
            } else {
                return TString{inFile};
            }
        }

        TString GenerateInputPrefix(TStringBuf inFile, const TString& outSuffix) {
            StripGzip(inFile);
            StripSuffix(inFile, outSuffix);
            return IsStdin(inFile) ? TString() : TString{inFile};
        }
    }


    TString ChDirToWorkspace(const TStringBuf inputName) {
        auto inputPrefix = GenerateInputPrefix(inputName, TString());
        Y_ENSURE(!IsStdin(inputPrefix));

        TFsPath dir(inputPrefix);
        if (!dir.IsDirectory()) {
            dir.MkDir(MODE0755);
        }
        NFs::SetCurrentWorkingDirectory(inputPrefix);
        return TFsPath("..") / inputName;
    }


    TFileManager::TFileManager(const TString& inFile, const TFileManagerOpts& opts)
        : Opts(opts)
        , InputName(GenerateInputName(inFile))
        , InputPrefix(GenerateInputPrefix(inFile, Opts.OutputSuffix))
    {
        Y_ENSURE(Opts.OutputSuffix);
        Y_ENSURE(!IsStdin(inFile) || Opts.AllowStdin);
        Y_ENSURE(InputPrefix);
        TFsPath inFilePath(inFile);
        if (!Opts.AllowNonexisting || inFilePath.Exists()) {
            Input = (
                FileInput = MakeHolder<TFileInput>(inFile)
            ).Get();
            if (IsGzip(inFile)) {
                Input = (
                    Gunzip = MakeHolder<TZDecompress>(FileInput.Get())
                ).Get();
            }
        } else if (Opts.AllowNonexisting) {
            Input = &Cnull;
        }
    }

    TString TFileManager::GetInputName() const {
        return InputName;
    }

    TFileManager TFileManager::CreateChildManager(const TString& intermediateFile) const {
        auto opts = Opts;
        opts.AllowStdin = false;
        opts.AllowNonexisting = true;
        return TFileManager(JoinSeq(".", {intermediateFile, Opts.OutputSuffix}), opts);
    }

    IInputStream& TFileManager::GetInput() const {
        return *Input;
    }

    IOutputStream& TFileManager::GetFileOutput(const TString& fname) {
        if (auto* out = OutputMap->FindPtr(fname)) {
            return **out;
        } else {
            return *OutputMap->emplace(fname, OpenOutput(
                JoinSeq(".", {fname, Opts.OutputSuffix, "gz"})
            )).first->second;
        }
    }

    TString TFileManager::GetFileNameBySuffix(TStringBuf suff) {
        Y_ENSURE(InputPrefix);
        Y_ENSURE(suff);
        return JoinSeq(".", {InputPrefix, suff});
    }

    void TFileManager::CloseOutput() {
        OutputMap->clear();
    }

    bool TFileManager::HasDoneTag(TStringBuf name) const {
        return Opts.KeepExisting && TFsPath(GetDoneTag(name)).Exists();
    }

    void TFileManager::SetDoneTag(TStringBuf name) {
        TFsPath(GetDoneTag(name)).Touch();
    }

    TString TFileManager::GetDoneTag(TStringBuf name) const {
        return JoinSeq(".", {name, "done", Opts.OutputSuffix});
    }

    TVector<TString> TFileManager::ListFiles(TString glob) const {
        TGlobPaths gpaths(JoinSeq(".", {glob, Opts.OutputSuffix}));
        TVector<TString> result;
        for (auto path : gpaths) {
            result.emplace_back(TString{TStringBuf(path).Chop(1 + Opts.OutputSuffix.size())});
        }
        return TVector<TString>(gpaths.begin(), gpaths.end());
    }
}
