#ifdef HAVE_CONFIG_H
    #include "config.h"
#endif

#include <cstdio>
#include <memory>

#include "stubServer.h"
#include "testUtils.h"
#include "controller.h"
#include "signalHelpers.h"
#include "setupHelpers.h"
#include "testOutOfGame.h"
#include "netTests.h"
#include "viewTest.h"
#include "avatarTest.h"
#include "calendarTest.h"

#include <Eris/Connection.h>
#include <Eris/Account.h>
#include <Eris/Avatar.h>
#include <Eris/Entity.h>
#include <Eris/View.h>
#include <Eris/PollDefault.h>
#include <sigc++/object_slot.h>
#include <sigc++/object.h>
#include <Eris/LogStream.h>
#include <Eris/Log.h>
#include <Atlas/Objects/Operation.h>
#include <Atlas/Codecs/Bach.h>
#include <Atlas/Objects/Encoder.h>
#include <Atlas/Message/MEncoder.h>

#include <signal.h>
#include <sys/socket.h>
#include <wfmath/atlasconv.h>

using namespace Eris;
using std::endl;
using std::cout;
using std::cerr;

using Atlas::Objects::Entity::RootEntity;
using Atlas::Objects::Root;
using Atlas::Objects::Operation::Move;

typedef WFMath::Point<3> Point3;

class DummyDecoder : public Atlas::Objects::ObjectsDecoder
{
protected:
    void objectArrived(const Atlas::Objects::Root&)
    {
    }
};

void erisLog(Eris::LogLevel level, const std::string& msg)
{
    switch (level) {
    case LOG_ERROR:
        cerr << "ERIS_ERROR: " << msg << endl; break;
        
    case LOG_WARNING:
        cout << "ERIS_WARN: " << msg << endl; break;
        
    default:
        cout << "ERIS: " << msg << endl;
       return;
    }
}
    
class AttributeTracker : public SigC::Object
{
public:
    void waitForChangeOf(Eris::Entity* e, const std::string& attr)
    {
        assert(e);
        m_changed = false;
        
        e->observe(attr, SigC::slot(*this, &AttributeTracker::attrChange));
        while (!m_changed) Eris::PollDefault::poll();
    }
    
    Atlas::Message::Element newValue() const 
    { return m_value; }
    
private:
    void attrChange(const std::string& attr, const Atlas::Message::Element& v)
    {
        m_name = attr;
        m_value = v;
        m_changed = true;
    }
    
    bool m_changed;
    std::string m_name;
    Atlas::Message::Element m_value;
};
    
#pragma mark -

void testCharActivate(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount player = stdLogin("account_B", "sweede", con.get());

    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    AutoAvatar av = AvatarGetter(player.get()).take("acc_b_character");
    
    Eris::View* v = av->getView();
    assert(v->getTopLevel()->getId() == "_world");
    
    assert(v->getTopLevel()->hasChild("_hut_01"));
    assert(!v->getTopLevel()->hasChild("_field_01")); // not yet
}

void testCharCreate()
{
    AutoConnection con = stdConnect();
    AutoAccount player = stdLogin("account_A", "pumpkin", con.get());
    
    RootEntity charEnt;
    StringList prs;
    prs.push_back("settler");
    charEnt->setParents(prs);
    charEnt->setName("Fruitfucker");
    charEnt->setAttr("foo", 42);
    
    AutoAvatar av = AvatarGetter(player.get()).create(charEnt);
    
    assert(av->getEntity()->getName() == "Fruitfucker");
    assert(av->getEntity()->valueOfAttr("foo").asInt() == 42);
    
    assert(player->getActiveCharacters().count(av->getId()) == 1);
}

void testBadCreate()
{
    AutoConnection con = stdConnect();
    AutoAccount player = stdLogin("account_A", "pumpkin", con.get());
    
    AvatarGetter g(player.get());
    g.expectFailure();
    
    RootEntity charEnt;
    StringList prs;
    prs.push_back("__fail__");
    charEnt->setParents(prs);
    
    AutoAvatar av2 = g.create(charEnt);
    assert(av2.get() == NULL);
}

void testSet(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount acc = stdLogin("account_B", "sweede", con.get());
    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_table_1", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_vase_1", "acc_b_character");
    
    AutoAvatar av = AvatarGetter(acc.get()).take("acc_b_character");

    {
        WaitForAppearance wf(av->getView(), "_table_1");
        wf.waitFor("_vase_1");
        wf.run();
    }
    
    Eris::Entity* table = av->getView()->getEntity("_table_1");
    assert(table);
        
    assert(table->getPosition() == WFMath::Point<3>(1.0, 2.0, 3.0));
    assert(table->numContained() == 1);
    assert(table->getName() == "George");
    
    ctl.setAttr("_table_1", "status", "funky");
    
    AttributeTracker at;
    at.waitForChangeOf(table, "status");
        
    assert(at.newValue() == std::string("funky"));
    // check the value made it through
    assert(table->valueOfAttr("status") == std::string("funky"));
    
    // and that nothing else got bent
    assert(table->getPosition() == WFMath::Point<3>(1.0, 2.0, 3.0));
    assert(table->numContained() == 1);
    
    assert(table->getName() == "George");
}

class WaitForSay : public SigC::Object
{
public:
    WaitForSay(Eris::Entity* e, const std::string& what) : 
        m_what(what),
        m_heard(false)
    {
        e->Say.connect(SigC::slot(*this, &WaitForSay::onSay));
    }
    
    void run()
    {
        while (!m_heard) Eris::PollDefault::poll();
    }
private:
    void onSay(const Atlas::Objects::Root & what)
    {
		assert(what->hasAttr("say"));
		assert(what->getAttr("say").isString());
		assert(what->getAttr("say").asString() == m_what);
		
		m_heard = true;
    }

    std::string m_what;
    bool m_heard;
};

void testTalk(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount acc = stdLogin("account_B", "sweede", con.get());
    
    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_table_1", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_vase_1", "acc_b_character");
    
    AutoAvatar av = AvatarGetter(acc.get()).take("acc_b_character");
    
    {
        WaitForAppearance wf(av->getView(), "_table_1");
        wf.waitFor("_vase_1");
        wf.run();
    }
    
    Eris::Entity* vase = av->getView()->getEntity("_vase_1");
    WaitForSay wfs(vase, "cut yourself.");
    
    {
        Atlas::Objects::Operation::Talk t;
        Atlas::Objects::Root what;
        what->setAttr("say", "cut yourself.");
        t->setArgs1(what);
        t->setFrom("_vase_1");    
        ctl.broadcastSoundFrom("_vase_1", t);
    }
    
    wfs.run();
}


void testSeeMove(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount acc = stdLogin("account_B", "sweede", con.get());
    
    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_table_1", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_vase_1", "acc_b_character");
    
    AutoAvatar av = AvatarGetter(acc.get()).take("acc_b_character");
    {
        WaitForAppearance wf(av->getView(), "_table_1");
        wf.waitFor("_vase_1");
        wf.run();
    }
    
    SignalCounter0 moved;
    Eris::Entity* vase = av->getView()->getEntity("_vase_1");
    vase->Moved.connect(SigC::slot(moved, &SignalCounter0::fired));
    
 //   assert(vase->getPosition() == Point3(1.0, 2.0, 3.0));

    Point3 newPos(-2, -5, 1);
    ctl.movePos(vase->getId(), newPos);
      
    while (!moved.fireCount()) {
        Eris::PollDefault::poll();
    }
    
    assert(vase->getPosition() == newPos);
}

void testLocationChange(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount acc = stdLogin("account_B", "sweede", con.get());
    
    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_table_1", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_vase_1", "acc_b_character");
    
    AutoAvatar av = AvatarGetter(acc.get()).take("acc_b_character");
    {
        WaitForAppearance wf(av->getView(), "_vase_1");
        wf.run();
    }
    
    SignalCounter0 moved;
    SignalRecorder1<Eris::Entity*> locChanged;
    
    Eris::Entity* vase = av->getView()->getEntity("_vase_1");
    Eris::Entity* hut = av->getView()->getEntity("_hut_01");
    Eris::Entity* table = av->getView()->getEntity("_table_1");
    
    vase->Moved.connect(SigC::slot(moved, &SignalCounter0::fired));
    vase->LocationChanged.connect(SigC::slot(locChanged, &SignalRecorder1<Eris::Entity*>::fired));
    
    Point3 newPos(30, -19, 8);
    ctl.moveLocation(vase->getId(), "_hut_01", newPos);
      
    while (!moved.fireCount() && !locChanged.fireCount()) {
        Eris::PollDefault::poll();
    }
    
    assert(locChanged.lastArg0() == table);
    assert(vase->getPosition() == newPos);
    assert(vase->getLocation() == hut);
}

void testSeeMoveWithUpdates(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount acc = stdLogin("account_B", "sweede", con.get());
    
    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_table_1", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_vase_1", "acc_b_character");
    
    AutoAvatar av = AvatarGetter(acc.get()).take("acc_b_character");
    {
        WaitForAppearance wf(av->getView(), "_table_1");
        wf.waitFor("_vase_1");
        wf.run();
    }
    
    SignalCounter0 moved;
    Eris::Entity* vase = av->getView()->getEntity("_vase_1");
    vase->Moved.connect(SigC::slot(moved, &SignalCounter0::fired));
    assert(vase->valueOfAttr("stamina").asInt() == 105);
    
    Point3 newPos(-2, -5, 1);
    
    Move mv;
    Root arg;
    arg->setAttr("pos", newPos.toAtlas());
    arg->setAttr("stamina", 100);
    mv->setArgs1(arg);
        
    ctl.move(vase->getId(), mv);
      
    while (!moved.fireCount()) {
        Eris::PollDefault::poll();
    }
    
    assert(vase->getPosition() == newPos);
    assert(vase->valueOfAttr("stamina").asInt() == 100);
    
}

void testSightAction(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount acc = stdLogin("account_B", "sweede", con.get());
    
    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_table_1", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_vase_1", "acc_b_character");
    
    AutoAvatar av = AvatarGetter(acc.get()).take("acc_b_character");
    {
        WaitForAppearance wf(av->getView(), "_vase_1");
        wf.run();
    }

    SignalRecorderRef1<Atlas::Objects::Operation::RootOperation> action;
    Eris::Entity* vase = av->getView()->getEntity("_vase_1");
    vase->Acted.connect(SigC::slot(action, 
        &SignalRecorderRef1<Atlas::Objects::Operation::RootOperation>::fired));
    
    Atlas::Objects::Operation::Touch t;
    t->setFrom(vase->getId());
    ctl.broadcastSightFrom(vase->getId(), t);
    
    while (!action.fireCount()) {
        Eris::PollDefault::poll();
    }
    
    assert(action.lastArg0()->asMessage() == t->asMessage());
// same again, but with a non-built-in op type    
    {
        Atlas::Objects::Operation::Action parry;
        parry->setFrom(vase->getId());
        StringList prs;
        prs.push_back("parry");
        parry->setParents(prs);
        
        action.reset();
        ctl.broadcastSightFrom(vase->getId(), parry);
        
        while (!action.fireCount()) {
            Eris::PollDefault::poll();
        }
        
        assert(action.lastArg0()->asMessage() == parry->asMessage());
    }
}

void testSoundAction(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount acc = stdLogin("account_B", "sweede", con.get());
    
    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_table_1", "acc_b_character");
    
    AutoAvatar av = AvatarGetter(acc.get()).take("acc_b_character");
    {
        WaitForAppearance wf(av->getView(), "_table_1");
        wf.run();
    }

    SignalRecorderRef1<Atlas::Objects::Root> heard;
    Eris::Entity* table = av->getView()->getEntity("_table_1");
    table->Noise.connect(SigC::slot(heard, 
        &SignalRecorderRef1<Atlas::Objects::Root>::fired));
    
    Atlas::Objects::Operation::Touch t;
    t->setFrom(table->getId());
    ctl.broadcastSoundFrom(table->getId(), t);
    
    while (!heard.fireCount()) {
        Eris::PollDefault::poll();
    }
    
    assert(heard.lastArg0()->asMessage() == t->asMessage());
// same again, but with a non-built-in op type    
    {
        Atlas::Objects::Operation::Action parry;
        parry->setFrom(table->getId());
        StringList prs;
        prs.push_back("parry");
        parry->setParents(prs);
        
        heard.reset();
        ctl.broadcastSoundFrom(table->getId(), parry);
        
        while (!heard.fireCount()) {
            Eris::PollDefault::poll();
        }
        
        assert(heard.lastArg0()->asMessage() == parry->asMessage());
    }
}

class EntityDeleteWatcher : public SigC::Object
{
public:
    EntityDeleteWatcher(Eris::Entity* e) :
        m_ent(e),
        m_originalLoc(e->getLocation()),
        m_fired(false)
    { }
    
    void onEntityDelete(Eris::Entity* e)
    {
        assert(e == m_ent);
        assert(e->getLocation() == m_originalLoc);
        m_fired = true;
    }
    
    bool fired() const
    { return m_fired; }
    
private:
    Eris::Entity* m_ent;
    Eris::Entity* m_originalLoc;
    bool m_fired;
};

void testSightDelete(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount acc = stdLogin("account_B", "sweede", con.get());
    
    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_table_1", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_vase_1", "acc_b_character");
    
    AutoAvatar av = AvatarGetter(acc.get()).take("acc_b_character");
    {
        WaitForAppearance wf(av->getView(), "_vase_1");
        wf.run();
    }

    Eris::Entity* vase = av->getView()->getEntity("_vase_1");
    Eris::Entity* table = av->getView()->getEntity("_table_1");
    
    EntityDeleteWatcher dw(table);
    av->getView()->EntityDeleted.connect(SigC::slot(dw, 
        &EntityDeleteWatcher::onEntityDelete));
    
    assert(vase->getLocation() == table);
    assert(vase->getPosition() == Point3(50, 40, 0));
    
    ctl.deleteEntity("_table_1");
    
    while (!dw.fired()) Eris::PollDefault::poll();
    
    
    assert(vase->getLocation() == av->getView()->getEntity("_hut_01"));
    assert(vase->getPosition() == Point3(51, 42, 3));
}


void testMovement(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount acc = stdLogin("account_B", "sweede", con.get());
    ctl.setEntityVisibleToAvatar("_ball", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    
    AutoAvatar av = AvatarGetter(acc.get()).take("acc_b_character");
    {
        WaitForAppearance wf(av->getView(), "_ball");
        wf.run();
    }

    Eris::Entity* ball = av->getView()->getEntity("_ball");
    assert(!ball->isMoving());
    
    WFMath::Vector<3> vel(2, 3, 0);
    ctl.moveVelocity("_ball", vel);
    
    SignalRecorder1<bool> moving;
    ball->Moving.connect(SigC::slot(moving, &SignalRecorder1<bool>::fired));
    
    while (!moving.fireCount()) {
        Eris::PollDefault::poll();
    }
    
    assert(ball->getVelocity() == vel);
    
    /*
    
    run loop 50 times
    {
        each 0.1 of a second, do view::update prediction
        check predicted pos is close to locally predicted.
    }
    
    send stop
    
    check moving goes false, check final pos is 'exact' match, etc
 
    */
}

void testEmote(Controller& ctl)
{
    AutoConnection con = stdConnect();
    AutoAccount acc = stdLogin("account_B", "sweede", con.get());
    
    ctl.setEntityVisibleToAvatar("_hut_01", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_table_1", "acc_b_character");
    ctl.setEntityVisibleToAvatar("_vase_1", "acc_b_character");
    
    AutoAvatar av = AvatarGetter(acc.get()).take("acc_b_character");
    {
        WaitForAppearance wf(av->getView(), "_vase_1");
        wf.run();
    }

    Atlas::Objects::Operation::Imaginary imag;
    
    Atlas::Objects::Entity::Anonymous em;
    em->setId("emote");
    em->setAttr("description", "I wish I was a teapot");
    
    imag->setArgs1(em);
    imag->setFrom("_vase_1");
    imag->setSerialno(getNewSerialno());
    
    SignalRecorderRef1<std::string> emote;
    
    Eris::Entity* vase = av->getView()->getEntity("_vase_1");
    vase->Emote.connect(SigC::slot(emote, &SignalRecorderRef1<std::string>::fired));
    
    ctl.broadcastSightFrom("_vase_1", imag);

    while (!emote.fireCount()) {
        Eris::PollDefault::poll();
    }
    
    assert(emote.lastArg0() == "I wish I was a teapot");   
}

int runTests(Controller& ctl)
{
    try {
        testLogin();
        testBadLogin();
        testBadLogin2();
        testAccCreate();
        testLogout();
        testAccountCharacters();
        testBadTake();
        testServerInfo();
        testDuplicateCreate();
        
        testServerSocketShutdown(ctl);
    
        testCharActivate(ctl);
        testCharCreate();
        testBadCreate();
        
        testAppearance(ctl);
        testSet(ctl);
        testTalk(ctl);
        testSeeMove(ctl);
        testSeeMoveWithUpdates(ctl);
        testLocationChange(ctl);
        testSightAction(ctl);
        testSightDelete(ctl);
        testSoundAction(ctl);
        testMovement(ctl); 
        
        testCharacterInitialVis(ctl);
        testLookQueue(ctl);
        testEmote(ctl);
        testEntityCreation(ctl);
        testUnseen(ctl);
        
        testWield(ctl);
        testDeleteWielded(ctl);
        testHear(ctl);
        testTasks(ctl);
        
        testSimpleCalendar(ctl);
    }
    catch (TestFailure& tfexp)
    {
        cout << "tests failed: " << tfexp.what() << endl;
        return EXIT_FAILURE;
    }
    catch (std::exception& except)
    {
        cout << "caught general exception: " << except.what() << endl;
        return EXIT_FAILURE;
    }

    cout << "all tests passed" << endl;
    return EXIT_SUCCESS;
}

int forkAndRunBoth(int argc, char* argv[])
{
    pid_t childPid = fork();
    if (childPid == 0)
    {
        sleep(1);
        Controller ctl("/tmp/eris-test");
        
        Eris::setLogLevel(LOG_DEBUG);
        Eris::Logged.connect(SigC::slot(&erisLog));
    
        return runTests(ctl);
    } else {
        Eris::setLogLevel(LOG_DEBUG);
        Eris::Logged.connect(SigC::slot(&erisLog));
        
        StubServer stub(7450);
        return stub.run(childPid);
    }
}

int runServer(int argc, char* argv[])
{    
    Eris::setLogLevel(LOG_DEBUG);
    Eris::Logged.connect(SigC::slot(&erisLog));
    
    StubServer stub(7450);
    return stub.run(0);

}

int runClient(int argc, char* argv[])
{    
    Controller ctl("/tmp/eris-test");
        
    Eris::setLogLevel(LOG_DEBUG);
    Eris::Logged.connect(SigC::slot(&erisLog));

    return runTests(ctl);
}

int main(int argc, char **argv)
{
    if (argc > 1) {
        if (!strcmp(argv[1], "--server")) return runServer(argc, argv);
        if (!strcmp(argv[1], "--client")) return runClient(argc, argv);
        
        if (strcmp(argv[1], "--both")) {
            cerr << "unrecognized test mode: " << argv[1] << endl;
            return EXIT_FAILURE;
        }
    }

    return forkAndRunBoth(argc, argv);
}


syntax highlighted by Code2HTML, v. 0.9.1