/***************************** 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 <cmath>
#include <MvRequest.h>
#include <Assertions.hpp>

#include "MagicsGraphicsEngine.h"
#include "Canvas.h"
#include "PlotModConst.h"
#include "Root.h"
#include "SuperPage.h"
#include "DrawingPriority.h"
#include "MagicsTranslator.h"
#include "ObjectList.h"
#include "PlotMod.h"

// ==================================================================
//
// class MagicsGEFactory
class MagicsGEFactory : public GraphicsEngineFactory
{
    GraphicsEngine* Build(Presentable& owner) override
    {
        return new MagicsGraphicsEngine(owner);
    }

public:
    MagicsGEFactory() :
        GraphicsEngineFactory("Magics") {}
};

// Instance of MagicsGE which is loaded in the "factory"
static MagicsGEFactory magicsGEFactoryInstance;

// ==================================================================
//
// class MagicsGraphicsEngine
MagicsGraphicsEngine::MagicsGraphicsEngine(Presentable& owner) :
    GraphicsEngine(owner)
{
}

MagicsGraphicsEngine::~MagicsGraphicsEngine() = default;

#if 0
PSCanvas * 
MagicsGraphicsEngine::MakePSCanvas ( Device&    device, 
				     const Point&     point,
				     const Rectangle& rect,
				     const PaperSize& canvasSize,
				     const PaperSize& fullPaperSize,
			             int   subpageId                ) const
{
	PSCanvas * psCanvas = new PSCanvas ( device, point, rect, 
					     canvasSize, fullPaperSize,
					     subpageId );

return psCanvas;
}

XCanvas * 
MagicsGraphicsEngine::MakeXCanvas  (  Device&      device,  
				     PresentableWidget* parent,
				     const Rectangle&   rect,
				     const PaperSize&   size,
				           int          subpageId ) const 
{
	XCanvas * xc =  new MagicsCanvas ( device, parent, rect, size, subpageId );
	ensure ( xc != 0 );

	return xc;
}
#endif

// U BEGIN
Canvas*
MagicsGraphicsEngine::MakeCanvas(Device& device,
                                 const Rectangle& rect,
                                 const PaperSize& size,
                                 int subpageId) const
{
    auto* xc = new Canvas(device, rect, size, subpageId);
    ensure(xc != nullptr);

    return xc;
}
// U END

//
//  Draw
//
//  INPUT - magicsRequest: the METVIEW Request which has been "translated"
//                         into a MAGICS request (see the MagicsTranslator class)
//
//  PURPOSE - Add request
void MagicsGraphicsEngine::Draw(const MvRequest& magicsRequest, bool translate)
{
    // Add a magicsRequest to the service Request
    // If requested, translate first METVIEW calls into MAGICS calls
    if (translate) {
        MagicsTranslator& mt = MagicsTranslator::Find(magicsRequest.getVerb());
        serviceRequest_ += mt.Execute(magicsRequest);
    }
    else
        serviceRequest_ += magicsRequest;
}

void MagicsGraphicsEngine::DrawDataVisDef(
    MvIcon& dataUnit,
    MvRequest& dataInfoRequest,
    MvIconList& visdefList,
    int nDimFlag)
{
    // Draw the dataunit
    MvListCursor vdCursor;
    vdCursor = visdefList.begin();
    MvIcon& visdef = *(vdCursor);
    this->DrawDataUnit(dataUnit, dataInfoRequest, visdef);

    // Check Visdefs. They can be:
    // a) single or multi-visdefs to be applied to the whole fieldset
    // b) multi-visdefs, each one(s) to be applied to specific fields in the fieldset,
    // e.g. MCONT to 1D fields, MWIND to 2d fields.
    MvRequest dataUnitReq = dataUnit.Request();
    MvRequest vdRequest;
    std::vector<std::string> vstr;
    ObjectList::VisDefNDimFlags(nDimFlag, vstr);

    // Define Visdefs
    // No info about data dimension. Assumes that all fields have the same
    // dimension; therefore, associate all visdefs to the fieldset.
    if (vstr.size() == 0) {
        // Translate METVIEW calls into MAGICS calls
        TranslateVisDefRequest(visdefList, dataUnitReq, vdRequest);
    }
    // Fieldset contains fields with same dimension
    else if (vstr.size() == 1) {
        MvIconList laux = visdefList;
        if (ObjectList::CheckValidVisDefList(dataUnitReq.getVerb(), laux, vstr[0].c_str())) {
            // Translate METVIEW calls into MAGICS calls
            TranslateVisDefRequest(laux, dataUnitReq, vdRequest);

            // Update visdef list if only a select visdefs were used
            if (visdefList.size() != laux.size())
                visdefList = laux;
        }
    }
    // Multi-dimensional fields, e.g. fieldset contains 1-D and 2-D fields
    else if (vstr.size() > 1) {
        vdRequest.setVerb("MULTI");

        // Get visdefs related to each dimension
        MvIconList vdList;
        const char* verb = dataUnitReq.getVerb();
        bool vdDefault = false;
        for (auto& i : vstr) {
            MvIconList laux = visdefList;
            const char* caux = i.c_str();

            // No valid visdef found in the list. Create a default visdef.
            if (!ObjectList::CheckValidVisDefList(verb, laux, caux)) {
                // Get the first default nD-visdef
                std::string lvd = (const char*)ObjectList::Find("dataunit", verb, caux);
                size_t pos = lvd.find('/');
                if (pos != std::string::npos)
                    lvd = lvd.substr(0, pos);

                // Build a default visdef request
                MvRequest vdRequest = ObjectList::CreateDefaultRequest(lvd.c_str());

                // Insert the visdef into the list
                MvIcon newVd(vdRequest);
                laux.clear();
                laux.push_back(newVd);
                vdDefault = true;
            }

            // Translate METVIEW calls into MAGICS calls
            MvRequest vdAux;
            TranslateVisDefRequest(laux, dataUnitReq, vdAux);
            vdRequest(caux) = vdAux;

            // Add used visdefs to the  list
            vdList.splice(vdList.end(), laux);
        }

        // Update visdef list if only a select visdefs were used or
        // a new default visdef was added
        if ((visdefList.size() != vdList.size()) || vdDefault)
            visdefList = vdList;
    }

    // Draw the visdefs
    MvRequest req("VISDEFS");
    req("ACTIONS") = vdRequest;
    this->Draw(req);
}

void MagicsGraphicsEngine::DrawDataUnit(MvIcon& dataUnit, MvRequest& dataInfoRequest, MvIcon& visDef)
{
    // Translate METVIEW calls into MAGICS calls
    // Find a metview to magics translator and execute it
    MvRequest vdRequest = visDef.Request();
    MvRequest duReq = dataUnit.Request();
    MagicsTranslator& mt = MagicsTranslator::Find(duReq.getVerb());

    MvRequest magicsDataRequest = mt.Execute(duReq, dataInfoRequest, vdRequest);

    // Draw the data unit (with the appropriate MAGICS request )
    this->Draw(magicsDataRequest);
}

void MagicsGraphicsEngine::DrawNewPage(MvRequest& viewRequest)
{
    // Add page Id
    // FAMI20161124 : it should set only _PRESENTABLE_ID. The _ID value
    // clashes with one of the icon's _ID value, but at this date, this
    // _ID for a PAGE/PRESENTABLE is used by Magics later. Magics should
    // use _PRESENTABLE_ID, instead of _ID. Update this code below when
    // Magics is updated.
    //   MvIcon temp = MvIcon(true);
    //   viewRequest("_ID") = temp.Id();
    viewRequest("_ID") = owner_.Id();
    viewRequest("_PRESENTABLE_ID") = owner_.Id();

    // Call the magics translator to translate METVIEW calls
    // into MAGICS calls
    MagicsTranslator& mt = MagicsTranslator::Find("PAGE");
    MvRequest pageRequest = mt.Execute(viewRequest);

    // Set Background colour
    if (strcmp(drawType_.c_str(), "BACKGROUND") == 0 ||
        strcmp(drawType_.c_str(), "DATA") == 0) {
        const char* colour = viewRequest("SUBPAGE_BACKGROUND_COLOUR");
        if (colour != nullptr)
            pageRequest("SUBPAGE_BACKGROUND_COLOUR") = colour;
    }

    // Add extra information to the View, if the View is available.
    // Otherwise, add it to the Page
    pageRequest.advance();
    if (!pageRequest)
        pageRequest.rewind();

    // Add Zoom information
    int zoomNumberOfLevels = 0, zoomCurrentLevel = 0;
    owner_.ZoomInfo(zoomNumberOfLevels, zoomCurrentLevel);
    pageRequest("ZOOM_NUMBER_OF_LEVELS") = zoomNumberOfLevels;
    pageRequest("ZOOM_CURRENT_LEVEL") = zoomCurrentLevel;

    // Draw the new page
    pageRequest.rewind();
    this->Draw(pageRequest);
}

#if 0
//
// Boundaries, Cities, Rivers and Grid are plotted in the Foreground.
void MagicsGraphicsEngine::DrawGeoLayersBack ( MvRequest& viewRequest )
{
   //bool draw = false;
   //string ON = "ON";

	// Get DrawingPriority from Page
  // DrawingPriority& tmpDrawingPriority = owner_.GetDrawPriority ();

   // Retrieve the GeoLayers request
   MvRequest geoRequest = viewRequest.getSubrequest ("GEO_LAYERS");

   // Account for land-sea shading
   MvRequest landReq = geoRequest.getSubrequest ("GEO_LAND_SHADE");
   if ( landReq )
      this->Draw ( landReq );

#if 0
   std::string onoff = (const char*)expRequest ( "MAP_COASTLINE_LAND_SHADE" );
   if ( onoff == ON )
	{
      int drawPrior = tmpDrawingPriority.GetPriority ( "COASTLINE_LAND_SHADE" );
		if ( drawPrior == 999 )
         coastRequest ("MAP_COASTLINE_LAND_SHADE") = "OFF";
		else
			draw = true;
	}

	// Check if Coastline Sea Shade is to be drawn on the background
   onoff = (const char*)expRequest ( "MAP_COASTLINE_SEA_SHADE" );
   if ( onoff == ON )
	{
      int drawPrior = tmpDrawingPriority.GetPriority ( "COASTLINE_SEA_SHADE" );
		if ( drawPrior == 999 )
         coastRequest ("MAP_COASTLINE_SEA_SHADE") = "OFF";
		else
			draw = true;
	}

	// Check if Coastline is to be drawn on the background
   onoff = (const char*)expRequest ( "MAP_COASTLINE" );
   if ( onoff == ON )
	{
      int drawPrior = tmpDrawingPriority.GetPriority ( "COASTLINE" );
		if ( drawPrior == 999 && draw == false )
         coastRequest ("MAP_COASTLINE") = "OFF";
		else
			draw = true;
	}

	// Draw Coastline on the background
	if ( draw )
	{
      // By default Boundaries, Cities and Rivers are plotted in the Foreground.
      coastRequest ("MAP_BOUNDARIES") = "OFF";
      coastRequest ("MAP_CITIES") = "OFF";
      coastRequest ("MAP_RIVERS") = "OFF";

		// Unset other drawings
      coastRequest ("MAP_GRID") = "OFF";
      coastRequest ("MAP_LABEL") = "OFF";
      this->Draw ( coastRequest );
	}
#endif
}
#endif

#if 0
void
MagicsGraphicsEngine::DrawCoastlines( Canvas& canvas, MvRequest& viewRequest )
{
	// Retrieve the coastlines Request 
	// Call the magics translator to translate METVIEW calls 
	// into MAGICS calls
	MagicsTranslator& mt = MagicsTranslator::Find ( "COASTLINES" );
	MvRequest coastRequest = mt.Execute ( viewRequest );

	// MAGICS does not know about drawing order
	coastRequest.unsetParam ( "MAP_COASTLINE_DRAWING_ORDER" );

	// Draw the coastlines
	this->Draw ( canvas, coastRequest );
}
#endif

void MagicsGraphicsEngine::StartPicture(const char* drawType)
{
#if 0
	MvRequest newRequest ( "MAGICS_ENGINE" );

	int id = canvas.PresentableId();
	newRequest ("SUBPAGE_ID" ) = id;

	if ( PlotMod::Instance().IsWindow() )
	{
		Presentable *subpage = Root::Instance().FindBranch ( id );
		if ( subpage )
		  newRequest("PICTURE_ID") = subpage->PictureId(drawType,true);
       	}
	else
		newRequest ("SUPERPAGE_INDEX") = iconId; // Index defined by SubPage

	// Add information
	SuperPage *sp = (SuperPage*)(owner_.FindSuperPage ( ));
	PaperSize psize = sp->GetMySize();
	double width  = psize.GetWidth();
	double height = psize.GetHeight();
	if ( strcmp(drawType,"DATA") == 0 )
		newRequest ( "PRINT_INDEX" ) = sp->IncrementPrintIndex();
	newRequest ( "TYPE" )                = drawType;
	newRequest ( "ICON_ID" )             = iconId;
	newRequest ( "SUPER_PAGE_X_LENGTH" ) = width;
	newRequest ( "SUPER_PAGE_Y_LENGTH" ) = height;
//	newRequest ( "REFRESH" )             = sp->Request()("_REFRESH");
     serviceRequest_ = newRequest;
#endif

    drawType_ = drawType;
    serviceRequest_.clean();
}

void MagicsGraphicsEngine::EndPicture()
{
    //     const char* type = serviceRequest_ ("TYPE");
    if (drawType_ == "DATA") {
#if 0
          // Build SUPERPAGE request for each plot, taking
          // information from the original MAGICS_ENGINE request.
          // Update page position at PAGE request.
          // Copy the remaining requests.
          while ( serviceRequest_ )
          {
               MvRequest req1 =  serviceRequest_.justOneRequest();
               if ( strcmp((const char*)req1.getVerb(),"MAGICS_ENGINE") == 0 )
               {
                    // set verb to SUPERPAGE and send the request to the Superpage
                    req1.setVerb("SUPERPAGE");
                    Presentable* sp = owner_.FindSuperPage();
                    SuperPage *sp1 = (SuperPage*)(owner_.FindSuperPage ( ));
                    superpageRequest_ += req1;
               }
               else
                    fullRequest_ += req1;

               serviceRequest_.advance();
          }
#endif
        fullRequest_ += serviceRequest_;
    }
}

void MagicsGraphicsEngine::DrawProlog()
{
    // Initialize requests
    fullRequest_.clean();
}

void MagicsGraphicsEngine::DrawPageHeader(Canvas& canvas)
{
    MvRequest newRequest("MAGICS_ENGINE");
    newRequest("SUBPAGE_ID") = canvas.PresentableId();
    newRequest("SUPERPAGE_INDEX") = owner_.Parent()->PaperPageIndex();
    newRequest("TYPE") = "PAGEHEADER";

    MvRequest printerRequest = canvas.PrinterRequest();
    newRequest("PRINTER") = printerRequest;
    serviceRequest_ = newRequest;

    this->EndPicture();
}

#if 0
void
MagicsGraphicsEngine::DrawTrailer ( Canvas& canvas ) //CHECK IF THIS IS STILL NEEDED
{
        // Call MAGICS to process the request
        MagPlusService::Instance().SetRequest ( fullRequest_ );

#if 0  // D
	MvRequest newRequest ( "MAGICS_ENGINE" );

	newRequest ("SUBPAGE_ID")      = canvas.PresentableId ();
	newRequest ("SUPERPAGE_INDEX") = owner_.MyParent()->PaperPageIndex();
	newRequest ("TYPE")            = "TRAILER";

	MvRequest printerRequest = canvas.PrinterRequest ();
	newRequest ("PRINTER") = printerRequest;	
	serviceRequest_ = newRequest;

	this->EndPicture ();
#endif
}
#endif

void MagicsGraphicsEngine::DrawTrailer(MvRequest& fullReq)
{
    fullReq += fullRequest_;
}

double
MagicsGraphicsEngine::ComputeAspectRatio(Canvas& canvas, MvRequest& req)
{
    double spwidth = 0., spheight = 0.;

    // Retrieve canvas size
    double ar = 1.;
    double cwidth = canvas.GetWidth();
    double cheight = canvas.GetHeight();
    if (cwidth == 0. || cheight == 0.)
        return ar;

    // Retrieve superpage size. If SuperPage does not exist,
    // it is because request came from Assist buttom in the
    // Edit Window
    Presentable* pr = Root::Instance().FindBranch(canvas.PresentableId());
    if (pr) {
        Presentable* sp = pr->FindSuperPage();
        spwidth = sp->Request()("CUSTOM_WIDTH");
        spheight = sp->Request()("CUSTOM_HEIGHT");
    }
    else {
        spwidth = req("_WIDTH");
        spheight = req("_HEIGHT");
    }
    if (spwidth == 0. || spheight == 0.)
        return ar;

    // Compute aspect ratio
    ar = (cwidth > cheight) ? (cheight / spheight) : (cwidth / spwidth);

    return sqrt(ar);
}

void MagicsGraphicsEngine::TranslateVisDefRequest(MvIconList& iList, MvRequest& du, MvRequest& req)
{
    // Prepare the request to be drawn
    // Translate METVIEW calls into MAGICS calls
    // For each icon, find a metview to magics translator and execute it
    MvListCursor iCursor;
    for (iCursor = iList.begin(); iCursor != iList.end(); ++iCursor) {
        MvRequest idef = (*(iCursor)).Request();
        MagicsTranslator& mt2 = MagicsTranslator::Find(idef.getVerb());
        req += mt2.Execute(idef, du);
    }
}
