/***************************** 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.
//

#ifndef MagPlusService_H
#define MagPlusService_H

#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 string& name, int index = 0) :
        request_(request),
        name_(name),
        index_(index),
        count_(-1) {}
    virtual ~MvMagParam() {}

    virtual operator double() const { return (double)request_(name_.c_str(), index_); }
    operator string() const
    {
        const char* val = request_(name_.c_str(), index_);
        return (val) ? string(val) : string("");
    }
    operator int() const { return (int)request_(name_.c_str(), index_); }
    operator long int() const { return (long int)request_(name_.c_str(), index_); }
    MagParam& operator=(int i)
    {
        request_(name_.c_str(), index_) = i;
        count(-1);
        return *this;
    }
    MagParam& operator=(long int i)
    {
        request_(name_.c_str(), index_) = i;
        count(-1);
        return *this;
    }
    MagParam& operator=(string s)
    {
        request_(name_.c_str(), index_) = s.c_str();
        count(-1);
        return *this;
    }
    MagParam& operator=(double d)
    {
        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_;
    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 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;
        }
    }

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

    int count() { return len_; }

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

protected:
    double *values_;
    int len_;

};


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

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

    MvMagParam *ensureParamAdded(const 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() && string(subreq.getVerb()) == "VECTOR") {
                MvVectorMagParam * 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 string& name) const
    {
        MvMagParam *param =  ensureParamAdded(name);
        return *(param);
    }

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

    string getVerb() const { return request_.getVerb(); }

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

    int countValues(const string& name) const
    {
        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 string& name)
    {
        return *(new MvMagRequest(request_.getSubrequest(name.c_str())));
    }

    operator bool() const { return request_; }

    int countParameters() { return request_.countParameters(); }
    int countValues(const string& name) { return request_.countValues(name.c_str()); }
    string getParameter(int i) { return request_.getParameter(i); }
    void print() { return request_.print(); }
    void read(const string& file) { request_.read(file.c_str()); }
    
    MagRequest& justOneRequest()
    {
        MvRequest sub = request_.justOneRequest();
        return *(new MvMagRequest(sub));
    }

    MvRequest& getRequest() { return request_; }

protected:
    mutable MvRequest request_;
    mutable map<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() {}

    // Methods
    // Perform actions after receiving response from Magics
    virtual void endOfTask(MvTask*);
    //	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 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_;
};

#endif
