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

 Copyright 2020- 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 *************************************/

//
// .NAME: MagPlusService
//
// .AUTHOR:
//  Fernando Augusto Mitsuo Ii
//     ECMWF, December 2005
//
// .SUMMARY:
//  Describes the MagPlusService class, which handles
//  the communication with Magics++.
//
// .CLIENTS:
//
// .RESPONSABILITY:
//
// .COLLABORATORS:
//  MvServiceTask
//
// .ASCENDENT:
//  MvClient
//
// .DESCENDENT:
//  MagPlusInteractiveService, MagPlusBatchService
//
// .REFERENCES:
//  This class is based on the general facilities for
//  process communication used in METVIEW, especially the
//  MvClient and MvServiceTask facilities. Please refer
//  to these classes (or have a chat with Baudoin) for
//  more information.
//
//  This class implements a "singleton" as suggested
//  by Myers, "More Effective C+", page 130.
//

#pragma once

#include <MagPlus.h>

#include "Metview.h"
#include "MvTask.h"
#include "MagicsEvent.h"
#include "VectorUtils.h"

class MtInputEvent;
class MvMagParam : public MagParam
{
public:
    MvMagParam(MvRequest& request, const std::string& name, int index = 0) :
        request_(request),
        name_(name),
        index_(index),
        count_(-1) {}
    virtual ~MvMagParam() = default;

    operator double() const override { return (double)request_(name_.c_str(), index_); }
    operator string() const override
    {
        const char* val = request_(name_.c_str(), index_);
        return (val) ? std::string(val) : std::string("");
    }
    operator int() const override { return (int)request_(name_.c_str(), index_); }
    operator long int() const override { return (long int)request_(name_.c_str(), index_); }
    MagParam& operator=(int i) override
    {
        request_(name_.c_str(), index_) = i;
        count(-1);
        return *this;
    }
    MagParam& operator=(long int i) override
    {
        request_(name_.c_str(), index_) = i;
        count(-1);
        return *this;
    }
    MagParam& operator=(string s) override
    {
        request_(name_.c_str(), index_) = s.c_str();
        count(-1);
        return *this;
    }
    MagParam& operator=(double d) override
    {
        request_(name_.c_str(), index_) = d;
        count(-1);
        return *this;
    }
    void index(int index) { index_ = index; }
    void count(int count) { count_ = count; }
    int count() { return count_; }


protected:
    MvRequest& request_;
    std::string name_;
    int index_;
    int count_;
    // count_ : set to -1 to mean that we have not yet done the count, or to
    // denote that the request has been modified, meaning that we should redo the
    // count if asked for it
};


class MvVectorMagParam : public MvMagParam
{
public:
    MvVectorMagParam(MvRequest& req, const std::string& name, int index = 0) :
        MvMagParam(req, name, index)
    {
        values_ = nullptr;
        request* r = req;
        len_ = 0;
        if (read_vector_from_request(r, &values_, &len_) != MV_VECTOR_READ_OK) {
            // error reading the vector file (could be that it's temporary and has been deleted)
            len_ = 0;
        }
    }

    ~MvVectorMagParam() override
    {
        if (values_)
            free(values_);
        values_ = nullptr;
        len_ = 0;
    }

    int count() { return len_; }

    operator double() const override { return values_[index_]; }

protected:
    double* values_;
    int len_;
};


class MvMagRequest : public MagRequest
{
public:
    MvMagRequest(const MvRequest& request) :
        request_(request) {}
    ~MvMagRequest() override { freeParams(); }

    void freeParams()
    {
        for (auto const& p : parameters_) {
            if (p.second != nullptr) {
                delete p.second;
            }
        }
    }

    MvMagParam* ensureParamAdded(const std::string& name) const
    {
        auto param = parameters_.find(name);
        if (param == parameters_.end()) {
            bool addedVectorParam = false;
            int count = request_.countValues(name.c_str());
            // check if this is a vector of values - if so, we will
            // instantiate an MvVectorMagParam instead of an MvMagParam
            // in theory, we should be able to check whether there is a single value "#"
            // and this will indicate a subrequest, but this is not the case when we right-click
            // and visualise a Macro from the Desktop
            MvRequest subreq = request_(name.c_str(), 0);
            if (!subreq.isEmpty() && std::string(subreq.getVerb()) == "VECTOR") {
                auto* vecParam = new MvVectorMagParam(subreq, name);
                parameters_.insert(make_pair(name, vecParam));
                addedVectorParam = true;
                count = vecParam->count();
            }
            if (!addedVectorParam)
                parameters_.insert(make_pair(name, new MvMagParam(request_, name)));
            param = parameters_.find(name);
            param->second->count(count);
        }
        return param->second;
    }

    MagParam& operator()(const std::string& name) const override
    {
        MvMagParam* param = ensureParamAdded(name);
        return *(param);
    }

    void advance() override
    {
        request_.advance();
    }

    std::string getVerb() const override { return request_.getVerb(); }

    MagParam& operator()(const std::string& name, int i) const override
    {
        MvMagParam* param = ensureParamAdded(name);
        param->index(i);
        return *(param);
    }

    int countValues(const std::string& name) const override
    {
        MvMagParam* param = ensureParamAdded(name);
        int count = param->count();
        if (count == -1) {
            count = request_.countValues(name.c_str());
            param->count(count);
        }
        return count;
    }

    MagRequest& getSubRequest(const std::string& name) override
    {
        return *(new MvMagRequest(request_.getSubrequest(name.c_str())));
    }

    operator bool() const override { return request_; }

    int countParameters() override { return request_.countParameters(); }
    int countValues(const std::string& name) override { return request_.countValues(name.c_str()); }
    std::string getParameter(int i) override { return request_.getParameter(i); }
    void print() override { return request_.print(); }
    void read(const std::string& file) override { request_.read(file.c_str()); }

    MagRequest& justOneRequest() override
    {
        MvRequest sub = request_.justOneRequest();
        return *(new MvMagRequest(sub));
    }

    MvRequest& getRequest() { return request_; }

protected:
    mutable MvRequest request_;
    mutable std::map<std::string, MvMagParam*> parameters_;
};
// public magics::MagicsObserver {
class MagPlusService : public MvClient
{
public:
    // Access to singleton object
    static MagPlusService* magPlusServiceInstance_;
    static MagPlusService& Instance();
    static void Instance(MagPlusService*);

    // Destructor
    ~MagPlusService() override = default;

    // Methods
    // Perform actions after receiving response from Magics
    void endOfTask(MvTask*) override;
    //	virtual void endOfTask ( MvTask* ){}

    // Call MagPlus application to redraw the map on a specified device(s)
    //	void CallMagPlusRedraw( MvRequest& request );
    //	void CallMagPlusRedraw( MvRequest& request ){}

    // Call MagPlus application to draw the map on screen
    virtual void CallMagPlus(MvRequest&) = 0;
    //	void CallMagPlus( MvRequest& request ){}

    // Set request to be sent to Magics++
    void SetRequest(const MvRequest&);
    //	void SetRequest ( const MvRequest& ){}

    // Decode the geographical coordinates in json string to a MvRequest class
    void decode(MvRequest&, const std::string&);

    // Register to magics observer
    //	void RegisterObserver(MagicsObserver* observer);
    //	void RegisterObserver(MagicsObserver* observer){}

    // UnRegister to magics observer
    //	void UnRegisterObserver(MagicsObserver* observer);
    //	void UnRegisterObserver(MagicsObserver* observer){}

protected:
    // Constructors
    MagPlusService();  // no external access point

    magics::MagPlus* magplus_;
};
