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

#include <Assertions.hpp>

#include "CommonXSectView.h"
#include "DataObject.h"
#include "MvDecoder.h"
#include "ObjectList.h"
#include "PlotMod.h"
#include "PlotModTask.h"
#include "PmContext.h"
#include "SubPage.h"
#include "MvRequestUtil.hpp"
#include "MvLog.h"

CommonXSectView::CommonXSectView(Page& owner,
                                 const MvRequest& viewRequest,
                                 const MvRequest& setupRequest) :
    PlotModView(owner, viewRequest, setupRequest),
    PlotModService(setupRequest("service"), this),
    appViewReq_(viewRequest)
{
    // Get min/max values
    if ((const char*)viewRequest_("BOTTOM_LEVEL") && (const char*)viewRequest_("TOP_LEVEL")) {
        yMin_ = viewRequest_("BOTTOM_LEVEL");
        yMax_ = viewRequest_("TOP_LEVEL");
    }
    else {
        // Expand request
        MvRequest expReq = ObjectList::ExpandRequest(viewRequest_, EXPAND_DEFAULTS);
        yMin_ = expReq("BOTTOM_LEVEL");
        yMax_ = expReq("TOP_LEVEL");
    }
}

std::string CommonXSectView::Name()
{
    char tmp[32];
    sprintf(tmp, "CommonXSectView%d", Owner().Id());

    return {tmp};
}

// Synchronous call service. Calls a service and wait its return.
bool CommonXSectView::callServiceInternal(const MvRequest& dataReq, PmContext& context, MvRequest& replyReq)
{
    if (dataReq.getVerb() != GRIB && dataReq.getVerb() != NETCDF && dataReq.getVerb() != BUFR)
        return false;

    // Build a request to send to the Service.
    // The _CONTEXT subrequest is an important information to the Service and
    // needs to be attached to the main request. To avoid duplication of messages
    // remove the _CONTEXT subrequest from the DATA request, if it exists.
    MvRequest appRequest(ApplicationName().c_str());
    if ((const char*)dataReq("_CONTEXT")) {
        MvRequest auxReq(dataReq.getVerb());
        metview::CopyAndRemoveParameters(dataReq, auxReq, "_CONTEXT");
        appRequest("DATA") = auxReq;
    }
    else
        appRequest("DATA") = dataReq;

    appRequest("_CONTEXT") = viewRequest_;
    appRequest("_ACTION") = (const char*)dataReq("_ACTION") ? (const char*)dataReq("_ACTION") : "prepare";
    appRequest("_VERB") = (const char*)dataReq("_VERB") ? (const char*)dataReq("_VERB") : "";
    appRequest("_CLASS") = (const char*)dataReq("_CLASS") ? (const char*)dataReq("_CLASS") : "";

    //( new PlotModTask (this, context, serviceName_.c_str(), appRequest ) )->run();
    auto* pmt = new PlotModTask(this, context, serviceName_.c_str(), appRequest);
    int err = 0;
    replyReq = pmt->run(err);
    if (!replyReq || err != 0)
        return false;

    // Add a flag to indicate that there is a data attached to this View
    viewRequest_("_DATAATTACHED") = "YES";

    // This will be done by the destructor of the MvClient/MvTask
    // delete pmt; pmt = 0;
    return true;
}

void CommonXSectView::Drop(PmContext& context)
{
    // Process the drop
    MvIconDataBase& dataBase = Owner().IconDataBase();
    MvRequest dropRequest = context.InRequest();
    MvIconList duList;
    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();

            // The drop request has been already advanced
            if (!dropRequest)  // no more drops
                break;
        }

        // Process data coming from special Modules (i.e. tag _MODULEID)
        MvRequest replyRequest;
        bool consistency_check = false;
        if (this->DropFromModule(dropRequest, replyRequest, consistency_check)) {
            // Insert reply request to the database
            if (consistency_check)
                duList = this->InsertDataRequest(replyRequest);

            //  The drop request has been already advanced
            continue;
        }

        // Process a dataUnit
        MvRequest request = dropRequest.justOneRequest();
        std::string verb = request.getVerb();
        if (ObjectList::IsDataUnit(verb.c_str()) == 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);
            // if ( ! CallService(request, context) )
            //   InsertDataRequest(request);

            // It calls an external service and waits for its return.
            // If returns false, it means that either:
            // a) this is a dataUnit and no need to be processed by a external service
            // b) error on processing the external service
            if (!callServiceInternal(request, context, replyRequest)) {
                // The external service sent an error message
                if (replyRequest && strcmp(replyRequest.getVerb(), "ERROR") == 0) {
                    std::string error_message;
                    if ((const char*)replyRequest("MESSAGE"))
                        error_message = (const char*)replyRequest("MESSAGE");
                    else
                        error_message = "Error";

                    MvLog().popup().err() << error_message;
                    dropRequest.advance();
                    continue;
                }

                // This is a dataunit. Insert it in the database at the
                // Page level request("_PARENT_DATAUNIT") = 1;
                MvIcon dataUnit = dataBase.InsertDataUnit(request, Owner().Id());
                duList.push_back(dataUnit);

                // IMPORTANT: CHECK IF THIS COMMAND IS NEEDED IN THE InsertDataRequest
                // Save to be used when returning from service
                //  this->ParentDataUnitId ( dataUnit.Id() );

                // Add information to the SubPage
                DecodeDataUnit(dataUnit);
            }
            else {
                // An external servce was called and may have returned a single dataUnit
                // or a combination of dataUnits and visdefs (e.g.: XSection application
                // may return: XY_MATRIX, XY_POINTS, MGRAPH)

                // IMPORTANT: DO WE NEED TO CREATE THIS 'PARENT' DATA UNIT????
                // request("_PARENT_DATAUNIT") = 1;
                // this->ParentDataUnitId ( 5 );

                // Insert reply request to the database
                duList = this->InsertDataRequest(replyRequest);
            }

            dropRequest.advance();
            continue;
        }

        // Process other icons
        if (ObjectList::IsView(verb.c_str()) == true)
            Owner().UpdateView(request);

        else if (verb == PLOTSUPERPAGE) {
            context.AdvanceTo(PLOTSUPERPAGE);
            return;
        }

        else if (verb == NEWPAGE) {
            context.AdvanceTo(NEWPAGE);
            return;
        }

        else if (verb == "DRAWING_PRIORITY") {
            Owner().SetDrawPriority(request);

            // Redraw this page
            if (strcmp(request("_APPL"), "macro") != 0)
                Owner().RedrawIfWindow();
        }
        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();
}

bool CommonXSectView::DropFromModule(MvRequest& dropRequest, MvRequest& replyRequest, bool& consistency_check)
{
    // If a request comes from a Module, identified by a tag _MODULEID, then group
    // all the relative requests. These requests should be processed together.
    // There are two possible set of requests coming from a Module:
    // A) main verb is NETCDF: this is the case when the Module was called to
    //    compute the data but not to visualise it. This happens, for instance, when
    //    function mcross_sect is called inside a Macro and used in a PLOT command
    //    afterwards. All the relevant visualisation data requests are under parameter
    //    _VISUALISE.
    //    If the request also contains a hidden parameter describing the internal VIEW
    //    to be used with this dataunit, then it needs to be checked against the current
    //    VIEW. This situation can happen, for instance, in a Macro program:
    //        xs = mcross_sect ( line : [ 0,-180,0,10],... )
    //        view = mxsectview( line : [10,-180,0,10],... )
    //        plot(view, xs)
    //    Output parameter consistency_check will indicate if the consistency check
    //    has failed.
    //
    // B) others: group all subsequent requests with the same tag id.


    // Process option A
    consistency_check = true;  // by default the consistency check returns true
    if ((const char*)dropRequest.getVerb() == std::string("NETCDF")) {
        replyRequest = dropRequest.getSubrequest("_VISUALISE");

        // Consistency check
        MvRequest origReq = dropRequest.getSubrequest("_VIEW_REQUEST");
        dropRequest.advance();
        if (origReq)
            consistency_check = this->ConsistencyCheck(viewRequest_, origReq);

        return true;
    }

    // process option B
    bool found = false;
    int moduleId = 0;
    while (dropRequest) {
        if (!(const char*)dropRequest("_MODULEID"))
            return found;

        if (!found)  // found first request
        {
            moduleId = (int)dropRequest("_MODULEID");
            replyRequest.clean();
            found = true;
        }
        else {
            if (moduleId != (int)dropRequest("_MODULEID"))
                return found;
        }

        // Save request
        replyRequest += dropRequest.justOneRequest();
        dropRequest.advance();
    }

    return found;
}

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

    // Save some specific values sent by the Data Applicaation
    ApplicationInfo(dropRequest);

    // Process the request
    MvIconList duList;
    MvIconList duListAll;  // store all dataunit processed
    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 req = dropRequest.justOneRequest();
        std::string verb = req.getVerb();
        if (verb.find(NETCDF) != std::string::npos || verb.find("INPUT") != std::string::npos) {
            // Insert the dataunit in the data base at the Page level
            //  request("_PARENT_DATAUNIT") = 1;
            MvIcon dataUnit = dataBase.InsertDataUnit(req, Owner().Id());

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

            // Add information to the SubPage
            DecodeDataUnit(dataUnit);

            if (this->ParentDataUnitId()) {
                dataUnit.ParentId(this->ParentDataUnitId());  // Check if is needed
                MvIcon parentDataUnit;
                dataBase.RetrieveIconFromList(DB_DATAUNIT, ParentDataUnitId(), parentDataUnit);
                parentDataUnit.SvcRequest(req);
            }
        }
        else {
            Owner().InsertCommonIcons(req);

            // 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();
    }

    return duListAll;
}

// -- METHOD: DecodeDataUnit
//
// -- PURPOSE:
//    . Extract the data and the metadata from a NetCDF request
//    . Provides the proper axis and graph definition according
//      to the metada.
//
void CommonXSectView::DecodeDataUnit(MvIcon& dataUnit)
{
    Owner().InitMatching();

    // 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);

    int subpageId = 0;
    while (decoder->ReadNextData()) {
        // Pass the data and metadata info to the page
        MatchingInfo dataInfo = decoder->CreateMatchingInfo();

        Owner().InsertDataUnit(dataUnit.Id(), 0, 0, dataInfo, subpageId);
    }
}

void CommonXSectView::Draw(SubPage* subPage)
{
    MvIconDataBase& dataBase = Owner().IconDataBase();

    // Get the data units for this subpage
    std::list<DataObject*> doList;
    subPage->GetDataObjects(doList);
    std::list<DataObject*>::iterator ii;
    DataObject* dataObject = nullptr;
    DrawPriorMap drawPriorMap;
    DrawPriorMap::iterator j;

    // Loop over data objects
    MvIcon dataUnit;
    for (ii = doList.begin(); ii != doList.end(); ii++) {
        dataObject = (*ii);
        // if ( ! dataBase.RetrieveDataUnit (dataObject->DataUnitId(), dataUnit) )
        if (!dataBase.RetrieveIconFromList(DB_DATAUNIT, dataObject->DataUnitId(), dataUnit))
            continue;

        // Retrieve visdef associated to this dataunit
        MvIconList vdList;
        dataObject->RetrieveMyVisDefList(dataUnit, vdList);

        DrawPriorMap tmpMap = dataObject->DrawPriority(vdList);
        for (j = tmpMap.begin(); j != tmpMap.end(); ++j)
            drawPriorMap.insert(*j);
    }

    // Do the drawing
    CommonDraw(subPage, drawPriorMap);
}

#if 0
void CommonXSectView::GenerateData(FILE *fp,int startIndex,double yMin,
				   double yMax,std::vector<double> &xvector,
				   std::vector<double> &yvector, int nrPoints, bool logax, bool modlev)
{

  int nrLevels = 85; // Hardcoded, same as in xsection application.

  int scale = 100;

  if ( modlev )
    {
      nrLevels = int(yMin - yMax);
      scale = 1;
    }
  int i;

  double PresBot = yMin * scale;
  double PresTop = yMax * scale;

  double lpa;
  double zdp = ( PresBot - PresTop)/ ( nrLevels - 1 );
  double deltalog, pk;
  int firstindex, nextindex;
 
  if ( logax ) 
    deltalog = (log10(PresBot) - log10(PresTop))/(nrLevels - 1);

  for ( int k = 0; k < nrLevels; k++ )
    {
      if ( logax )
	pk = exp((log10(PresTop) + (double)k*deltalog)*log(10.)); 
      else
	pk = modlev == 0 ? PresTop + k * zdp : PresTop + k;

      if ( FindPressure(yvector,pk,firstindex,nextindex,scale) )
	{ 
	  if ( modlev )
	    lpa = ( pk - (yvector[firstindex] * scale)) /
	      ((yvector[nextindex]*scale) - (yvector[firstindex]*scale));
	  else
	    if ( logax )
	      lpa = ( pk - (yvector[firstindex]*scale)) /
		((yvector[nextindex]*scale) - (yvector[firstindex]*scale));
	    else
	      lpa = log( pk/(yvector[firstindex]*scale)) /
		log((yvector[nextindex]*scale)/ (yvector[firstindex]*scale));
	  
	  double xfirst, xnext;
	  for (i = 0; i < nrPoints; i++ )
	    {
	      xfirst = xvector[startIndex + (firstindex * nrPoints) + i ];
	      xnext =  xvector[startIndex + (nextindex * nrPoints) + i ];
	      
	      if ( xfirst < BIGDOUBLE && xnext < BIGDOUBLE )
		fprintf(fp,"%e ",( xfirst + ( xnext - xfirst) *lpa ));
	      else
		fprintf(fp,"%e ", DONTCARE);
	    }
	  
	}
      else
	for (i=0;i<nrPoints;i++)
	  fprintf(fp,"%e ", DONTCARE);
    }
}
bool CommonXSectView::CheckLatLon(const std::string& name,double minVal, double maxVal,
				  MvRequest& axisRequest, const char *param)

{
  bool retVal = false;
  const char *tickType = axisRequest("AXIS_TICK_LABEL_TYPE");
  if ( axisRequest( "AXIS_ORIENTATION" ) != Cached ("VERTICAL" ) &&
      ( tickType && name == tickType ) )
    {
      if ( axisRequest.countValues("AXIS_MIN_VALUE") && axisRequest.countValues("AXIS_MAX_VALUE") )
	{
	  double minY = axisRequest("AXIS_MIN_VALUE");
	  double maxY = axisRequest("AXIS_MAX_VALUE");
	  if ( minY != minVal || maxY != maxVal )
	    {
	      minVal = minY;
	      maxVal = maxY;
	      double lat1 = viewRequest_(param,0);
	      double lat2 = viewRequest_(param,2);
	      double lon1 = viewRequest_(param,1);
	      double lon2 = viewRequest_(param,3);
	      viewRequest_.unsetParam(param);

	      if ( name == "LONGITUDE" )
		{
		  lon1 = minVal; lon2 = maxVal;
		}
	      else
		{
		  lat1 = minVal; lat2 = maxVal;
		}

	      viewRequest_.addValue(param,lat1);
	      viewRequest_.addValue(param,lon1);
	      viewRequest_.addValue(param,lat2);
	      viewRequest_.addValue(param,lon2);
	    }
	  retVal = true;
	}
    }
  return retVal;
}

bool CommonXSectView::FindPressure(std::vector<double> &yvector,double pk,int &firstindex,int &nextindex,int scale)
{
  int i  = 0;

  pk /= scale;

  while ( i < yvector.size() - 1 )
    {
      if ( (yvector[i] <= pk && pk <= yvector[i+1]) ||  ( yvector[i+1] <= pk && pk <= yvector[i] ) )
	{
	  firstindex = i; nextindex = i+1;
	  return true;
	}
      i++;
    }
  return false;
}
void
CommonXSectView::ReplaceAxis ( MvRequest& axisRequest )
{
  MvRequest oldAxis;
  bool changeData = false;

  if ( axisRequest( "AXIS_ORIENTATION" ) == Cached ("VERTICAL" ) )
    {
      // Check min/max values
      if ( axisRequest.countValues("AXIS_MIN_VALUE") && axisRequest.countValues("AXIS_MAX_VALUE") )
	{
	  double minY = axisRequest("AXIS_MIN_VALUE");
	  double maxY = axisRequest("AXIS_MAX_VALUE");
	  
	  if ( minY != (double)viewRequest_("BOTTOM_PRESSURE") || 
	       maxY != (double)viewRequest_("TOP_PRESSURE") )
	    {
	      viewRequest_("BOTTOM_PRESSURE") = minY;
	      viewRequest_("TOP_PRESSURE") = maxY;
	      changeData = true;
	    }
	}
      // Check if axis type is the same ( log or not).
      bool axisLog = (  (const char*)axisRequest("AXIS_TYPE") && 
			strcmp(axisRequest("AXIS_TYPE"),"LOGARITHMIC") == 0);
      bool viewLog = (  (const char*)viewRequest_("PRESSURE_LEVEL_AXIS") && 
			strcmp(viewRequest_("PRESSURE_LEVEL_AXIS"),"LOG") == 0);
      if ( axisLog != viewLog )
	{
	  if ( axisLog )
	    viewRequest_("PRESSURE_LEVEL_AXIS") = "LOG";
	  else
	   viewRequest_.unsetParam("PRESSURE_LEVEL_AXIS"); 
	  
	  changeData = true;
	}
    } // end vertical axis
  else if ( CheckHorizontal(axisRequest) )
    changeData = true;

  // Recreate data if needed.
  if ( changeData )
    RecreateData();
  else
    {
      Owner().NeedsRedrawing(true);
      Owner().EraseBackDraw();
    }

  Owner().RedrawIfWindow();

}

bool CommonXSectView::Reset(const MvRequest &req)
{
  if ( strcmp(req.getVerb(),"PAXIS") == 0 )
    {
      if ( req("AXIS_ORIENTATION") == Cached ("VERTICAL" ) && 
	   req.countValues("AXIS_MIN_VALUE") && req.countValues("AXIS_MAX_VALUE") )
	{
	  viewRequest_("BOTTOM_PRESSURE") = yMin_;
	  viewRequest_("TOP_PRESSURE") = yMax_;
	  return true;
	}
    }
  
  return false;
}
#endif
