/*
 * (C) Copyright 1996-2016 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include "MagJSon.h"


using namespace magics;
using namespace json_spirit;
#include "XmlMagics.h"
#include "XmlTree.h"

#include "Timer.h"
MagJSon::MagJSon() {
    patchs_["drivers"]    = &MagJSon::drivers;
    patchs_["definition"] = &MagJSon::definitions;
}

void MagJSon::execute(const string& magml, const map<string, string>& params) {
    TempFile file;

    prepare(magml, params, file);
    parse(file.name());
}

void MagJSon::parse(const string& file) {
    MagLog::dev() << "parse-->" << file.c_str() << endl;

    ifstream is(file.c_str());
    json_spirit::Value value;
    json_spirit::read_or_throw(is, value);
    magics(value);
}

void MagJSon::interpret(const string& def) {
    MagLog::dev() << "interpret-->" << def << endl;
    istringstream is(def);

    json_spirit::Value value;
    json_spirit::read_or_throw(is, value);
    ASSERT(value.type() == obj_type);
    Object object = value.get_value<Object>();
    // buils the Magics XmlNode!
    build(*tree_.root(), "magics", object);
}

void ParamJSon::magics(const json_spirit::Value& value) {
    ASSERT(value.type() == obj_type);
    Object object = value.get_value<Object>();
    for (vector<Pair>::const_iterator entry = object.begin(); entry != object.end(); ++entry) {
        if (entry->value_.type() == str_type) {
            this->insert(make_pair(entry->name_, entry->value_.get_value<string>()));
        }
        if (entry->value_.type() == int_type) {
            string value = tostring(entry->value_.get_value<int>());
            this->insert(make_pair(entry->name_, value));
        }
        if (entry->value_.type() == real_type) {
            string value = tostring(entry->value_.get_value<double>());
            this->insert(make_pair(entry->name_, value));
        }
    }
}

ParamJSon::ParamJSon(const string& param) {
    if (param.empty())
        return;
    istringstream is(param);
    json_spirit::Value value;
    json_spirit::read_or_throw(is, value);
    magics(value);
}


void MagJSon::drivers(XmlNode& parent, const json_spirit::Value& value) {
    ASSERT(value.type() == array_type);
    XmlNode* drivers = new XmlNode("drivers");
    parent.push_back(drivers);
    Array all = value.get_value<Array>();

    for (Array::iterator entry = all.begin(); entry != all.end(); ++entry) {
        ASSERT(entry->type() == obj_type);
        Object driver = entry->get_value<Object>();
        map<string, string> attributes;
        for (vector<Pair>::const_iterator elt = driver.begin(); elt != driver.end(); ++elt) {
            ASSERT(elt->value_.type() == str_type);
            attributes.insert(make_pair(elt->name_, elt->value_.get_value<string>()));
        }
        map<string, string>::iterator format = attributes.find("format");
        ASSERT(format != attributes.end());
        drivers->push_back(tree_.newNode(format->second, attributes));
    }
}


void MagJSon::definitions(XmlNode& parent, const json_spirit::Value& value) {
    ASSERT(value.type() == array_type);

    XmlNode* definitions = new XmlNode("definition");
    tree_.definition(definitions);

    Array all = value.get_value<Array>();
    for (Array::iterator entry = all.begin(); entry != all.end(); ++entry) {
        ASSERT(entry->type() == obj_type);
        Object def = entry->get_value<Object>();
        map<string, string> attributes;
        for (vector<Pair>::const_iterator elt = def.begin(); elt != def.end(); ++elt) {
            ASSERT(elt->value_.type() == str_type);
            attributes.insert(make_pair(elt->name_, elt->value_.get_value<string>()));
        }
        map<string, string>::iterator type = attributes.find("class");
        ASSERT(type != attributes.end());
        definitions->push_back(tree_.newNode(type->second, attributes));
    }
}


void MagJSon::build(XmlNode& parent, const string& name, Object& object) {
    map<string, string> attributes;

    for (vector<Pair>::const_iterator entry = object.begin(); entry != object.end(); ++entry) {
        if (entry->value_.type() == str_type) {
            attributes.insert(make_pair(entry->name_, entry->value_.get_value<string>()));
        }
        if (entry->value_.type() == bool_type) {
            string value = entry->value_.get_value<bool>() ? "on" : "off";
            attributes.insert(make_pair(entry->name_, value));
        }
        if (entry->value_.type() == int_type) {
            string value = tostring(entry->value_.get_value<int>());
            attributes.insert(make_pair(entry->name_, value));
        }
        if (entry->value_.type() == real_type) {
            string value = tostring(entry->value_.get_value<double>());
            attributes.insert(make_pair(entry->name_, value));
        }
    }
    XmlNode* node = tree_.newNode(name, attributes);
    parent.push_back(node);
    for (vector<Pair>::const_iterator entry = object.begin(); entry != object.end(); ++entry) {
        // We can apply a patch ..
        map<string, Patch>::iterator patch = patchs_.find(entry->name_);
        if (patch != patchs_.end()) {
            ((this->*patch->second)(*node, entry->value_));
            continue;
        }

        if (entry->value_.type() == obj_type) {
            Object object = entry->value_.get_value<Object>();
            build(*node, entry->name_, object);
        }
        if (entry->value_.type() == array_type) {
            Array object = entry->value_.get_value<Array>();
            for (Array::iterator val = object.begin(); val != object.end(); ++val) {
                if (val->type() == obj_type) {
                    Object object = val->get_value<Object>();
                    build(*node, entry->name_, object);
                }
            }
        }
    }
}


void MagJSon::magics(const json_spirit::Value& value) {
    ASSERT(value.type() == obj_type);
    Object object = value.get_value<Object>();

    XmlMagics magics;
    // buils the Magics XmlNode!
    build(*tree_.root(), "magics", object);
    Timer timer("total", "execution");
    magics.execute(tree_);
}

bool ParamJSon::hasKey(const string& key) {
    return (find(key) != end());
}

string ParamJSon::get(const string& key, const string& val) {
    const_iterator value = find(key);
    return (value != end()) ? value->second : val;
}