#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%new-style
%strict-args
%require-types
%enable-all-warnings

%include  ../../qore/classes/FtpServer.qc

%requires ../../../../qlib/Util.qm
%requires ../../../../qlib/FsUtil.qm
%requires ../../../../qlib/QUnit.qm
%requires ../../../../qlib/DataProvider
%requires ../../../../qlib/FtpPollerUtil.qm
%requires ../../../../qlib/FtpPoller.qm

%exec-class FtpPollerTest

class MyFtpPoller inherits FtpPoller {
    public {
        list<hash<FtpPollerFileEventInfo>> files;
    }

    private {
        hash<auto> opts;
    }

    constructor(FtpClient ftp, hash<auto> opts) : FtpPoller(ftp, opts) {
        self.opts = opts;
    }

    singleFileEvent(hash<FtpPollerFileEventInfo> fih) {
        files += fih;
    }

    postSingleFileEvent(hash<FtpPollerFileEventInfo> fih) {
    }
}

class FtpPollerTest inherits QUnit::Test {
    public {
    }

    private {
        const Files = (
            "file_1_",
            "file_2_",
            "file_3_",
            "file_4_",
        );
    }

    constructor() : Test("FtpPollerTest", "1.0") {
        addTestCase("main test", \mainTest());
        addTestCase("data provider test", \dataProviderTest());

        # Return for compatibility with test harness that checks return value
        set_return_value(main());
    }

    int getPort() {
        int port;
        if (m_options.port) {
            port = m_options.port;
        } else if (ENV.FTPCLIENT_TEST_PORT) {
            port = ENV.FTPCLIENT_TEST_PORT.toInt();
        } else {
            port = 0;
        }

        return port;
    }

    private mainTest() {
        int port = getPort();

        # PORT only supports IPv4, so we can't use "localhost", which may resolve
        # to an IPv6 address
        FtpServer serv(port, m_options.verbose - 1, "127.0.0.1");
        on_exit serv.shutdown();

        port = serv.getPort();

        FtpClient ftp_client("ftp://user:pass@127.0.0.1:" + port);
        Queue queue();
        ftp_client.setWarningQueue(20000, 20000, queue, "test");
        assertNothing(ftp_client.connect());

        TmpDir tmp_dir();
        string dir = tmp_dir.path;
        # printf("DIR: %y\n", dir);
        serv.setPwd(dir);

        list<TmpFile> files;
        map files += new TmpFile("ftp_test_" + $1, NOTHING, dir), Files;

        hash<string, bool> file_paths;
        map file_paths{$1.path} = True, files;

        # printf("files: %y\n", file_paths);

        MyFtpPoller poller(ftp_client, {"path": tmp_dir.path});
        assertTrue(poller.runOnce());

        foreach hash<FtpPollerFileEventInfo> file in (poller.files) {
            on_error printf("file: %y file_paths: %y\n", file - "data", keys file_paths);
            assertTrue(file.name != NOTHING);
            assertTrue(file_paths.hasKey(file.filepath));
        }
    }

    private dataProviderTest() {
        Logger logger();
        {
            LoggerLevel level;
            if (m_options.verbose > 3) {
                level = LoggerLevel::getLevelDebug();
            } else if (m_options.verbose > 2) {
                level = LoggerLevel::getLevelInfo();
            } else {
                level = LoggerLevel::getLevelError();
            }
            logger = new Logger("test", level);
        }
        logger.addAppender(new StdoutAppender());

        int port = getPort();

        # PORT only supports IPv4, so we can't use "localhost", which may resolve
        # to an IPv6 address
        FtpServer serv(port, m_options.verbose - 1, "127.0.0.1");
        on_exit serv.shutdown();

        TmpDir tmp_dir();
        string dir = tmp_dir.path;
        serv.setPwd(dir);
        serv.setDirect();

        port = serv.getPort();

        TmpDir local_dir();
        MyObserver observer();
        FtpDelayedPollerDataProvider prov({
            "url": sprintf("ftp://user:pass@127.0.0.1:%d%s", port, tmp_dir.path),
            "poll_interval": 1,
            "local_dir": local_dir.path,
        });
        prov.setLogger(logger);
        prov.registerObserver(observer);

        list<TmpFile> files;
        map files += new TmpFile("ftp_test_" + $1, NOTHING, dir), Files;
        map $1.file.write(get_random_string()), files;

        prov.observersReady();

        observer.wait();
        map assertEq($1, observer.h{$1}.name), (map basename($1.path), files);

        assertTrue(True);

        map assertEq(File::readBinaryFile($1.path),
            File::readBinaryFile(local_dir.path + DirSep + basename($1.path)), basename($1.path)),
            files;
    }
}

class MyObserver inherits Observer {
    public {
        hash<string, hash<auto>> h();
    }

    private {
        Mutex m();
        Condition cond();
    }

    update(string event_id, hash<auto> data_) {
        h{data_.name} = data_;
        if (h.size() == 3) {
            m.lock();
            on_exit m.unlock();

            cond.broadcast();
        }
    }

    wait() {
        date start = now_us();

        m.lock();
        on_exit m.unlock();

        while (h.size() != 4) {
            cond.wait(m, 1s);
            if ((now_us() - start) > 5s) {
                throw "ERROR", sprintf("%d/4 files received in timeout: %y", h.size(), keys h);
            }
        }
    }
}
