#include "wrap-string.hpp"

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

#include <iomanip>

static void write_closed_message(Channel *ch, StreamBuffer *out) {
    eng::ostringstream oss;
    oss << "Chan " << ch->chid() << " closed [" << ch->error() << "]\n";
    out->write_bytes(oss.str());
}

static void dump_lines(StreamBuffer *in, StreamBuffer *out, int chid) {
    while (true) {
        eng::string l = in->readline();
        if (l == "") break;
        eng::ostringstream oss;
        oss << "Chan " << chid << ": " << l;
        out->write_bytes(oss.str());
    }
}

// This test is the minimal possible DrivenEngine.
class DriverStubTest : public DrivenEngine {
    virtual void event_init(int argc, char *argv[]) {
        stop_driver();
    }
};

// This test connects to a public webserver and prints
// the output from the server.
class DriverWebServerTest : public DrivenEngine {
public:
    eng::vector<SharedChannel> channels_;
    virtual void event_init(int argc, char *argv[]) {
        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() {
        SharedChannel stdioch = get_stdio_channel();
        dump_lines(stdioch->in(), stdioch->out(), 0);
        eng::vector<SharedChannel> keep;
        for (SharedChannel &ch : channels_) {
            dump_lines(ch->in(), stdioch->out(), ch->chid());
            if (ch->closed()) {
                write_closed_message(ch.get(), stdioch->out());
            } 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_;
    virtual void event_init(int argc, char *argv[]) {
        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() {
        SharedChannel stdioch = get_stdio_channel();
        dump_lines(stdioch->in(), stdioch->out(), 0);
        eng::vector<SharedChannel> keep;
        for (SharedChannel &ch : channels_) {
            dump_lines(ch->in(), stdioch->out(), ch->chid());
            if (ch->closed()) {
                write_closed_message(ch.get(), stdioch->out());
            } 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_;
    virtual void event_init(int argc, char *argv[]) {
        count_ = 0;
        last_clock_ = 0.0;
    }
    
    virtual void event_update() {
        double clock = get_clock();
        if (clock > last_clock_ + 0.5) {
            int ms = eng::memhash();
            stdostream() << std::fixed << std::setprecision(2) << clock << "   " << std::hex << ms << "  ";
            count_++;
            last_clock_ = clock;
        }
        if (count_ == 4) {
            stdostream() << std::endl;
            count_ = 0;
        }
    }
};


class RunUnitTests : public DrivenEngine {
private:
    UniqueWorld world_;

    void event_init(int argc, char *argv[])
    {
        world_.reset(new World(util::WORLD_TYPE_STANDALONE));
        world_->update_source(get_lua_source());
        world_->run_unittests();
        stop_driver();
    }
};

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

