#include "wrap-string.hpp"

#include "drivenengine.hpp"
#include "streambuffer.hpp"
#include "world.hpp"
#include "traceback.hpp"

#include <iomanip>

static void run_unittests(lua_State *L) {
    LuaVar unittests, name, func, err, globtab;
    LuaExtStack LS(L, unittests, name, func, err, globtab);

    LS.getglobaltable(globtab);
    LS.rawget(unittests, globtab, "unittests");

    // Sort the unit test names.
    eng::set<eng::string> names;
    LS.set(name, LuaNil);
    while (LS.next(unittests, name, func) != 0) {
        if (LS.isfunction(func) && LS.isstring(name)) {
            names.insert(LS.ckstring(name));
        }
    }

    // Run the functions in order
    bool any = false;
    for (const eng::string &name : names) {
        util::dprint("Running unittests: ", name);
        LS.rawget(func, unittests, name);

        lua_pushvalue(L, func.index());
        eng::string msg = traceback_pcall(L, 0, 0);
        if (!msg.empty()) {
            LS.set(err, msg);
            util::dprint(msg);
            any = true;
        }
    }

    if (any) {
        exit(1);
    }
}

class RunUnitTests : public DrivenEngine {
public:
    UniqueWorld world_;

    RunUnitTests(EngineWrapper *) {
        world_.reset(new World(WORLD_TYPE_MASTER));
        rescan_lua_source(true);
    }

    virtual void event_access(AccessKind kind, int64_t place_id, std::string_view datapk, StreamBuffer *retpk) override {
        switch (kind) {
            case AccessKind::INVOKE_LUA_SOURCE: {
                Invocation inv(kind, place_id, place_id, datapk);
                world_->invoke(0, inv);
                run_unittests(world_->state());
                stop_driver();
                break;
            }
            default: break;
        }
    }

    void event_update() override {}
};

////////////////////////////////////////////////////////////
//
// Driver tests.
//
// These tests can't go through the unit testing framework,
// because they're tests of the driver-engine interface
// and the driver.
//
////////////////////////////////////////////////////////////

static void write_closed_message(Channel *ch) {
    util::dprint("Chan ", ch->chid(), " closed [", ch->error(), "]\n");
}

static void dump_lines(StreamBuffer *in, int chid) {
    while (true) {
        eng::string l = in->readline();
        if (l == "") break;
        util::dprint("Chan ", chid, ": ", l);
    }
}

// This test connects to a public webserver and prints
// the output from the server.
class DriverWebServerTest : public DrivenEngine {
public:
    eng::vector<SharedChannel> channels_;
    DriverWebServerTest(EngineWrapper *) {
        SharedChannel ch = new_outgoing_channel("cert:stanford.edu:443");
        ch->out()->write_bytes("GET https://stanford.edu/xbanankjdsh.html HTTP/1.1\n\n");
        channels_.emplace_back(std::move(ch));
    }
    
    virtual void event_update() override {
        eng::vector<SharedChannel> keep;
        for (SharedChannel &ch : channels_) {
            dump_lines(ch->in(), ch->chid());
            if (ch->closed()) {
                write_closed_message(ch.get());
            } else {
                keep.emplace_back(std::move(ch));
            }
        }
        channels_ = std::move(keep);
    }
};

// This test produces a DNS resolution failure.
class DriverDNSFailTest : public DrivenEngine {
public:
    eng::vector<SharedChannel> channels_;
    DriverDNSFailTest(EngineWrapper *) {
        SharedChannel ch = new_outgoing_channel("akjsdkajshdakjshd.alk:80");
        ch->out()->write_bytes("GET http://stanford.edu/index.html HTTP/1.1\n\n");
        channels_.emplace_back(std::move(ch));
    }
    
    virtual void event_update() override {
        eng::vector<SharedChannel> keep;
        for (SharedChannel &ch : channels_) {
            dump_lines(ch->in(), ch->chid());
            if (ch->closed()) {
                write_closed_message(ch.get());
            } else {
                keep.emplace_back(std::move(ch));
            }
        }
        channels_ = std::move(keep);
    }
};

// This test just prints the time.
class DriverPrintClockTest : public DrivenEngine {
public:
    int count_;
    double last_clock_;
    DriverPrintClockTest(EngineWrapper *) {
        count_ = 0;
        last_clock_ = 0.0;
    }
    
    virtual void event_update() override {
        double clock = get_clock();
        if (clock > last_clock_ + 0.5) {
            int ms = eng::memhash();
            util::dprint(std::fixed, std::setprecision(2), clock, "   ", std::hex, ms);
            count_++;
            last_clock_ = clock;
        }
    }
};


DrivenEngineDefine("driverwebservertest", DriverWebServerTest);
DrivenEngineDefine("driverdnsfailtest", DriverDNSFailTest);
DrivenEngineDefine("driverprintclocktest", DriverPrintClockTest);
DrivenEngineDefine("rununittests", RunUnitTests);

