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

// Methods for MapView.cc
//
//
// This is the exemplar object for the MapView  class
//
//

#include <Assertions.hpp>
#include <MvRequestUtil.hpp>

#include "MapView.h"
#include "ObjectList.h"
#include "GraphicsEngine.h"
#include "Page.h"
#include "PmContext.h"
#include "MagPlusService.h"
#include "MvDecoder.h"
#include "PlotMod.h"
#include "PlotModTask.h"
#include "MvLog.h"

class MapViewFactory : public PlotModViewFactory
{
    // Virtual Constructor - Builds a new MapView
    PlotModView* Build(Page& page,
                       const MvRequest& contextRequest,
                       const MvRequest& setupRequest) override
    {
        return new MapView(page, contextRequest, setupRequest);
    }

public:
    MapViewFactory() :
        PlotModViewFactory("MapView") {}
};

static MapViewFactory mapViewFactoryInstance;

MapView::MapView(Page& owner,
                 const MvRequest& viewRequest,
                 const MvRequest& setupRequest) :
    PlotModView(owner, viewRequest, setupRequest),
    PlotModService(this)
{
#if 0
	// Create a coastline request, if one does not exist
	MvRequest coastRequest = viewRequest_.getSubrequest ("COASTLINES");
	if ( !ObjectList::IsVisDefCoastlines(coastRequest.getVerb()) )
	{
		coastRequest = ObjectList::UserDefaultRequest ( "MCOAST" );
		viewRequest_ ( "COASTLINES" ) = coastRequest;
	}
#endif
}

string
MapView::Name()
{
    int id = Owner().Id();
    return (const char*)ObjectInfo::ObjectName(viewRequest_, "MapView", id);
}

// If a group of Visdefs and DataUnits were dropped,
// the Context organize them to ensure that all the
// VisDefs are always after each DataUnit
void MapView::Drop(PmContext& context)
{
    MvLog().dbg() << "MapView::Drop";
    MvIconDataBase& dataBase = Owner().IconDataBase();

    // Process the drop
    MvRequest dropRequest;
    dropRequest.copyFromCurrentTo(context.InRequest(), NEWPAGE.c_str());
    MvIconList duList;
    bool foundDU = false;
    while (dropRequest) {
        // VisDefs are processed in one single goal. This is because the
        // processing of a single or a group of visdefs is different. It
        // is assumed that visdefs always come after a dataunit, if it exists.
        MvRequest vdRequestList;
        if (this->RetrieveVisdefList(dropRequest, vdRequestList)) {
            Owner().InsertVisDef(vdRequestList, duList);

            // Clear dataUnitList variable to avoid a possible visdef in the next
            // iteraction to be associate to an old dataUnit.
            if (!duList.empty())
                duList.clear();

            if (!dropRequest)  // no more drops
                break;
        }

        MvRequest request = dropRequest.justOneRequest();
        Cached verb = request.getVerb();
        if (ObjectList::IsDataUnit(verb) == true) {
            MvIcon dataUnit = dataBase.InsertDataUnit(request, Owner().Id());
            duList.push_back(dataUnit);
            Owner().InitMatching();
            DecodeDataUnit(dataUnit);

            foundDU = true;
            dropRequest.advance();
            continue;
        }

        // Update view (if applicable)
        if (ObjectList::IsView(verb) == true)
            this->UpdateViewWithReq(request);

        else if (ObjectList::IsVisDefCoastlines(verb))
            Owner().ProcessCoastlines(request, PlotMod::Instance().CalledFromMacro(), foundDU);

        else if (ObjectList::IsService(verb, "visualise", true)) {
            // Call the service to process the request.
            // After CallService is finished, function PlotModService::endOfTask
            // should be called and this function should perform the drawing.
            Owner().HasDrawTask(false);
            CallService(request, context);

            // Avoid this drop to be send to visualization
            // The result of the service call is the one that will
            // be send to the visualization procedure later
            // Owner().NeedsRedrawing(false);
        }
        else if ((const char*)verb == PLOTSUPERPAGE) {
            context.AdvanceTo(PLOTSUPERPAGE);
            return;
        }
        else if ((const char*)verb == NEWPAGE) {
            context.AdvanceTo(NEWPAGE);
            return;
        }
        else if (verb == Cached("DRAWING_PRIORITY")) {
            Owner().SetDrawPriority(request);

            // Redraw this page
            if (request("_APPL") != Cached("macro")) {
                Owner().RedrawIfWindow();
            }
        }
        else if (ObjectList::IsWeatherSymbol(verb))
            PlotMod::Instance().addWeatherSymbol(request);
        else
            Owner().InsertCommonIcons(request);

        // This request is not a dataUnit. Clear dataUnitList variable
        // to avoid a possible visdef in the next iteraction to be
        // associate to an old dataUnit.
        if (!duList.empty())
            duList.clear();

        dropRequest.advance();
    }

    context.AdvanceToEnd();
}

MvIconList
MapView::InsertDataRequest(MvRequest& dropRequest)
{
    MvIconDataBase& dataBase = Owner().IconDataBase();

    MvIconList duList;
    while (dropRequest) {
        MvRequest request = dropRequest.justOneRequest();

        // Initialise the Matching
        Owner().InitMatching();

        // Insert dataUnit to the database
        MvIcon dataUnit = dataBase.InsertDataUnit(request);
        DecodeDataUnit(dataUnit);

        // Add dataUnit to the output list
        duList.push_back(dataUnit);

        dropRequest.advance();
    }

    return duList;
}

// Decode the data unit and send each field to the page for matching
void MapView::DecodeDataUnit(MvIcon& dataUnit)
{
    ensure(dataUnit.Id() > 0);

    int subpageId = 0;
    MvIconDataBase& dataBase = Owner().IconDataBase();

    // Build a new data decoder, which will provide information
    // about the data
    std::unique_ptr<Decoder> decoder(DecoderFactory::Make(dataUnit.Request()));
    ensure(decoder.get() != nullptr);

    // Inquire if this Page is empty
    bool empty = Owner().ChildHasData() ? false : true;

    // Read data headers (one by one)
    // retrieve the data offset and matching info
    // Pass this information to the page for matching
    //    int dimFlag = 0;  //FAMI20170210 maybe there is not need for this flag
    bool swap = false;
    while (decoder->ReadNextData()) {
        long offset = decoder->CurrentOffset();
        long nextDataOffset = offset;

        MatchingInfo dataInfo = decoder->CreateMatchingInfo();

        MvRequest iconRequest = dataInfo.Request();
        if (metview::IsParameterSet(iconRequest, "MY_COMPANION")) {
            if (decoder->ReadNextData()) {
                nextDataOffset = decoder->CurrentOffset();
                MatchingInfo nextDataInfo = decoder->CreateMatchingInfo();

                // Are they companions ?
                if (!matchingCriteria_.IsPair(dataInfo, nextDataInfo, swap) ||
                    !matchingCriteria_.Match(dataInfo, nextDataInfo)) {
                    // No, so plot them separately
                    //                    dimFlag = dimFlag | 1;
                    if (Owner().InsertDataUnit(dataUnit.Id(), offset, offset, dataInfo, subpageId) == false)
                        break;

                    if (Owner().InsertDataUnit(dataUnit.Id(), nextDataOffset, nextDataOffset, nextDataInfo, subpageId) == false)
                        break;
                }
                else {
                    // Yes, so plot them together.
                    // Insert dataunit using the correct order of the offsets
                    // (i.e. u/v not v/u). This is a Magics requirement.
                    //                    dimFlag = dimFlag | 2;
                    bool flag = false;
                    if (swap)
                        flag = Owner().InsertDataUnit(dataUnit.Id(), nextDataOffset, offset, dataInfo, subpageId);
                    else
                        flag = Owner().InsertDataUnit(dataUnit.Id(), offset, nextDataOffset, dataInfo, subpageId);

                    if (flag == false)
                        break;
                }
            }
            else  // An isolated field, who just happens to have a companion defined.
            {
                //                dimFlag = dimFlag | 1;
                if (Owner().InsertDataUnit(dataUnit.Id(), offset, nextDataOffset, dataInfo, subpageId) == false)
                    break;
            }
        }
        else {
            // It is an isolated field
            //            dimFlag = dimFlag | 1;
            if (Owner().InsertDataUnit(dataUnit.Id(), offset, nextDataOffset, dataInfo, subpageId) == false)
                break;
        }

        MvRequest decRequest = decoder->Request();

        // F TEMPORARY SOLUTION FOR VISUALISATION OF SATELLITE
        // F IMAGES PRODUCED AT INPE (use Cylindrical projection)
        // F		if ( (int)decRequest ("_ORIGCENTRE") ==  46 ) continue;
        // F		else {

        const char* repres = decRequest("REPRES");
        if (ObjectList::IsImage(repres)) {
            std::string myName = this->Name();
            if (myName != "Satellite") {
                MvRequest ownerReq = Owner().Request();

                // To change the view to Satellite, two requirements are needed:
                // 1. current View has to be a default one
                // 2. current View is empty (no data)
                MvRequest viewRequest = ownerReq.getSubrequest("VIEW");
                if (((int)viewRequest("_DEFAULT") == 1) && empty) {
                    this->ConvertToSatellite(decRequest);

                    // PlotMod service will be called again to decode this grib file
                    return;
                }
            }
        }
        // F		}
        empty = false;
    }

    // Some decoders need to update the data request
    MvRequest dataRequest;
    if (decoder->UpdateDataRequest(dataRequest)) {
        // update database and icon structure
        dataBase.UpdateIcon(DB_DATAUNIT, dataUnit.Id(), dataRequest);
        dataUnit.SaveRequest(dataRequest);
    }

    // Update icon request to indicate a vector drawing
    //    MvRequest req = dataUnit.Request();
    //    dataRequest("_NDIM_FLAG") = dimFlag;
    //    dataUnit.SaveRequest(dataRequest);
}

void MapView::ConvertToSatellite(MvRequest& decRequest)
{
    MvRequest newViewReq = ObjectList::CreateDefaultRequest("SATELLITEVIEW");

    newViewReq("SUBPAGE_MAP_SUB_SAT_LONGITUDE") = decRequest("_IMAGE_MAP_SUB_SAT_LONGITUDE");
    newViewReq("INPUT_IMAGE_COLUMNS") = decRequest("_IMAGE_MAP_COLUMNS");
    newViewReq("INPUT_IMAGE_ROWS") = decRequest("_IMAGE_MAP_ROWS");
    newViewReq("SUBPAGE_MAP_INITIAL_COLUMN") = decRequest("_IMAGE_MAP_INITIAL_COLUMN");
    newViewReq("SUBPAGE_MAP_INITIAL_ROW") = decRequest("_IMAGE_MAP_INITIAL_ROW");
    newViewReq("SUBPAGE_MAP_SUB_SAT_X") = decRequest("_IMAGE_MAP_SUB_SAT_X");
    newViewReq("SUBPAGE_MAP_SUB_SAT_Y") = decRequest("_IMAGE_MAP_SUB_SAT_Y");
    newViewReq("SUBPAGE_MAP_X_EARTH_DIAMETER") = decRequest("_IMAGE_MAP_X_EARTH_DIAMETER");
    newViewReq("SUBPAGE_MAP_Y_EARTH_DIAMETER") = decRequest("_IMAGE_MAP_Y_EARTH_DIAMETER");
    newViewReq("SUBPAGE_MAP_GRID_ORIENTATION") = decRequest("_IMAGE_MAP_GRID_ORIENTATION");
    newViewReq("SUBPAGE_MAP_CAMERA_ALTITUDE") = decRequest("_IMAGE_MAP_CAMERA_ALTITUDE");

    this->UpdateViewWithReq(newViewReq);
}

void MapView::UpdateViewWithReq(MvRequest& viewReq)
{
    // uPlot recognises that MapView and GeoView are basically the same thing
    if (ObjectList::IsGeographicalView(viewReq.getVerb())) {
        // Ensure coastlines exist
        MvRequest coastRequest = viewReq.getSubrequest("COASTLINES");
        if (!coastRequest) {
            coastRequest = ObjectList::UserDefaultRequest("MCOAST");
            viewReq("COASTLINES") = coastRequest;
        }

        // Update the Owner View. The Owner will also update the local viewRequest_
        // structure (the Owner has a pointer to it). There is no need to have
        // command: viewRequest_ = viewReq;
        Owner().UpdateView(viewReq);

        // Update Coastlines
        // The above function Owner().UpdateView(viewReq) is very generic
        // and is called by the other Views. But, this View has an embeded
        // Coastlines icon, which is not updated in the MvDataBase by the
        // above function.
        Owner().ProcessCoastlines(coastRequest, PlotMod::Instance().CalledFromMacro());

        // Redraw this page
        Owner().RedrawIfWindow();
        Owner().NotifyObservers();

        Owner().InitZoomStacks();
    }
    else {
        // Changing View is disabled at the moment
        MvLog().popup().err() << "Changing View (" << viewRequest_.getVerb() << " to " << viewReq.getVerb() << ") is currently disabled";
    }
}

#if 0
void
MapView::ReplaceArea ( const Location& coordinates, int izoom)
{
     // If the zoom level is 0 (original request) and the geographical area was
     // not given by the user then unset parameter AREA. The default geographical 
     // area will be computed by Magics.
     if ( izoom == 0 )
     {
          if ( (const char*)viewRequest_("_DEFAULT_AREA") )
          {
               viewRequest_.unsetParam("AREA");
               return;
          }
     }

	// Initialize the bounding box in geodetic coordinates
	viewRequest_ ( "AREA" ) = coordinates.Bottom();
	viewRequest_ ( "AREA" ) += coordinates.Left();
	viewRequest_ ( "AREA" ) += coordinates.Top();
	viewRequest_ ( "AREA" ) += coordinates.Right();
}
#endif

void MapView::DrawBackground()
{
    // Retrieve background request
    MvRequest backList;
    if (this->RetrieveBackground(backList) == 0)
        return;  // nothing to be plotted

    // Loop all requests
    GraphicsEngine& ge = Owner().GetGraphicsEngine();
    while (backList) {
        // Draw the layer info
        MvRequest req = backList.justOneRequest();
        std::string icon_name = (const char*)req("_NAME") ? mbasename((const char*)req("_NAME")) : "<coastlines>";
        Owner().DrawLayerInfo((int)req("_ID"), icon_name.c_str());

        // Ask the graphics engine to draw the coastlines
        ge.Draw(req, true);

        backList.advance();
    }
}

void MapView::DrawForeground()
{
    // Retrieve foreground request
    MvRequest foreList;
    if (this->RetrieveForeground(foreList) == 0)
        return;  // nothing to be plotted

    // Loop all requests
    GraphicsEngine& ge = Owner().GetGraphicsEngine();
    while (foreList) {
        // Draw the layer info
        MvRequest req = foreList.justOneRequest();
        std::string icon_name = (const char*)req("_NAME") ? mbasename((const char*)req("_NAME")) : "<coastlines>";
        Owner().DrawLayerInfo((int)req("_ID"), icon_name.c_str());

        // Ask the graphics engine to draw the coastlines
        ge.Draw(req, true);

        foreList.advance();
    }
}

// Describe the contents of the view
// for saving into a macro
void MapView::DescribeYourself(ObjectInfo& description)
{
    // Translate MAPVIEW to GEOVIEW by adding GEOVIEW parameters
    MvRequest tmpRequest = viewRequest_;
    if ((const char*)tmpRequest("AREA"))
        tmpRequest("MAP_AREA_DEFINITION") = "CORNERS";

    // Translate json zoom definition to a Magics request
    if ((const char*)tmpRequest("_ZOOM_DEFINITION")) {
        MvRequest req;
        std::string sjson = (const char*)tmpRequest("_ZOOM_DEFINITION");
        MagPlusService::Instance().decode(req, sjson);

        // in zoom mode we cannot have these params
        tmpRequest.unsetParam("AREA_MODE");
        tmpRequest.unsetParam("AREA_NAME");

        // Translate Magics request to a Metview request
        if ((const char*)req("subpage_map_projection"))
            tmpRequest("MAP_PROJECTION") = (const char*)req("subpage_map_projection");

        if ((const char*)req("subpage_map_area_definition"))
            tmpRequest("MAP_AREA_DEFINITION") = (const char*)req("subpage_map_area_definition");

        tmpRequest("AREA") = req("subpage_lower_left_latitude");
        tmpRequest("AREA") += req("subpage_lower_left_longitude");
        tmpRequest("AREA") += req("subpage_upper_right_latitude");
        tmpRequest("AREA") += req("subpage_upper_right_longitude");
    }

    // Convert my request to macro
    std::string defView = DEFAULTVIEW;
    std::transform(defView.begin(), defView.end(), defView.begin(), ::tolower);
    std::set<Cached> skipSet;
    description.ConvertRequestToMacro(tmpRequest, PUT_END, MacroName().c_str(), defView.c_str(), skipSet);
}

bool MapView::CallService(const MvRequest& req, PmContext& context)
{
    MvRequest appRequest = req;
    appRequest("_CONTEXT") = viewRequest_;

    // Find service name
    std::string service = ObjectList::FindService(appRequest.getVerb(), "visualise");

    // Call service
    if (service.size()) {
        (new PlotModTask(this, context, service.c_str(), appRequest))->run();
        return true;
    }
    else {
        MvLog().popup().err() << "uPlot MapView::CallService-> Service name not found";
        return false;
    }
}

int MapView::RetrieveBackground(MvRequest& backReq)
{
    // Get icons from the Database first.
    // If there is none then get icons from the View (embedded icon).
    // If there is none then create a default Coastlines.
    int nicons = this->RetrieveBackForeground("BACKGROUND", backReq);
    if (nicons)
        return nicons;  // icons retrieved from the Database

    // We can have a coastlines icon with foreground layer mode.
    // In this case RetrieveBackForeground() will not find it!!!!
    MvRequest fgReq;
    this->RetrieveBackForeground("FOREGROUND", fgReq);
    if (fgReq && !fgReq.isEmpty()) {
        const char* layerMode = (const char*)fgReq("MAP_LAYER_MODE");
        // The whole coastlines icon is rendered into the foreground
        if (layerMode && strcmp(layerMode, "FOREGROUND") == 0) {
            return 0;
        }
    }

    // Retrieve icon from the View
    bool iconDefault = false;
    MvRequest req = viewRequest_.getSubrequest("COASTLINES");
    if (!req) {
        // Create a default Coastlines
        req = ObjectList::CreateDefaultRequest(MCOAST);
        iconDefault = true;
    }

    // By default, Coastline will only be plotted on the foreground, unless
    // there is a sea/land shade to be drawn on the background.
    Owner().CoastlinesBackground(req, backReq);

    if (backReq.isEmpty()) {
        return 0;
    }

    // Create an icon with an ID
    if (backReq) {
        MvIcon icon(backReq, true);
        backReq = icon.Request();

        // Add icon to the DataBase
        int id = (iconDefault) ? Owner().FindSuperPage()->Id() : Owner().Id();
        MvIconDataBase& dataBase = Owner().IconDataBase();
        dataBase.InsertIcon(PRES_VISDEF_REL, id, icon);

        return 1;
    }

    return 0;
}

int MapView::RetrieveForeground(MvRequest& foreReq)
{
    // Get icons from the Database first.
    // If there is none then get icons from the View (embedded icon).
    // If there is none then create a default Coastlines.
    int nicons = this->RetrieveBackForeground("FOREGROUND", foreReq);
    if (nicons)
        return nicons;  // icons retrieved from the Database

    // We can have a coastlines icon with background layer mode.
    // In this case RetrieveBackForeground() will not find it!!!!
    MvRequest bgReq;
    this->RetrieveBackForeground("BACKGROUND", bgReq);
    if (bgReq && !bgReq.isEmpty()) {
        const char* layerMode = (const char*)bgReq("MAP_LAYER_MODE");
        // The whole coastlines icon is rendered into the background
        if (layerMode && strcmp(layerMode, "BACKGROUND") == 0) {
            return 0;
        }
    }

    // Retrieve icon from the View
    bool iconDefault = false;
    MvRequest req = viewRequest_.getSubrequest("COASTLINES");
    if (!req) {
        // Create a default Coastlines
        req = ObjectList::CreateDefaultRequest(MCOAST);
        iconDefault = true;
    }

    // Account for land-sea shading. If they are setted to ON then
    // they were previously drawn on the Background by default.
    Owner().CoastlinesForeground(req, foreReq);

    if (foreReq.isEmpty()) {
        return 0;
    }

    // Create an icon with an ID
    MvIcon icon(foreReq, true);
    foreReq = icon.Request();

    // Add icon to the DataBase
    int id = iconDefault ? Owner().FindSuperPage()->Id() : Owner().Id();
    MvIconDataBase& dataBase = Owner().IconDataBase();
    dataBase.InsertIcon(PRES_VISDEF_REL, id, icon);

    return 1;
}

int MapView::RetrieveBackForeground(const char* stype, MvRequest& req)
{
    // Get icons from the database (if any)
    req.clean();
    MvIconList iconList;
    bool usingDefault = Owner().DefaultVisDefList(MCOAST, iconList, 0, GETBYVISDEF);
    if (usingDefault)
        usingDefault = Owner().DefaultVisDefList(PCOAST, iconList, 0, GETBYVISDEF);

    if (usingDefault)
        return 0;

    // Save the requested type icons
    int ncount = 0;
    MvListCursor ii;
    for (ii = iconList.begin(); ii != iconList.end(); ii++) {
        MvRequest currentReq = (*ii).Request();
        if (ObjectList::IsVisDefCoastlines(currentReq.getVerb())) {
            if ((const char*)currentReq("_PLOTTING_ORDER") &&
                (string(stype) == (const char*)currentReq("_PLOTTING_ORDER"))) {
                req = req + currentReq;
                ncount++;
            }
        }
    }

    return ncount;
}

#if 0
void
MapView::DescribeSubrequest ( ObjectInfo& description, 
			      MvRequest& request,
			      const Cached& name,
			      const Cached& verb)
{
	Cached macroName = ObjectInfo::SpaceToUnderscore ( name );
	description.ConvertRequestToMacro ( request, PUT_END,macroName,verb );
}

void
MapView::SaveRequest ( const Cached& path, MvRequest& viewRequest )
{
	Cached fileName = MakeIconName ( path, "MapView" );
	
	// WARNING - this command should not be needed, but there is a "feature"
	// in GenApp that does not allow a complete definition to be overrriden
	// by a name
	viewRequest.unsetParam ("COASTLINES");
	
	viewRequest.save ( (const char*) fileName );

	// If icon, create description for Metview UI
	Cached iconFile = MakeIconDescriptionName( fileName );
  
	UtWriteIconDescriptionFile ( iconFile, "MAPVIEW" );
}

// Attach a psymb request, return the id for the it's visdef, and
// fill in the request with the visdef's request.
int MapView::CheckPSymb(MvRequest &symbRequest)
{
	MvIconDataBase&  dataBase = Owner().IconDataBase();
  
	dataBase.PresentableVisDefRelationRewind();

	bool found = false;
	MvIcon visDef;
	int visDefId;

	// Check if it's already attached.
	while ( dataBase.NextVisDefByPresentableId ( Owner().Id(), visDef ) )
	{
		MvRequest vdRequest = visDef.Request();
		if ( vdRequest.getVerb() == Cached("PSYMB") )
		{
			symbRequest = vdRequest;
			found = true;
			visDefId = visDef.Id();
		}
	} 

	// If not found, attach one from the viewRequest, or a default request.
	if ( ! found ) 
	{
		symbRequest = viewRequest_.getSubrequest ("SYMBOL");
		if ( symbRequest.getVerb() == Cached( "PSYMB" ) )
		{
			symbRequest = ObjectList::UserDefaultRequest ( "PSYMB" );
			viewRequest_ ( "SYMBOL" ) = symbRequest;
		}

		// Add the visdef to the database.
		MvIcon symbIcon(symbRequest);
		dataBase.InsertVisDef(symbIcon);
		//dataBase.PresentableVisDefRelation(Owner().Id(), symbIcon.Id() );
		visDefId = symbIcon.Id();
	}
	return visDefId;
}
#endif
