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

// File Page.CC
// Gilberto Camara and Fernando Ii - ECMWF Mar 97
// Lubia Vinhas                    - ECMWF Mar 98

#include "Page.h"

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

#include "ObjectList.h"
#include "PlotMod.h"
#include "PmContext.h"
#include "SubPage.h"
#include "MvLog.h"

Page::Page(MvRequest& pageRequest) :
    Presentable(pageRequest),
    firstVisible_(1),
    visibleCount_(0),
    dataUnitId_(0),
    dropShouldStay_(false)
{
    // Each page has its own graphics engine
    engine_ = std::unique_ptr<GraphicsEngine>(GraphicsEngineFactory::Make(*this));

    // Find out if there is a view associated with the request
    MvRequest viewRequest = pageRequest.getSubrequest("VIEW");
    const char* viewClass = (const char*)viewRequest.getVerb();
    if (viewClass && ObjectList::IsGeographicalView(viewClass)) {
        // If AREA is not given, Magics will use the default value
        if ((const char*)viewRequest("AREA")) {
            Location area;
            std::unique_ptr<PmProjection> proj(PmProjectionFactory::Make(viewRequest));

            const char* proj_name = viewRequest("MAP_PROJECTION");

            if (proj_name && strcmp(proj_name, "CYLINDRICAL") == 0) {
                std::unique_ptr<PmProjection> projCyl(PmProjectionFactory::Make(viewRequest));
                if (!projCyl.get()->CheckGeodeticCoordinates(area)) {
                    MvLog().warn() << "Some/All area coordinates are invalid! Replaced by default values";
                }
            }
            else {
                // Initialize the bounding box in geodetic coordinates
                double lat1  = viewRequest("AREA", 0);
                double long1 = viewRequest("AREA", 1);
                double lat2  = viewRequest("AREA", 2);
                double long2 = viewRequest("AREA", 3);

                area = Location(lat2, long1, lat1, long2);
            }

            viewRequest("AREA") = area.Bottom();
            viewRequest("AREA") += area.Left();
            viewRequest("AREA") += area.Top();
            viewRequest("AREA") += area.Right();

            double vertLong                       = proj.get()->CheckOriginLongitude();
            viewRequest("MAP_VERTICAL_LONGITUDE") = vertLong;
        }
        // Flag to indicate that this is a default area. This will be used by the zoom scheme
        else {
            viewRequest("_DEFAULT_AREA") = "YES";
        }

        myRequest_("VIEW")  = viewRequest;
        pageRequest("VIEW") = viewRequest;
    }

    // Create a new view request if there isn't any
    if (!viewClass || ObjectList::IsView(viewClass) == false) {
        viewRequest         = ObjectList::CreateDefaultRequest(DEFAULTVIEW.c_str(), EXPAND_NO_DEFAULT);
        pageRequest("VIEW") = viewRequest;
        myRequest_("VIEW")  = viewRequest;
    }

    // Create a view associated to the page
    myView_ = std::unique_ptr<PlotModView>(PlotModViewFactory::Make(*this, viewRequest));

    // By default, indicates that a drawing needs to be performed later.
    // This default will be overrulled if the dropping icon triggers a call
    // to a Service. In this case, PlotModService::endOfTask should be called
    // and this function should perform the drawing.
    this->HasDrawTask(true);

    // Create the subpages associated to the page
    CreateSubpages();
}

Page::Page(const Page& old) :
    Presentable(old),
    firstVisible_(1),
    visibleCount_(old.visibleCount_),
    dataUnitId_(old.dataUnitId_),
    dropShouldStay_(old.dropShouldStay_),
    drawingPriority_(old.drawingPriority_)
{
    // ZoomStacks not copied
    // Easiest to make new projection from view request
    projection_ = std::unique_ptr<PmProjection>(PmProjectionFactory::Make(old.myView_->ViewRequest()));

    // Each page has its own graphics engine, create one for this page
    engine_ = std::unique_ptr<GraphicsEngine>(GraphicsEngineFactory::Make(*this));

    // Clone the view associated to the page and set the new owner.
    myView_ = std::unique_ptr<PlotModView>(old.GetView().Clone());
    myView_->Owner(*this);
}

Page::~Page()
{
    MvChildList revertedChildList = childList_;
    revertedChildList.reverse();

    // Get Last and previous to last in child list
    for (auto& j : revertedChildList) {
        if (!(j->GetDeviceData()))
            this->RemovePresentable(j);
    }

    // Remove Page from DataBase
    this->RemovePageFromDataBase();
}

string
Page::Name()
{
    return (const char*)ObjectInfo::ObjectName(myRequest_, "Page", Id());
}

void Page::Draw()
{
    HasDrawTask(true);
    this->NeedsRedrawing(true);

    // Draw all my children
    DrawChildren();

    this->HasDrawTask(false);
    this->NeedsRedrawing(false);
}

void Page::DrawProlog()
{
    auto child = childList_.begin();
    (*child)->DrawProlog();
}

void Page::DrawPageHeader()
{
    auto child = childList_.begin();
    (*child)->DrawPageHeader();
}

void Page::Drop(PmContext& context)
{
    MvLog().dbg() << "Page::Drop";
    // Skip the first request if it's a drop ( it normally is).
    // If visualize is done directly on a data object, the
    // data request is the first, and should not be skipped.
    if (context.InRequest().getVerb() == Cached("DROP"))
        context.RewindToAfterDrop();

    myView_->Drop(context);
}

void Page::Visit(Visitor& v)
{
    // Implement the visitor pattern
    // Visit me
    v.Visit(*this);

    // Visit all my children
    Presentable::Visit(v);
}

bool Page::InsertDataUnit(int dataUnitId, long offset, long offset2,
                          MatchingInfo& dataInfo, int& subPageId)
{
    require(myView_.get() != 0);

    // Initialise variables
    SubPage* subpage = 0;  // pointer to subpage

    // Start at the beginning of the list
    MvChildIterator child;
    for (child = childList_.begin(); child != childList_.end(); ++child) {
        subpage = (SubPage*)(*child);

        subpage->InsertDataUnit(dataUnitId, offset, offset2, dataInfo);
        subPageId = subpage->Id();

        subpage->NeedsRedrawing(true);
        RedrawIfWindow();
        break;
    }

    NotifyObservers();

    return true;
}

// The algorithm is the following:
// 1. Drop into Display Window: all icons go to the Plot Level (PL),
//    with some exceptions:
//    1.0. MText and MLegend (maybe others?) will be connected to the View Level (VL).
//    1.1. VisDef (VD) without DataUnit (DU):
//         1.1.1. single VD:
//                . first, try to replace all existing VDs with similar name
//                . if there is no replacement then
//                     . if there are DUs in the Contents then
//                        . for each DU add VD and remove the old ones
//                            with similar TYPE (Algorithm 1).
//                     . else
//                        . add DU to VL
//                . else return
//         1.1.2. group of VDs:
//                . for each DU in the Contents do Algorithm 1
//    1.2. (VDs) with (DU):
//         . they will be connected together in the PL (Algorithm 1)
//
// 2. Drop into Contents Window: icons stay where there were dropped
//    2.1. (VDs) without (DUs)
//         . replace (VDs) with similar name OR
//         . add (VDs).
//    2.2. (VDs) with (DU)
//         . add (VDs) to (DU).
void Page::InsertVisDef(MvRequest& vdRequestList, MvIconList& duList)
{
    // Check if visdef was dropped to superpage, in which case it's already added
    if (myParent_->DroppedHere())
        return;

    // VD may have dropped with or without DU
    MvListCursor duCursor;
    if (duList.empty()) {
        // Only VD was dropped, no DU
        MvIconDataBase& dataBase = this->IconDataBase();
        MvIconList oldDuList;
        if (this->DropShouldStay()) {
            // Drop came from Contents Window
            if (this->ContentsDataUnit()) {
                // VD dropped into a DU container
                // Retrieve the related DU
                MvIcon theIcon;
                int iconId = this->ContentsDataUnit();
                dataBase.RetrieveIconFromList(DB_DATAUNIT, iconId, theIcon);
                oldDuList.push_back(theIcon);
            }
        }
        else
            // Drop came from Display Window
            // Retrieve all dataunit(s) connected to this Page
            dataBase.RetrieveIcon(PRES_DATAUNIT_REL, Id(), oldDuList);

        // If there are DUs, process VDs for each of them.
        // Otherwise, add VDs to the Page
        if (!oldDuList.empty()) {
            for (duCursor = oldDuList.begin(); duCursor != oldDuList.end(); ++duCursor)
                this->InsertVisDefToDataUnit(vdRequestList, *duCursor);
        }
        else
            this->InsertVisDefToPresentable(vdRequestList);
    }
    else  // VD and DU dropped together
    {
        // Process VDs for each DU
        for (duCursor = duList.begin(); duCursor != duList.end(); ++duCursor)
            InsertVisDefToDataUnit(vdRequestList, *duCursor);
    }

    // Redraw window
    RedrawIfWindow();
    NotifyObservers();
}

bool Page::InsertVisDefToDataUnit(MvRequest& vdRequestList, MvIcon& du)
{
    // Retrieve the Icon Data Base
    MvIconDataBase& dataBase = this->IconDataBase();

    // Remove visdefs with same TYPE
    vdRequestList.rewind();
    while (vdRequestList) {
        MvRequest reqVd = vdRequestList.justOneRequest();
        MvIconList duList;
        if (CheckValidVisDefWithIcl(du, reqVd, duList)) {
            MvListCursor duCursor;
            for (duCursor = duList.begin(); duCursor != duList.end(); ++duCursor) {
                MvIcon& theDataUnit = *(duCursor);
                dataBase.RemoveAllVisDefsByDataUnit(theDataUnit, &reqVd);
            }
        }
        vdRequestList.advance();
    }

    // Insert new VD
    bool found = false;
    vdRequestList.rewind();
    while (vdRequestList) {
        MvRequest reqVd = vdRequestList.justOneRequest();
        MvIconList duList;
        if (CheckValidVisDefWithIcl(du, reqVd, duList)) {
            MvListCursor duCursor;
            for (duCursor = duList.begin(); duCursor != duList.end(); ++duCursor)
                dataBase.InsertVisDef(reqVd, Id(), du);

            found = true;
        }

        vdRequestList.advance();
    }

    if (found) {
        // Remove all drawings related to the DataUnit,
        // but not the DataUnit itself
        removeData_     = false;
        MvIcon dataUnit = du;
        this->RemoveIcon(dataUnit);
        removeData_ = true;
    }

    return found;
}

void Page::InsertVisDefToPresentable(MvRequest& vdRequestList)
{
    // Retrieve the Icon Data Base
    MvIconDataBase& dataBase = this->IconDataBase();

    // If Visdef came from Contents edition window
    int visdefId = vdRequestList("_CONTENTS_ICON_EDITED");
    if (visdefId != 0) {
        // Visdef already exists in the data base, replace it
        dataBase.UpdateIcon(DB_VISDEF, visdefId, vdRequestList);

        return;
    }

    // Insert visdefs into the dataBase. If a visdef with the same NAME
    // and CLASS already exists in the dataBase, replace it.
    bool exclusive = true;
    vdRequestList.rewind();
    while (vdRequestList) {
        MvRequest reqVd = vdRequestList.justOneRequest();
        MvIcon icon(reqVd, true);
        dataBase.InsertIcon(PRES_VISDEF_REL, Id(), icon, -1, exclusive);
        exclusive = false;

        vdRequestList.advance();
    }

    return;
}

void Page::RemoveIcon(MvIcon& icon)
{
    // Remove drawings
    MvRequest req = icon.Request();
    Cached verb   = req.getVerb();
    if (ObjectList::IsVisDef(verb) ||
        ObjectList::IsDataUnit(verb) ||
        ObjectList::IsVisDefText(verb)) {
        MvChildIterator child;
        for (child = childList_.begin(); child != childList_.end(); ++child)
            (*child)->RemoveIcon(icon);
        if (ObjectList::IsVisDefAxis(verb)) {
            // It needs to implement this function
            // EraseBackDraw();
        }
    }

    // Remove PText annotation
    if (ObjectList::IsVisDefText(verb)) {
        //U		if ( verb == ANNOTATION )
        //U			this->EraseForeDraw ();
    }
    myView_->Reset(req);

    RedrawIfWindow();
    if (ObjectList::IsDataUnit(verb))
        NotifyObservers();
}

void Page::RemoveAllData()
{
    RemoveAllDataChildren();

    //	if ( myView_->BackgroundFromData() )
    // Enable new drawing of back and foreground
    //		EraseDraw();

    RemoveMyData();

    // Clear Matching Info
    this->InitMatching();
}

void Page::UpdateDataForZoom(const std::string& /*zoomInfo*/)
{
    bool updateWmsForZoom = true;
    if (!updateWmsForZoom)
        return;

    std::vector<Presentable*> presVec;
    FindBranchesByRequest("PRASTERLOOP", presVec);

    for (auto pres : presVec) {
        MvIcon dataUnit;
        pres->DataUnit(dataUnit);
        if (strcmp(dataUnit.Request().getVerb(), "PRASTERLOOP") == 0) {
            MvRequest r = dataUnit.Request().getSubrequest("WMS_CLIENT_REQUEST");

            r("_CONTEXT") = myView_->ViewRequest();

            r("_CLASS")        = "WMSCLIENT";
            r("_ACTION")       = "update";
            r("_SERVICE")      = "WmsClient";
            r("_USELOGWINDOW") = "1";

            int error;
            MvRequest result = MvApplication::waitService("wmsclient", r, error);

            if (result && error == 0) {
                // WMS request's bounding box was updated for zoom
                pres->SetRequest(result);

                MvIconDataBase& dataBase = pres->IconDataBase();
                dataBase.UpdateIcon(DB_DATAUNIT, dataUnit.Id(), result);
            }
        }
    }
}

void Page::ZoomRequest(int izoom)
{
    // Retrieve zoom stack structure
    ZoomStacks* stacks = this->GetZoomStacks();
    if (stacks == 0)
        return;

    std::string zoomInfo;

    //If zoom level is 0 then send the original message to Magics
    if (izoom == 0) {
        // Set zoom stack index to 0
        stacks->Current(izoom);
        (*myView_).ViewRequest().unsetParam("_ZOOM_DEFINITION");
        zoomInfo = stacks->Get(izoom);
    }
    else {
        // Get the requested element
        zoomInfo = stacks->Get(izoom);

        // Update zoom info
        (*myView_).ViewRequest()("_ZOOM_DEFINITION") = zoomInfo.c_str();
    }

    //Update some dataUnits for the new area
    this->UpdateDataForZoom(zoomInfo);
}

void Page::ZoomRequest(const std::string& zoomInfo)
{
    // Check if Zoom operation can be performed in this Page
    if (this->CanDoZoom() == false)
        return;

    // Retrieve zoom stack structure
    ZoomStacks* stacks = this->GetZoomStacks();
    if (stacks == 0)
        return;

    // Save new coordinates
    if (stacks->Size() == 0) {
        // Save initial zoom info
        std::string cc1("Zoom 0");
        stacks->GeodeticPush(cc1);
    }
    stacks->GeodeticPush(zoomInfo);

    // Update zoom info
    (*myView_).ViewRequest()("_ZOOM_DEFINITION") = zoomInfo.c_str();

    this->UpdateDataForZoom(zoomInfo);
}

void Page::ZoomInfo(int& zoomNumberOfLevels, int& zoomCurrentLevel)
{
    // Initial values
    zoomNumberOfLevels = zoomCurrentLevel = 0;

    // Retrieve stack
    ZoomStacks* stacks = this->GetZoomStacks();
    if (stacks == 0)
        return;

    // Get values
    zoomNumberOfLevels = stacks->Size();
    zoomCurrentLevel   = stacks->Current();
}

void Page::DescribeYourself(ObjectInfo& description)
{
    description.PutNewLine("#PageDescription");

    // Ask the view to describe itself
    myView_->DescribeYourself(description);

    // Remove extra parameters that have not been used in the current
    // implementation of the PLOT_PAGE icon (FAMI, Oct-2015, METV-4.5.7)
    MvRequest myRequest = myRequest_;
    myRequest.unsetParam("ROWS");
    myRequest.unsetParam("COLUMNS");
    myRequest.unsetParam("PAGE_X_GAP");
    myRequest.unsetParam("PAGE_Y_GAP");

    // Convert the page description to the macro syntax
    std::set<Cached> skipSet;
    skipSet.insert("VIEW");
    description.ConvertRequestToMacro(myRequest, PUT_LAST_COMMA, MacroName().c_str(), "plot_page", skipSet);

    // Include the view information
    int size             = description.MaximumParameterNameLength(myRequest);
    std::string viewName = myView_->MacroName();
    description.FormatLine("VIEW", viewName.c_str(), "", size);

    // Close the page description
    description.PutNewLine(")");
}

void Page::CreateSubpages()
{
    MvRequest sub = myRequest_("SUB_PAGES");

    // Subpage request can be NULL when the Page contains only
    // one Subpage. In this case, creates a default Subpage
    if (!sub) {
        MvRequest subpageRequest("PLOT_SUBPAGE");
        subpageRequest = ObjectList::ExpandRequest(subpageRequest, EXPAND_DEFAULTS);

        Presentable* subpage = new SubPage(subpageRequest);
        ensure(subpage != 0);

        // Insert the subpage in the superpage's child list
        this->Insert(subpage);
        visibleCount_++;
    }
    else {
        while (sub) {
            MvRequest r          = sub.justOneRequest();
            Presentable* subpage = new SubPage(r);
            ensure(subpage != 0);
            this->Insert(subpage);
            sub.advance();
            visibleCount_++;
        }
    }
}

bool Page::RemovePresentable(Presentable* subpage)
{
    // If is the last and doesnt't own a canvas
    if ((subpage == childList_.back()) &&
        ((signed int)childList_.size() > visibleCount_) &&
        !subpage->IsVisible()) {
        delete subpage;
        return true;
    }

    MvChildList revertedChildList = childList_;
    revertedChildList.reverse();

    // Get Last and previous to last in child list
    auto j                       = revertedChildList.begin();
    Presentable* lastSubPage     = *j;
    Presentable* previousSubPage = *(++j);

    // Save DeviceData and Canvas of the last SubPage that owns a canvas
    DeviceData* savedDevDat = 0;
    Canvas* savedCanvas     = 0;
    if (((signed int)childList_.size() == visibleCount_) &&
        (lastSubPage != subpage)) {
        savedCanvas = &(lastSubPage->GetCanvas());
        savedDevDat = lastSubPage->ReleaseDeviceData();
    }

    // Scan child list in reverse order
    while (lastSubPage != subpage) {
        // Disconnect last SubPage canvas from it
        Canvas& lastCanvas = lastSubPage->GetCanvas();
        lastCanvas.RemovePresentableId(lastSubPage->Id(), RMNONE);

        // Disconnect and save previous SubPage canvas
        Canvas& previousCanvas = previousSubPage->GetCanvas();
        previousCanvas.RemovePresentableId(previousSubPage->Id(), RMNONE);

        // Assign saved canvas to last SubPage
        lastSubPage->SetCanvas(&previousCanvas);

        lastSubPage->NeedsRedrawing(true);
        previousSubPage->NeedsRedrawing(true);

        // If previous SubPage owns the canvas
        // Save and disconnect device data
        DeviceData* deviceData = 0;
        if (previousSubPage->GetDeviceData())
            deviceData = previousSubPage->ReleaseDeviceData();

        // Assign device data to last SubPage
        if (deviceData)
            lastSubPage->SetDeviceData(deviceData);

        // If SubPage to delete owns a canvas
        if (((signed int)childList_.size() == visibleCount_) &&
            (previousSubPage == subpage)) {
            // Assign saved canvas and Device Data
            previousSubPage->SetCanvas(savedCanvas);
            previousSubPage->SetDeviceData(savedDevDat);

            previousSubPage->NeedsRedrawing(true);
            subpage->NeedsRedrawing(true);

            // Make SubPage the last of child list
            auto i = find(childList_.begin(), childList_.end(), subpage);
            ensure((*i) == subpage);

            childList_.erase(i);
            childList_.push_back(subpage);
        }

        // Get next set of SubPages
        lastSubPage     = previousSubPage;
        previousSubPage = *(++j);
    }

    if (!(subpage->GetDeviceData()))
        delete subpage;

    return true;
}

void Page::RemovePageFromDataBase()
{
    // Verify if my Parent is still alive
    if (myParent_ == 0)
        return;

    // Retrieve the Icon Data Base
    MvIconDataBase& dataBase = this->IconDataBase();

    // Remove Page from DataBase
    dataBase.RemovePresentable(presentableId_);

    // Remove children from DataBase
    MvChildIterator child;
    for (child = childList_.begin(); child != childList_.end(); ++child)
        dataBase.RemovePresentable((*child)->Id());
}

void Page::SetProjection(const MvRequest& viewRequest)
{
    projection_ = std::unique_ptr<PmProjection>(PmProjectionFactory::Make(viewRequest));
}

int Page::FirstPrintable()
{
    if (metview::IsParameterSet(myRequest_, "FIRST_VISIBLE"))
        return myRequest_("FIRST_VISIBLE");
    else
        return 1;
}

int Page::PaperPageNumber(int subPageId)
{
    int nrVisible = visibleCount_;

    MvChildIterator child;
    int i;

    for (child = childList_.begin(), i = 1; child != childList_.end();
         ++child, i++) {
        if ((*child)->Id() == subPageId)
            break;
    }

    int paperPageNumber;
    if (myParent_->PrintAll()) {
        paperPageNumber = ((i - 1) / nrVisible) + myParent_->PageIndex();
    }
    else {
        int firstPrintable = this->FirstPrintable();
        if ((i >= firstPrintable) &&
            (i < firstPrintable + nrVisible))
            paperPageNumber = myParent_->PageIndex();
        else
            paperPageNumber = -1;
    }

    return paperPageNumber;
}

Canvas*
Page::GetNextCanvas(MvRequest& subpageReq)
{
    Canvas* canvas = 0;
    MvChildIterator child;
    for (child = childList_.begin(); child != childList_.end(); ++child) {
        if (!(*child)->HasData())
            // Found available canvas on existing child
            break;
    }

    if (child == childList_.end()) {
        // Look for the least used canvas
        int index = childList_.size() % visibleCount_;

        child = childList_.begin();
        for (int i = 0; i < index; i++)
            child++;
    }
    canvas     = &((*child)->GetCanvas());
    subpageReq = (*child)->Request();

    return canvas;
}

bool Page::CheckValidVisDefWithIcl(MvIcon& dataUnit, MvRequest& vdRequest, MvIconList& dataUnitList)
{
    // Check only if a visdef is being wrongly applied to a 1D or 2D fields
    // Get the dimension flag
    int iflag = dataUnit.Request()("_NDIM_FLAG");

    // Check visdef on the page level
    if (ObjectList::CheckValidVisDef(dataUnit.IconClass(), vdRequest.getVerb(), iflag)) {
        dataUnitList.push_back(dataUnit);
        return true;
    }

    // Visdef is not valid here, check its children
    bool found = false;
    MvChildIterator child;
    for (child = childList_.begin(); child != childList_.end(); ++child) {
        auto* subpage = (SubPage*)(*child);
        if (subpage->CheckValidVisDefWithIcl(dataUnit, vdRequest, dataUnitList, iflag))
            found = true;
    }

    return found;
}

bool Page::CanDoZoom()
{
    return myView_->CanDoZoom();
}

void Page::RegisterDrawTrailer()
{
    // Call plotting procedure
    myParent_->DrawTrailer();
    return;
}

void Page::DropSimulate(PmContext& context)
{
    if (!context.InRequest())
        return;

    this->Drop(context);
}

void Page::UpdateView(const MvRequest& view1)
{
    // Expand request
    MvRequest view = ObjectList::ExpandRequest(view1, EXPAND_DEFAULTS);

    // Copy hidden parameters
    view.mars_merge(view1);

    // Create an icon saving the new ID value
    MvIcon iconView(view, true);
    MvRequest reqView = iconView.Request();

    // Update internal Request
    MvRequest presReq = Request();
    presReq("VIEW")   = reqView;
    SetRequest(presReq);

    // Update View resquest at myview structure
    myView_->ViewRequest(reqView);

    // Update dataBase
    MvIconDataBase& dataBase = this->IconDataBase();
    dataBase.InsertIcon(PRES_VIEW_REL, presentableId_, iconView, -1, true);
}

void Page::contentsRequest(MvRequest& req)
{
    myView_->contentsRequest(req);
}
