/***************************** LICENSE START ***********************************

 Copyright 2022 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 "methods.h"

#include "MvException.h"

namespace metview {

void buildOnePolygon(int idx, CVector* vlat, CVector* vlon, std::vector<eckit::geometry::polygon::LonLatPolygon>& polygons)
{
    if (vlat->Count() != vlon->Count()) {
        std::string msg;
        if (idx == -1) {
            msg = "latitude and longitude vectors must be the same size!";
        } else {
            msg = "latitude and longitude vectors at index=" + std::to_string(idx) + " must be the same size!";
        }
        msg += " " + std::to_string(vlat->Count()) + " != " +  std::to_string(vlon->Count());
        throw MvException(msg);
    }

    // create an eckit polygon
    std::vector<eckit::geometry::Point2> points;
    points.reserve(vlat->Count());
    for (int i=0; i < vlat->Count(); i++) {
        points.emplace_back((*vlon)[i], (*vlat)[i]);
    }

    assert(!points.empty());
    // make sure the first and last points are the same. Otherwise
    // eckit fails with an assertion! We use the same test as eckit.
    if (!eckit::geometry::points_equal(points.front(), points.back())) {
        points.push_back(points.front());
    }

    polygons.emplace_back(points);
}

// build polygons
void buildPolygons(Value* arg1, Value* arg2, std::vector<eckit::geometry::polygon::LonLatPolygon>& polygons)
{
    // list of vectors
    if (arg1->GetType() == tlist) {
        CList* latLst=nullptr;
        CList* lonLst=nullptr;
        arg1->GetValue(latLst);
        arg2->GetValue(lonLst);
        if (latLst->Count() != lonLst->Count()) {
            throw MvException("latitude and longitude lists must be the same size! " +
                              std::to_string(latLst->Count()) + " != " +
                              std::to_string(lonLst->Count()));
        }

        for (int i = 0; i < latLst->Count(); i++) {
            CVector* vlat;
            CVector* vlon;
            (*latLst)[i].GetValue(vlat);
            (*lonLst)[i].GetValue(vlon);
            metview::buildOnePolygon(i, vlat, vlon, polygons);
        }
    // vectors
    } else if (arg1->GetType() == tvector) {
        CVector* vlat;
        CVector* vlon;
        arg1->GetValue(vlat);
        arg2->GetValue(vlon);
        metview::buildOnePolygon(-1, vlat, vlon, polygons);
    }
}

/*
    checkStringOption
    This is used when a Macro function can take an optional string-based argument that acts as a boolean,
    for example mask(fs, area, "missing"). Here we would also like to allow Python to use this extra
    option as a named argument, e.g. mv.mask(fs, area, missing=True). In this case Macro will receive
    the arguments [fs, area, 'missing', 'on'] or [fs, area, 'missing', 1.000]. This means we need to handle
    [..args.., 'missing']  and [..args.., 'missing', 'on'] (1.000 instead of 'on'). This function scans
    the last two arguments to check for these two cases. If it finds the option, it will set the corresponding
    boolean variable and adjust the arity to essentially remove the option args from the args so that the rest
    of the code does not have to worry about it.
    This code assumes that there will only be one option arguement and that it will be the last argument.
*/
void checkStringOption(const char* optionName, int& arity, Value* arg, bool& optionValue, std::string& incorrectOption)
{

    optionValue     = false;
    incorrectOption = "";
    int option      = -1;

    if (arity >= 2) {
        // check the last two arguments to see if one is the option name
        for (int i = arity - 2; i < arity; i++) {
            if (arg[i].GetType() == tstring) {
                const char* name;
                arg[i].GetValue(name);
                if (!strcmp(name, optionName)) {
                    // got it - if this is the last arg then it means it is 'on'
                    // so set the flag and pop the arg from the list
                    if (i == arity - 1) {
                        optionValue = true;
                        arity--;
                    }
                    else {
                        // must be the second-last arg, so it's probably
                        // a Python named argument, with its value
                        // being the last argument
                        if (arg[arity - 1].GetType() == tstring) {
                            const char* value;
                            arg[arity - 1].GetValue(value);
                            if (!strcasecmp(value, "on"))
                                option = 1;
                            else if (!strcasecmp(value, "off"))
                                option = 0;
                            if (option > -1) {
                                optionValue = (option == 1) ? true : false;
                                arity -= 2;
                            }
                        }
                        else if (arg[arity - 1].GetType() == tnumber) {
                            double vv;
                            arg[arity - 1].GetValue(vv);
                            optionValue = (fabs(1. - vv) < 1E-6);
                            arity -= 2;
                        }
                    }
                }
                // we got an unexpected string, not the one we're looking for
                else {
                    incorrectOption = name;
                    arity--;
                }
            }
        }
    }
}


}
