#include "input.h"
#include "output.h"

#include <library/cpp/testing/unittest/registar.h>

#include <util/system/file.h>
#include <util/system/yassert.h>

#ifdef _win_
    #include <io.h>
#endif

class TMockStdIn {
public:
    TMockStdIn()
        : StdInCopy_(dup(0))
    {
    }
    ~TMockStdIn() {
        close(StdInCopy_);
    }

    template <typename FuncType>
    void ForInput(const TStringBuf text, const FuncType& func) {
        TFile tempFile(TFile::Temporary("input_ut"));
        tempFile.Write(text.data(), text.size());
        tempFile.FlushData();
        tempFile.Seek(0, sSet);

        TFileHandle tempFh(tempFile.GetHandle());
        tempFh.Duplicate2Posix(0);
        tempFh.Release();

        func();
        Cin.ReadAll();
        dup2(StdInCopy_, 0);
        clearerr(stdin);
    }

private:
    int StdInCopy_;
};

class TNoInput: public IInputStream {
public:
    TNoInput(ui64 size)
        : Size_(size)
    {
    }

protected:
    size_t DoRead(void*, size_t len) override {
        len = Min(static_cast<ui64>(len), Size_);
        Size_ -= len;
        return len;
    }

private:
    ui64 Size_;
};

class TNoOutput: public IOutputStream {
public:
    TNoOutput() = default;

protected:
    void DoWrite(const void*, size_t) override {
    }
};

class TSimpleStringInput: public IInputStream {
public:
    TSimpleStringInput(const TString& string)
        : String_(string)
    {
    }

protected:
    size_t DoRead(void* buf, size_t len) override {
        Y_ASSERT(len != 0);

        if (String_.empty()) {
            return 0;
        }

        *static_cast<char*>(buf) = String_[0];
        String_.remove(0, 1);
        return 1;
    }

private:
    TString String_;
};

Y_UNIT_TEST_SUITE(TInputTest) {
    Y_UNIT_TEST(BigTransfer) {
        ui64 size = 1024ull * 1024ull * 1024ull * 5;
        TNoInput input(size);
        TNoOutput output;

        ui64 transferred = TransferData(&input, &output);

        UNIT_ASSERT_VALUES_EQUAL(transferred, size);
    }

    Y_UNIT_TEST(TestReadTo) {
        /* This one tests default implementation of ReadTo. */

        TSimpleStringInput in("0123456789abc");

        TString t;
        UNIT_ASSERT_VALUES_EQUAL(in.ReadTo(t, '7'), 8);
        UNIT_ASSERT_VALUES_EQUAL(t, "0123456");
        UNIT_ASSERT_VALUES_EQUAL(in.ReadTo(t, 'z'), 5);
        UNIT_ASSERT_VALUES_EQUAL(t, "89abc");
        UNIT_ASSERT_VALUES_EQUAL(in.ReadTo(t, 'z'), 0);
        UNIT_ASSERT_VALUES_EQUAL(t, "89abc");
    }

    Y_UNIT_TEST(TestReadLine) {
        TSimpleStringInput in("1\n22\n333");

        TString t;
        UNIT_ASSERT_VALUES_EQUAL(in.ReadLine(t), 2);
        UNIT_ASSERT_VALUES_EQUAL(t, "1");
        UNIT_ASSERT_VALUES_EQUAL(in.ReadLine(t), 3);
        UNIT_ASSERT_VALUES_EQUAL(t, "22");
        UNIT_ASSERT_VALUES_EQUAL(in.ReadLine(t), 3);
        UNIT_ASSERT_VALUES_EQUAL(t, "333");
        UNIT_ASSERT_VALUES_EQUAL(in.ReadLine(t), 0);
        UNIT_ASSERT_VALUES_EQUAL(t, "333");
    }

    Y_UNIT_TEST(TestStdInReadTo) {
        std::pair<std::pair<TStringBuf, char>, TStringBuf> testPairs[] = {
            {{"", '\n'}, ""},
            {{"\n", '\n'}, ""},
            {{"\n\t", '\t'}, "\n"},
            {{"\t\n", '\n'}, "\t"},
            {{"a\tb\n", '\t'}, "a"}};

        TMockStdIn stdIn;

        for (const auto& testPair : testPairs) {
            const TStringBuf text = testPair.first.first;
            const char delim = testPair.first.second;
            const TStringBuf expectedValue = testPair.second;

            stdIn.ForInput(text,
                           [=] {
                               TString value;
                               Cin.ReadTo(value, delim);
                               UNIT_ASSERT_VALUES_EQUAL(value, expectedValue);
                           });
        }
    }
}