// Test connection with node.
bool ElasticSearch::isActive() {

    Json::Object root;

    try {
        _http.get(0, 0, &root);
    }
    catch(Exception& e){
        printf("get(0) failed in ElasticSearch::isActive(). Exception caught: %s\n", e.what());
        return false;
    }
    catch(std::exception& e){
        printf("get(0) failed in ElasticSearch::isActive(). std::exception caught: %s\n", e.what());
        return false;
    }
    catch(...){
        printf("get(0) failed in ElasticSearch::isActive().\n");
        return false;
    }

    if(root.empty())
        return false;

    if(!root.member("status") || root["status"].getInt() != 200){
        printf("Status is not 200. Cannot find Elasticsearch Node.\n");
        return false;
    }

    return true;
}
/// Index a document.
bool ElasticSearch::index(const std::string& index, const std::string& type, const std::string& id, const Json::Object& jData){

    if(_readOnly)
        return false;

    std::stringstream url;
    url << index << "/" << type << "/" << id;

    std::stringstream data;
    data << jData;

    Json::Object result;
    _http.put(url.str().c_str(), data.str().c_str(), &result);

    if(!result.member("created"))
        EXCEPTION("The index induces error.");

    if(result.getValue("created"))
        return true;

    std::cout << "endPoint: " << index << "/" << type << "/" << id << std::endl;
    std::cout << "jData" << jData.pretty() << std::endl;
    std::cout << "result" << result.pretty() << std::endl;

    EXCEPTION("The index returns ok: false.");
    return false;
}
// Test if document exists
bool ElasticSearch::exist(const std::string& index, const std::string& type, const std::string& id){
    std::stringstream url;
    url << index << "/" << type << "/" << id;

    Json::Object result;
    _http.get(url.str().c_str(), 0, &result);

    if(!result.member("found")){
        std::cout << result << std::endl;
        EXCEPTION("Database exception, field \"found\" must exist.");
    }

    return result.getValue("found");
}
// Request the document number of type T in index I.
long unsigned int ElasticSearch::getDocumentCount(const char* index, const char* type){
    std::ostringstream oss;
    oss << index << "/" << type << "/_count";
    Json::Object msg;
    _http.get(oss.str().c_str(),0,&msg);

    size_t pos = 0;
    if(msg.member("count"))
        pos = msg.getValue("count").getUnsignedInt();
    else
        printf("We did not find \"count\" member.\n");

    return pos;
}
/// Delete the document by index/type.
bool ElasticSearch::deleteAll(const char* index, const char* type){
    if(_readOnly)
        return false;

    std::ostringstream uri, data;
    uri << index << "/" << type << "/_query";
    data << "{\"query\":{\"match_all\": {}}}";
    Json::Object msg;
    _http.remove(uri.str().c_str(), data.str().c_str(), &msg);

    if(!msg.member("_indices") || !msg["_indices"].getObject().member(index) || !msg["_indices"].getObject()[index].getObject().member("_shards"))
        return false;

    if(!msg["_indices"].getObject()[index].getObject()["_shards"].getObject().member("failed"))
        return false;

    return (msg["_indices"].getObject()[index].getObject()["_shards"].getObject()["failed"].getInt() == 0);
}
// Update a document field.
bool ElasticSearch::update(const std::string& index, const std::string& type, const std::string& id, const std::string& key, const std::string& value){
    if(_readOnly)
        return false;

    std::stringstream url;
    url << index << "/" << type << "/" << id << "/_update";

    std::stringstream data;
    data << "{\"doc\":{\"" << key << "\":\""<< value << "\"}}";

    Json::Object result;
    _http.post(url.str().c_str(), data.str().c_str(), &result);

    if(!result.member("_version"))
        EXCEPTION("The update failed.");

    return true;
}
// Update doccument fields.
bool ElasticSearch::update(const std::string& index, const std::string& type, const std::string& id, const Json::Object& jData){
    if(_readOnly)
        return false;

    std::stringstream url;
    url << index << "/" << type << "/" << id << "/_update";

    std::stringstream data;
    data << "{\"doc\":" << jData;
    data << "}";

    Json::Object result;
    _http.post(url.str().c_str(), data.str().c_str(), &result);

    if(result.member("error"))
        EXCEPTION("The update doccument fields failed.");

    return true;
}
/// Search API of ES.
long ElasticSearch::search(const std::string& index, const std::string& type, const std::string& query, Json::Object& result, const int n_result=10){

    std::stringstream url;
    url << index << "/" << type << "/_search?size=" << n_result;

    _http.post(url.str().c_str(), query.c_str(), &result);

    if(!result.member("timed_out")){
        std::cout << url.str() << " -d " << query << std::endl;
        std::cout << "result: " << result << std::endl;
        EXCEPTION("Search failed.");
    }

    if(result.getValue("timed_out")){
        std::cout << "result: " << result << std::endl;
        EXCEPTION("Search timed out.");
    }

    return result.getValue("hits").getObject().getValue("total").getLong();
}
//retrive all documemnts of a class
int ElasticSearch::fullScan(const std::string& index, const std::string& type, const std::string& query, Json::Array& resultArray, int scrollSize) {

    // Get the scroll id
    std::stringstream scrollUrl;
    scrollUrl << index << "/" << type << "/_search?search_type=scan&scroll=10m&size=" << scrollSize;

    Json::Object scrollObject;
    _http.post(scrollUrl.str().c_str(),query.c_str(),&scrollObject);

    if(!scrollObject.member("hits"))
        EXCEPTION("Result corrupted, no member \"hits\".");

    if(!scrollObject.getValue("hits").getObject().member("total"))
        EXCEPTION("Result corrupted, no member \"total\" nested in \"hits\".");

    int total = scrollObject.getValue("hits").getObject().getValue("total").getInt();

    std::string scrollId = scrollObject["_scroll_id"].getString();
    int count = 0;
    while(count < total) {

        Json::Object result;
        _http.rawpost("_search/scroll?scroll=10m", scrollId.c_str(), &result);

        // Kepp the new scroll id we received to inject in the next iteration.
        scrollId = result["_scroll_id"].getString();

        for(const Json::Value& value : result["hits"].getObject()["hits"].getArray()){
            resultArray.addElement(value);
            ++count;
        }
    }

    if(count != total)
        EXCEPTION("Result corrupted, total is different from count.");

    return total;
}
/// Index a document with automatic id creation
std::string ElasticSearch::index(const std::string& index, const std::string& type, const Json::Object& jData){

    if(_readOnly)
        return "";

    std::stringstream url;
    url << index << "/" << type << "/";

    std::stringstream data;
    data << jData;

    Json::Object result;
    _http.post(url.str().c_str(), data.str().c_str(), &result);

    if(!result.member("created") || !result.getValue("created")){
        std::cout << "url: " << url.str() << std::endl;
        std::cout << "data: " << data.str() << std::endl;
        std::cout << "result: " << result.str() << std::endl;
        EXCEPTION("The index induces error.");
    }

    return result.getValue("_id").getString();
}