/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <fstream>
#include <iostream>
#include <map>
#include <string>

#include "fstream_mars_fix.h"

#include <XmlMagics.h>
#undef ABS
#include "Metview.h"
#include "MvPath.hpp"

#include <XmlTree.h>
#include <XmlNode.h>
#include <XmlReader.h>
#include <MagException.h>


using namespace magics;


std::string DirectoryFromPath(const std::string& path)
{
    // find the last slash

    std::string::size_type pos = path.find_last_of("/");

    if (pos != std::string::npos) {
        std::string dir = path.substr(0, pos);
        return dir;
    }

    else {
        // no slash, so this must just be a filename; therefore the directory is nothing

        return "";
    }
}


/********************************************************************************
    MagMLNodeVisitor: derived from the Magics++ XmlNodeVisitor, this is used
                      to parse the <drivers> tag so that we know whether the
                      user has specified their own outputs (in which case we
                      use them) or not (in which case we write our own PS file).
                      Although in theory we could just quickly scan the MagML
                      file for the <drivers> string, that would not detect
                      if it had been commented out.

********************************************************************************/

class MagMLNodeVisitor : public XmlNodeVisitor
{
public:
    MagMLNodeVisitor() :
        hasDriversNode(false) {}
    void interpret(const std::string&);
    void visit(const XmlNode&);
    bool hasDriversNode;

private:
};


void MagMLNodeVisitor::interpret(const std::string& path)
{
    XmlReader parser(false);
    XmlTree tree;

    try {
        parser.interpret(path, &tree);
        tree.visit(*this);
    }
    catch (MagicsException& e) {
        std::cout << e.what() << std::endl;
    }
}


// this function will be called for each node in the XML file

void MagMLNodeVisitor::visit(const XmlNode& node)
{
    if (node.name() == "drivers") {
        hasDriversNode = true;
    }

    node.visit(*this);
}


/********************************************************************************
    MagicsLogObserver: derived from the Magics MagicsObserver, this is 
                       created so that we can receive callbacks whenever
                       Magics logs any messages.

********************************************************************************/

class MagicsLogObserver : public magics::MagicsObserver
{
public:
    MagicsLogObserver()  = default;
    ~MagicsLogObserver() = default;


private:
    void warningMessage(const std::string& msg)
    {
        char msg_c[1024];
        strncpy(msg_c, msg.c_str(), 1023);
        marslog(LOG_WARN, msg_c);
    }
    void errorMessage(const std::string& msg)
    {
        char msg_c[1024];
        strncpy(msg_c, msg.c_str(), 1023);
        marslog(LOG_EROR, msg_c);
    }
    void infoMessage(const std::string& msg)
    {
        char msg_c[1024];
        strncpy(msg_c, msg.c_str(), 1023);
        marslog(LOG_INFO, msg_c);
    }
};


//
//_____________________________________________________________________

class MagMLService : public MvService
{
public:
    MagMLService();
    void serve(MvRequest&, MvRequest&);

protected:
    XmlMagics manager_;
    std::string magml_;

    // we will not need to do anything with this observer object - it will
    // automatically be registered when it is created; just creating it is
    // enough to get the callbacks.
    MagicsLogObserver magicsLogObserver_;
};

//_____________________________________________________________________

MagMLService::MagMLService() :
    MvService("MAGML")
{
    // this means that if the user re-executes the icon, it will be run again,
    // even if it is green.

    saveToPool(false);
}

static void substitute(std::string& xml, const std::string& var, const std::string& val)
{
    std::string name = "$" + var;
    size_t index     = xml.find(name);
    int last         = 0;

    while (index != std::string::npos) {
        xml.replace(index, name.length(), val);
        last = index + val.length();
        std::cout << "[" << name << "]"
                  << "\n";
        index = xml.find(name, last);
    }

    std::cout << "[" << xml << "]"
              << "\n";
}

void MagMLService::serve(MvRequest& in, MvRequest& out)
{
    std::cout << "--------------MagMLService::serve() in--------------" << std::endl;
    in.print();

    const char* magml = in("template");
    std::map<std::string, std::string> variables;

    if (!magml) {
        magml = in("PATH");
        if (!magml) {
            setError(1, "MagML-> No MagML template to interpret");
            return;
        }
    }


    // check whether the file can be opened

    if (!FileCanBeOpened(magml, "r")) {
        setError(1, "MagML-> Could not open MagML file '%s'", magml);
        return;
    }


    // parse the MagML file to see whether the user has specified a <drivers> tag
    // or not (see class definition of MagMLNodeVisitor, above, for rationale).

    MagMLNodeVisitor magml_visitor;
    magml_visitor.interpret(magml);


    std::string xml = marstmp();
    std::string ps  = (magml_visitor.hasDriversNode) ? "" : marstmp();

    if (!ps.empty()) {
        ParameterManager::set("output_fullname", ps);
        sendProgress("MagML-> Using temporary PostScript file: %s", ps.c_str());
    }
    else {
        // change the current directory to be the same as the MagML icon so that
        // any outputs with relative filenames will be relative to the icon

        std::string path = DirectoryFromPath(magml);
        chdir(path.c_str());
        sendProgress("MagML-> Using outputs defined in <drivers> tag - will not return result to Metview.");
        sendProgress("MagML-> If outputs have relative paths, they will be relative to: '%s'.", path.c_str());
    }

    try {
        std::ifstream from(magml);
        std::ofstream to(xml.c_str());
        if (!from) {
            return;
        }
        static std::string s;
        s = "";

        char c;
        while (from.get(c)) {
            s += c;
        }

        int i = 0;
        while (i < in.countParameters()) {
            const char* param = in.getParameter(i++);
            const char* value = in(param);

            substitute(s, param, value);
        }

        to << s;

        from.close();
        to.close();


        // WebInterpretor::magml(xml);
        manager_.execute(xml);
    }
    catch (exception e) {
        sendProgress("MagML-> %s", e.what());
    }


    // the Magics log messages are not broadcast until the next log event - therefore, the
    // last log message will not be broadcast. We fix that by flushing the message streams
    // - we only need to do one of them, and all will be flushed behind the scenes.

    MagLog::info().flush();


    if (!ps.empty()) {
        MvRequest data("PSFILE");
        data("PATH")      = ps.c_str();
        data("TEMPORARY") = 1;
        data.print();
        out = data;
    }
    std::cout << "--------------MagMLService::serve() out--------------" << std::endl;
    out.print();
}
//_____________________________________________________________________

int main(int argc, char** argv)
{
    MvApplication theApp(argc, argv);
    MagMLService tool;
    theApp.run();
}
