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

 Copyright 2018 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 <iostream>

#include <stdarg.h>
#include "Metview.h"
#include "MvException.h"

svcid* _id = nullptr;

static void test_build_request(MvRequest& mode, MvRequest& out);

//! Internal class [used by MvService::addModeService()]
class MvModeService : public MvService
{
    MvService* MainService;
    char* Param;
    //
    virtual void _call_serve(svcid*, request*);
    virtual void serve(MvRequest&, MvRequest&);

public:
    //! Constructor
    MvModeService(char* name, char* param, MvService* main);
};


MvService::MvService(const char* name) :
    MvProtocol(name),
    HasModes(false),
    saveToPool_(true)
{
    // Limit to one request at a time (we fork to handle more)
    MvApplication::setMaximum(1);
    mvSetMarslogLevel();  //-- if "quiet log"
}

MvModeService::MvModeService(char* name, char* param, MvService* main) :
    MvService(name)
{
    MainService = main;
    Param = strcache(param);
}

void MvModeService::serve(MvRequest& in, MvRequest& out)
{
    // Get original request

    MvRequest clean("CLEAN");
    MvRequest mode = getMode();

    if (mars.debug)
        mode.print();
    // add the new parameter to the previous one.

    MvRequest orig;
    mode.getValue(orig, Param);
    orig = orig + in;
    mode.setValue(Param, orig);

    if (mars.debug)
        mode.print();

    // Call the original service

    MainService->modeServe(mode, out, Id);


    if (out) {
        out = clean + out;
        test_build_request(mode, out);
        out("_MODE") = mode;
    }
}
static void override(MvRequest& r, MvRequest& t)
{
    int i = 0;
    const char* val;

    t.unsetParam("_APPLICATION_OVERRIDES");
    while (val = r("_APPLICATION_OVERRIDES"), i) {
        t(val) = r(val);
        t("_APPLICATION_OVERRIDES") += val;
        i++;
    }
}
static request* getVisdefs(MvRequest& in, MvRequest& ref)
{
    request* visdefs = nullptr;
    while (ref) {
        Cached ref_verb(ref.getVerb());
        Cached in_verb(in.getVerb());
        if (in_verb == ref_verb) {
            Cached id1 = (const char*)in("_ID");
            Cached id2 = (const char*)ref("_ID");
            if (id1 == id2) {
                visdefs = ref("_VISDEFS");
                break;
            }
        }
        ref.advance();
    }
    ref.rewind();
    return visdefs;
}

static void test_build_request(MvRequest& mode, MvRequest& out)
{
    MvRequest new_out;
    MvRequest visdefs;
    MvRequest visdef;

    while (out) {
        if (visdefs) {
            Cached visdef_verb(visdefs.getVerb());
            Cached out_verb(out.getVerb());

            if (visdef_verb == out_verb) {
                visdef = visdefs.justOneRequest();
                override(out, visdef);
                new_out = new_out + visdef;
                visdefs.advance();
            }
            else
                new_out = new_out + visdefs;
        }
        else {
            visdefs = getVisdefs(out, mode);
            new_out = new_out + out.justOneRequest();
        }
        out.advance();
    }

    out = new_out;
}

static void link_objects(request* r)
{
    pool_link_objects(MvApplication::getService(), r);
}

static void _out(int lvl, const char* msg)
{
    if (_id) {
        if (lvl == LOG_EROR || lvl == LOG_EXIT)
            set_svc_msg(_id, "%s", msg);
        else
            send_progress(_id, msg, nullptr);
    }
}

// Sends a special progress message back to Desktop to indicate that the
// GUI app started up. Desktop then turns the icon green. Otherwise the icon would stay
// orange and all other actions would be blocked on it until the GUI finishes!!
// Ideally we should send a reply back to Desktop but this CAN ONLY BE SENT when
// the forked process (the GUI) has finished!!!
void MvService::acknowledgeGuiStartup()
{
    MvRequest a("GUI_STARTED");
    send_progress(_id, nullptr, a);
}

void MvService::addModeService(char* name, char* param)
{
    HasModes = true;
    new MvModeService(name, param, this);
}

void MvModeService::_call_serve(svcid* id, request* r)
{
    int forked = fork_service(id);
    if (forked > 0)
        return;

    if (forked == -1 && mars.nofork == false) {
        send_later(id);
        return;
    }

    std::cout << "CALLED " << iconClass();

    Id = _id = id;
    mars.outproc = _out;

    MvRequest in(r);
    MvRequest out;

    Error = 0;

    serve(in, out);

    if (mars.debug)
        out.print();
    svc* id_s = id->s;  // send_reply will free id so keep a ptr to id->s
    send_reply(id, out);

    if (forked != -1) {
        destroy_service(id_s);
        marsexit(0);
    }

    _id = nullptr;
    mars.outproc = nullptr;
}

MvRequest MvService::buildMode(MvRequest& in)
{
    return in;
}

void MvService::_call_serve(svcid* id, request* r)
{
    int forked = fork_service(id);
    if (forked > 0)
        return;

    if (forked == -1 && mars.nofork == false) {
        send_later(id);
        return;
    }

    Id = _id = id;
    mars.outproc = _out;

    request* u;

    const char* name = iconName();

    if (saveToPool_) {
        if ((u = pool_fetch(MvApplication::getService(), name, r->name))) {
            svc* id_s = id->s;  // send_reply will free id so keep a ptr to id->s
            send_reply(id, u);
            free_all_requests(u);
            if (forked != -1) {
                destroy_service(id_s);
                marsexit(0);
            }
            mars.outproc = nullptr;
            return;
        }
    }

    MvRequest in(r);
    MvRequest out;

    // if the request contains a directive to change the current working
    // directory, then do so
    const char* cwd = in("_CWD");
    if (cwd) {
        int cdret = chdir(cwd);
        if (cdret)  // may not be important in most cases
            std::cout << "WARNING: Could not cd to " << cwd << std::endl;
        in.unsetParam("_CWD");  // for efficiency
    }

    // Call service
    // Catch any error sent by MvException.
    Error = 0;
    try {
        serve(in, out);
    }
    catch (MvException& e) {
        setError(1, e.what());
        // marslog(LOG_EROR,"%s",e.what());
        sendProgress(e.what());
    }
    catch (std::exception& e) {
        setError(1, e.what());
        sendProgress(e.what());
    }
    catch (...) {
        setError(1, "Something failed: caught unspecified C++ exception...");
        sendProgress("serve function failed (a C++ exception cought)");
    }

    // Handle error messages.
    // Update the output request to be sent forward. The error messages
    // may come from MvException or setError routines.
    if (Error != 0) {
        const char* verb = out.getVerb();
        if (!verb || strcmp(out.getVerb(), "ERROR") != 0)
            out.setVerb("ERROR");

        if (!(const char*)out("MESSAGE"))
            out("MESSAGE") = (const char*)getMessage(0);
    }

    if (Id == nullptr)  // it was redispatched
    {
        if (forked != -1) {
            marsexit(0);
        }
        //_id          = nullptr;
        // mars.outproc = nullptr;
        // return;
    }


    if (HasModes && out) {
        MvRequest clean("CLEAN");
        out = clean + out;
        out.setValue("_MODE", buildMode(in));
    }

    if (Error == 0 && saveToPool_)
        pool_store(MvApplication::getService(), name, r->name, out);

    // Look for related objects
    if (saveToPool_)
        link_objects(r);


    if (mars.debug)
        out.print();

    _id = nullptr;
    mars.outproc = nullptr;

    svc* id_s = id->s;  // send_reply will free id so keep a ptr to id->s
    send_reply(id, out);

    if (forked != -1) {
        destroy_service(id_s);
        marsexit(0);
    }
}


err MvService::callService(char* name, MvRequest& in, MvRequest& out)
{
    err e;
    MvRequest reply(wait_service(Id->s, name, in, &e), false);
    out = reply;
    return e;
}


void MvService::linkTo(char* name)
{
    pool_link(Id->s, iconName(), name);
}

void MvService::modeServe(MvRequest& in, MvRequest& out, svcid* id)
{
    svcid* _id = Id;
    Id = id;

    try {
        serve(in, out);
    }
    catch (MvException& e) {
        std::cout << "Oops, caught MvException: " << e.what() << std::endl;
        marslog(LOG_EROR, "%s", e.what());
        sendProgress(e.what());
    }
    catch (...) {
        std::cout << "Something failed: caught unspecified C++ exception..." << std::endl;
        sendProgress("serve function failed (a C++ exception cought)");
    }
    Id = _id;
}

MvServiceFactory* MvServiceFactory::head_ = nullptr;

MvServiceFactory::MvServiceFactory() :
    next_(head_)
{
    head_ = this;
}

void MvServiceFactory::installServices()
{
    MvServiceFactory* x = head_;
    while (x) {
        x->installService();
        x = x->next_;
    }
}
