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

#include <Eris/Avatar.h>
#include <Eris/Entity.h>
#include <Eris/Connection.h>
#include <Eris/Log.h>
#include <Eris/View.h>
#include <Eris/IGRouter.h>
#include <Eris/Account.h>
#include <Eris/Exceptions.h>
#include <Eris/TypeService.h>
#include <Eris/Operations.h>
#include <Eris/Response.h>
#include <Eris/DeleteLater.h>

#include <wfmath/atlasconv.h>
#include <sigc++/slot.h>

#include <Atlas/Objects/Operation.h>
#include <Atlas/Objects/Entity.h>
#include <Atlas/Objects/Anonymous.h>

using namespace Atlas::Objects::Operation;
using Atlas::Objects::Root;
using Atlas::Objects::Entity::Anonymous;
using WFMath::TimeStamp;
using namespace Atlas::Message;
using Atlas::Objects::smart_dynamic_cast;

namespace Eris
{

Avatar::Avatar(Account* pl, const std::string& entId) : 
    m_account(pl),
    m_entityId(entId),
    m_entity(NULL),
    m_stampAtLastOp(TimeStamp::now()),
    m_lastOpTime(0.0)
{
    m_view = new View(this);
    m_entityAppearanceCon = m_view->Appearance.connect(sigc::mem_fun(this, &Avatar::onEntityAppear));
    
    m_router = new IGRouter(this);

    m_view->getEntityFromServer("");
    m_view->getEntity(m_entityId);
}

Avatar::~Avatar()
{
    m_account->internalDeactivateCharacter(this);
    
    delete m_router;
    delete m_view;
}

void Avatar::deactivate()
{
    Logout l;
    Anonymous arg;
    arg->setId(m_entityId);
    l->setArgs1(arg);
    l->setSerialno(getNewSerialno());
    
    getConnection()->getResponder()->await(l->getSerialno(), this, &Avatar::logoutResponse);
    getConnection()->send(l);
}

#pragma mark -

void Avatar::drop(Entity* e, const WFMath::Point<3>& pos, const std::string& loc)
{
    if(e->getLocation() != m_entity)
	{
		error() << "Can't drop an Entity which is not held by the character";
		
		return;
	}

    Move moveOp;
    moveOp->setFrom(m_entityId);

    Anonymous what;
    what->setLoc(loc);
    Atlas::Message::Element apos(pos.toAtlas());
    what->setPosAsList(apos.asList());
    what->setId(e->getId());
    moveOp->setArgs1(what);

    getConnection()->send(moveOp);
}

void Avatar::drop(Entity* e, const WFMath::Vector<3>& offset)
{
    drop(e, m_entity->getPosition() + offset, m_entity->getLocation()->getId());
}

void Avatar::take(Entity* e)
{
    Move moveOp;
    moveOp->setFrom(m_entityId);

    Anonymous what;
    what->setLoc(m_entityId);
    
    std::vector<double> p(3, 0.0);
    what->setPos(p); // cyphesis is rejecting move ops with no pos set
    
    what->setId(e->getId());
    moveOp->setArgs1(what);

    getConnection()->send(moveOp);
}

void Avatar::touch(Entity* e)
{
    Touch touchOp;
    touchOp->setFrom(m_entityId);
    
    Anonymous what;
    what->setId(e->getId());
    touchOp->setArgs1(what);

    getConnection()->send(touchOp);
}

void Avatar::say(const std::string& msg)
{
    Talk t;

    Anonymous what;
    what->setAttr("say", msg);
    t->setArgs1(what);
    t->setFrom(m_entityId);
    
    getConnection()->send(t);
}

void Avatar::emote(const std::string &em)
{
    Imaginary im;
	
    Anonymous emote;
    emote->setId("emote");
    emote->setAttr("description", em);
    
    im->setArgs1(emote);
    im->setFrom(m_entityId);
    im->setSerialno(getNewSerialno());
	
    getConnection()->send(im);
}

void Avatar::moveToPoint(const WFMath::Point<3>& pos)
{
    Anonymous what;
    what->setLoc(m_entity->getLocation()->getId());
    what->setId(m_entityId);
    what->setAttr("pos", pos.toAtlas());
    
    Move moveOp;
    moveOp->setFrom(m_entityId);
    moveOp->setArgs1(what);

    getConnection()->send(moveOp);
}

void Avatar::moveInDirection(const WFMath::Vector<3>& vel)
{
    const double MIN_VELOCITY = 1e-3;
    
    Anonymous arg;
    //arg->setAttr("location", m_entity->getLocation()->getId());
    arg->setId(m_entityId);
    arg->setAttr("velocity", vel.toAtlas());

    WFMath::CoordType sqr_mag = vel.sqrMag();
    if(sqr_mag > MIN_VELOCITY) { // don't set orientation for zero velocity
        WFMath::Quaternion q;
        WFMath::CoordType z_squared = vel.z() * vel.z();
        WFMath::CoordType plane_sqr_mag = sqr_mag - z_squared;
        if(plane_sqr_mag < WFMATH_EPSILON * z_squared) {
            // it's on the z axis
            q.rotation(1, vel[2] > 0 ? -WFMath::Pi/2 : WFMath::Pi/2);
        } else {
            // rotate in the plane first
            q.rotation(2, atan2(vel[1], vel[0]));
            // then get the angle away from the plane
            q = WFMath::Quaternion(1, -asin(vel[2] / sqrt(plane_sqr_mag))) * q;
        }
	
        arg->setAttr("orientation", q.toAtlas());
    }

    Move moveOp;
    moveOp->setFrom(m_entityId);
    moveOp->setArgs1(arg);

    getConnection()->send(moveOp);
}

void Avatar::moveInDirection(const WFMath::Vector<3>& vel, const WFMath::Quaternion& orient)
{
    Anonymous arg;
   // arg->setAttr("location", m_entity->getLocation()->getId());
    arg->setAttr("velocity", vel.toAtlas());
    arg->setAttr("orientation", orient.toAtlas());
    arg->setId(m_entityId);

    Move moveOp;
    moveOp->setFrom(m_entityId);
    moveOp->setArgs1(arg);

    getConnection()->send(moveOp);
}

void Avatar::place(Entity* e, Entity* container, const WFMath::Point<3>& pos)
{
    Anonymous what;
    what->setLoc(container->getId());
    what->setAttr("pos", pos.toAtlas());
   // what->setVelocityAsList( .... zero ... );
    what->setId(e->getId());

    Move moveOp;
    moveOp->setFrom(m_entityId);
    moveOp->setArgs1(what);

    getConnection()->send(moveOp);
}

void Avatar::wield(Entity * entity)
{
	if(entity->getLocation() != m_entity)
	{
		error() << "Can't wield an Entity which is not located in the avatar.";
		
		return;
	}
	
	Anonymous arguments;
	arguments->setId(entity->getId());
	
	Wield wield;
	wield->setFrom(m_entityId);
	wield->setArgs1(arguments);
	
	getConnection()->send(wield);
}

void Avatar::useOn(Entity * entity, const WFMath::Point< 3 > & position, const std::string& opType)
{
    Anonymous arguments;
	
	arguments->setId(entity->getId());
	arguments->setObjtype("obj");
	if (position.isValid()) arguments->setAttr("pos", position.toAtlas());
    
    Use use;
	use->setFrom(m_entityId);
	
    
    if (opType.empty())
    {
        use->setArgs1(arguments);
    } else {
        RootOperation op;
        StringList prs;
        prs.push_back(opType);
        op->setParents(prs);
        op->setArgs1(arguments);
        op->setFrom(m_entityId);
        
        use->setArgs1(op);
    }
    
	getConnection()->send(use);
}

void Avatar::attack(Entity* entity)
{
    assert(entity);
    Attack attackOp;
    attackOp->setFrom(m_entityId);
    
    Anonymous what;
    what->setId(entity->getId());
    attackOp->setArgs1(what);

    getConnection()->send(attackOp);
}

void Avatar::useStop()
{
    Use use;
	use->setFrom(m_entityId);
    getConnection()->send(use);
}

#pragma mark -

void Avatar::onEntityAppear(Entity* ent)
{
    if (ent->getId() == m_entityId) {
        assert(m_entity == NULL);
        m_entity = ent;
        
        ent->ChildAdded.connect(sigc::mem_fun(this, &Avatar::onCharacterChildAdded));
        ent->ChildRemoved.connect(sigc::mem_fun(this, &Avatar::onCharacterChildRemoved));
        
        ent->observe("right_hand_wield", sigc::mem_fun(this, &Avatar::onCharacterWield));
        
        GotCharacterEntity.emit(ent);
        m_entityAppearanceCon.disconnect(); // stop listenting to View::Appearance
    }
}

void Avatar::onCharacterChildAdded(Entity* child)
{
    InvAdded.emit(child);
}

void Avatar::onCharacterChildRemoved(Entity* child)
{
    InvRemoved.emit(child);
}

void Avatar::onCharacterWield(const std::string& s, const Atlas::Message::Element& val)
{
    if (s != "right_hand_wield") {
        warning() << "got wield for strange slot";
        return;
    }
    
    if (!val.isString()) {
        warning() << "got malformed wield value";
        return;
    }
    
    m_wielded = EntityRef(m_view, val.asString());
}

Connection* Avatar::getConnection() const
{
    return m_account->getConnection();
}

double Avatar::getWorldTime()
{
    WFMath::TimeDiff deltaT = TimeStamp::now() - m_stampAtLastOp;
    return m_lastOpTime + (deltaT.milliseconds() / 1000.0);
}

void Avatar::updateWorldTime(double seconds)
{
    m_stampAtLastOp = TimeStamp::now();
    m_lastOpTime = seconds;
}

void Avatar::logoutResponse(const RootOperation& op)
{
    if (!op->instanceOf(INFO_NO))
        warning() << "received an avatar logout response that is not an INFO";
        
    const std::vector<Root>& args(op->getArgs());
    
    if (args.empty() || (args.front()->getClassNo() != LOGOUT_NO)) {
        warning() << "argument of avatar logout INFO is not a logout op";
        return;
    }
    
    RootOperation logout = smart_dynamic_cast<RootOperation>(args.front());
    const std::vector<Root>& args2(logout->getArgs());
    assert(!args2.empty());
    
    std::string charId = args2.front()->getId();
    debug() << "got logout for character " << charId;
    assert(charId == m_entityId);

    m_account->AvatarDeactivated.emit(this);
    deleteLater(this);
}


} // of namespace Eris


syntax highlighted by Code2HTML, v. 0.9.1