#include "corax.h"

#include <iostream>

using std::cout;
using std::endl;

Corax* Corax::corax = 0;

Corax::Corax(QObject *parent):
    QObject(parent),
    started(false),
    server(new W::Server(W::String(u"Corax"), this)),
    logger(new W::Logger()),
    parentReporter(new W::ParentReporter()),
    attributes(new M::Attributes(W::Address({u"attributes"}))),
    commands(new U::Commands(W::Address{u"management"})),
    connector(0),
    dispatcher(new W::Dispatcher()),
    caches(),
    parsers(),
    players()
{
    if (corax != 0) 
    {
        throw SingletonError();
    }
    Corax::corax = this;
    
    connector = new U::Connector(dispatcher, server, commands);
    connector->addIgnoredNode(W::String(u"Lorgar"));
    connector->addIgnoredNode(W::String(u"Roboute"));
    
    connect(attributes, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&)));
    connect(commands, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&)));
    connect(connector, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&)));
    connect(connector, SIGNAL(nodeConnected(const W::String&)), SLOT(onNodeConnected(const W::String&)));
    connect(connector, SIGNAL(nodeDisconnected(const W::String&)), SLOT(onNodeDisconnected(const W::String&)));
    connect(server, SIGNAL(connectionCountChange(uint64_t)), SLOT(onConnectionCountChanged(uint64_t)));
    
    dispatcher->registerDefaultHandler(parentReporter);
    dispatcher->registerDefaultHandler(logger);
    
    attributes->addAttribute(W::String(u"connectionsCount"), new M::String(W::String(u"0"), W::Address({u"attributes", u"connectionCount"})));
    attributes->addAttribute(W::String(u"name"), new M::String(W::String(u"Corax"), W::Address({u"attributes", u"name"})));
    attributes->addAttribute(W::String(u"version"), new M::String(W::String(u"0.0.2"), W::Address({u"attributes", u"version"})));
    attributes->addAttribute(W::String(u"players"), new M::String(W::String(u"0"), W::Address({u"attributes", u"players"})));
    
    createCaches();
    createHandlers();
}

Corax::~Corax()
{
    std::map<W::String, Parser*>::const_iterator pbeg = parsers.begin();
    std::map<W::String, Parser*>::const_iterator pend = parsers.end();
    
    for (; pbeg != pend; ++pbeg) {
        delete pbeg->second;
    }
    
    std::map<W::String, ResourceCache*>::const_iterator beg = caches.begin();
    std::map<W::String, ResourceCache*>::const_iterator end = caches.end();
    
    for (; beg != end; ++beg) {
        delete beg->second;
    }
    
    std::map<W::Address, M::Player*>::const_iterator plit = players.begin();
    std::map<W::Address, M::Player*>::const_iterator plend = players.end();
    
    for (; plit != plend; ++plit) {
        delete plit->second;
    }
    
    delete connector;
    
    dispatcher->unregisterDefaultHandler(logger);
    
    delete commands;
    delete attributes;
    
    delete logger;
    delete dispatcher;
    
    Corax::corax = 0;
}

void Corax::onConnectionCountChanged(uint64_t count)
{
    attributes->setAttribute(W::String(u"connectionsCount"), new W::String(std::to_string(count)));
}

void Corax::start()
{
    if (started) {
        throw 3;
    }
    std::map<W::String, ResourceCache*>::iterator beg = caches.begin();
    std::map<W::String, ResourceCache*>::iterator end = caches.end();
    
    cout << "Starting corax..." << endl;
    server->listen(8080);
    
    cout << "Registering models..." << endl;
    attributes->getRegistered(connector);
    commands->getRegistered(connector);
    
    for (; beg != end; ++beg) {
        beg->second->getRegistered(connector);
    }
    
    std::map<W::Address, M::Player*>::const_iterator plit = players.begin();
    std::map<W::Address, M::Player*>::const_iterator plend = players.end();
    
    for (; plit != plend; ++plit) {
        plit->second->getRegistered(connector);;
    }
    
    cout << "Opening caches..." << endl;
    
    beg = caches.begin();
    for (; beg != end; ++beg) {
        beg->second->open();
    }
    
    commands->enableCommand(W::String(u"clearCache"), true);
    
    started = true;
    cout << "Corax is ready" << endl;
}

void Corax::stop()
{
    if (!started) {
        throw 3;
    }
    
    std::map<W::String, ResourceCache*>::iterator beg = caches.begin();
    std::map<W::String, ResourceCache*>::iterator end = caches.end();
    
    cout << "Stopping corax..." << endl;
    commands->getUnregistered();
    attributes->getUnregistered();
    
    for (; beg != end; ++beg) {
        beg->second->getUnregistered();
    }
    
    std::map<W::Address, M::Player*>::const_iterator plit = players.begin();
    std::map<W::Address, M::Player*>::const_iterator plend = players.end();
    
    for (; plit != plend; ++plit) {
        plit->second->getUnregistered();
    }
    
    server->stop();
    
    started = false;
    cout << "Corax is stopped" << endl;
}

void Corax::onModelServiceMessage(const QString& msg)
{
    cout << msg.toStdString() << endl;
}

void Corax::addCache(ResourceCache* cache)
{
    attributes->addAttribute(cache->name, new M::String(W::String(u"0"), W::Address({u"attributes", cache->name})));
    
    connect(cache, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&)));
    connect(cache, SIGNAL(countChange(uint64_t)), SLOT(onCacheCountChange(uint64_t)));
    
    parentReporter->registerParent(cache->getAddress(), cache->subscribeMember);
    
    caches.insert(std::make_pair(cache->name, cache));
}

void Corax::h_clearCache(const W::Event& ev)
{
    const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
    const W::String& name = static_cast<const W::String&>(vc.at(u"name"));
    
    cout << "received command to clear cache " << name.toString() << endl;
    
    std::map<W::String, ResourceCache*>::iterator itr = caches.find(name);
    if (itr == caches.end()) {
        cout << "cache " << name.toString() << " doesn't exist" << endl;
    } else {
        itr->second->clear();
    }
}

void Corax::h_parseDirectory(const W::Event& ev)
{
    const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
    const W::String& path = static_cast<const W::String&>(vc.at(u"path"));
    
    cout << "received command to parse directory " << path.toString() << endl;
    
    std::map<W::String, Parser*>::const_iterator itr = parsers.find(path);
    
    if (itr != parsers.end()) {
        cout << "directory " << path.toString() << " is already being parsed" << endl;
    } else {
        const W::Socket* socket = connector->getNodeSocket(W::String(u"Perturabo"));
        ResourceCache* music = caches.at(W::String(u"music"));
        ResourceCache* images = caches.at(W::String(u"images"));
        Parser* parser = new Parser(socket, dispatcher, music, images);
        parsers.insert(std::make_pair(path, parser));
        
        connect(parser, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&)));
        connect(parser, SIGNAL(done(const W::String&)), SLOT(onParserDone(const W::String&)));
        
        parser->parse(path);
    }
}

void Corax::createCaches()
{
    ResourceCache* music = new ResourceCache(W::String(u"music"));
    ResourceCache* images = new ResourceCache(W::String(u"images"));
    
    addCache(music);
    addCache(images);
}

void Corax::createHandlers()
{
    W::Handler* clearCache = W::Handler::create(W::Address({u"management", u"clearCache"}), this, &Corax::_h_clearCache);
    W::Vocabulary clearArgs;
    clearArgs.insert(u"name", W::Uint64(W::Object::string));
    commands->addCommand(W::String(u"clearCache"), clearCache, clearArgs);
    
    W::Handler* parseDirectory = W::Handler::create(W::Address({u"management", u"parseDirectory"}), this, &Corax::_h_parseDirectory);
    W::Vocabulary parseArgs;
    parseArgs.insert(u"path", W::Uint64(W::Object::string));
    commands->addCommand(W::String(u"parseDirectory"), parseDirectory, parseArgs);
    
    W::Handler* givePlayer = W::Handler::create(W::Address({u"management", u"givePlayer"}), this, &Corax::_h_givePlayer);
    commands->addCommand(W::String(u"givePlayer"), givePlayer, W::Vocabulary());
}

void Corax::onParserDone(const W::String& path)
{
    std::map<W::String, Parser*>::const_iterator itr = parsers.find(path);
    
    delete itr->second;
    parsers.erase(itr);
}

void Corax::onCacheCountChange(uint64_t count)
{
    ResourceCache* cache = static_cast<ResourceCache*>(sender());
    
    attributes->setAttribute(cache->name, W::String(std::to_string(count)));
}

void Corax::onNodeConnected(const W::String& name)
{
    cout << "connected node " << name.toString() << endl;
    if (name == u"Perturabo") {
        commands->enableCommand(W::String(u"parseDirectory"), true);
        commands->enableCommand(W::String(u"givePlayer"), true);
    }
}

void Corax::onNodeDisconnected(const W::String& name)
{
    cout << "disconnected node " << name.toString() << endl;
    if (name == u"Perturabo") {
        commands->enableCommand(W::String(u"parseDirectory"), false);
        commands->enableCommand(W::String(u"givePlayer"), false);
    }
}

void Corax::h_givePlayer(const W::Event& ev)
{
    W::String num(std::to_string(players.size()));
    W::Address addr{u"players", num};
    M::Player* pl = new M::Player(addr);
    connect(pl, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&)));
    players.insert(std::make_pair(addr, pl));
    
    attributes->setAttribute(W::String(u"players"), W::String(std::to_string(players.size())));
    pl->getRegistered(connector);           //it's a handler, so I assume corax is started here;
    
    W::Vocabulary* vc = new W::Vocabulary();
    vc->insert(u"address", addr);
    
    const W::Vocabulary& svc = static_cast<const W::Vocabulary&>(ev.getData());
    const W::Address& source = static_cast<const W::Address&>(svc.at(u"source"));
    uint64_t id = ev.getSenderId();
    vc->insert(u"source", W::Address{u"management"});       //TODO think about it, may be Corax should be a model?
    
    W::Event res(source + W::Address{u"getPlayer"}, vc);
    res.setSenderId(id);
    connector->getConnection(id)->send(res);
}