/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */

#ifndef OSGEARTHDRIVERS_DUKTAPE_JS_GEOMETRY_H
#define OSGEARTHDRIVERS_DUKTAPE_JS_GEOMETRY_H

#include <osgEarth/Geometry>
#include <osgEarth/GeometryUtils>
#include "duktape.h"

#define LC "[duktape] "

namespace osgEarth { namespace Drivers { namespace Duktape
{
    struct GeometryAPI
    {
        static void install(duk_context* ctx)
        {
            duk_push_c_function(ctx, GeometryAPI::buffer, 2/*numargs*/); // [global, function]        
            duk_put_prop_string(ctx, -2, "oe_geometry_buffer");          // [global]

            duk_push_c_function(ctx, GeometryAPI::getBounds, 1);
            duk_put_prop_string(ctx, -2, "oe_geometry_getBounds");

            duk_push_c_function(ctx, GeometryAPI::cloneAs, 2);
            duk_put_prop_string(ctx, -2, "oe_geometry_cloneAs");

            duk_eval_string_noresult(ctx,
                "oe_duk_bind_geometry_api = function(geometry) {"
                "    geometry.getBounds = function() {"
                "        return oe_geometry_getBounds(this);"
                "    };"
                "    geometry.buffer = function(distance) {"
                "        var result = oe_geometry_buffer(this, distance);"
                "        return oe_duk_bind_geometry_api(result);"
                "    };"
                "    geometry.cloneAs = function(typeName) {"
                "        var result = oe_geometry_cloneAs(this, typeName);"
                "        return oe_duk_bind_geometry_api(result);"
                "    };"
                "    return geometry;"
                "};"
            );
        }

        static void bindToFeature(duk_context* ctx)
        {
            duk_eval_string_noresult(ctx,
                "oe_duk_bind_geometry_api(feature.geometry);" );
        }
        
        /**
         * buffer operation
         * input:  1) geometry GeoJSON, 2) distance
         * output: geometry GeoJSON object, or undefined on fail
         */
        static duk_ret_t buffer(duk_context* ctx)
        {
            // arg#0: geometry object
            if ( !duk_is_object(ctx, 0) && !duk_is_number(ctx, 1) )
            {
                OE_WARN << LC << "geometry.buffer(): illegal arguments" << std::endl;
                return DUK_RET_TYPE_ERROR;
            }

            // arg#0 : geometry
            std::string geomJSON = duk_json_encode(ctx, 0);
            osg::ref_ptr<Geometry> input = GeometryUtils::geometryFromGeoJSON(geomJSON);
            if ( !input.valid() )
                return DUK_RET_TYPE_ERROR;
        
            // arg#1 : distance
            double distance = duk_get_number(ctx, 1);

            // run the buffer op:
            osg::ref_ptr<Geometry> output;
            BufferParameters p;
            p._cornerSegs = 0; // speed it up
            p._joinStyle  = p.JOIN_ROUND;
            p._capStyle   = p.CAP_ROUND;
            if ( input->buffer(distance, output, p) )
            {
                duk_push_string(ctx, GeometryUtils::geometryToGeoJSON(output.get()).c_str());
                duk_json_decode(ctx, -1);
            }
            else
            {
                duk_push_undefined(ctx);
            }
            return 1;
        }

        // input: GeoJSON geometry
        // output: bounds object, e.g. { xmin: num, ymin: num, xmax: num, ymax: num }
        static duk_ret_t getBounds(duk_context* ctx)
        {
            if ( !duk_is_object(ctx, 0) ) 
            {
                OE_WARN << LC << "geometry.getBounds(): illegal arguments" << std::endl;
                return DUK_RET_TYPE_ERROR;
            }

            // arg#0 : geometry
            std::string geomJSON = duk_json_encode(ctx, 0);
            osg::ref_ptr<Geometry> input = GeometryUtils::geometryFromGeoJSON(geomJSON);
            if ( !input.valid() )
                return DUK_RET_TYPE_ERROR;

            Bounds b = input->getBounds();

            duk_push_object(ctx);
            duk_push_number(ctx, b.xMin());
            duk_put_prop_string(ctx, -2, "xmin");
            duk_push_number(ctx, b.yMin());
            duk_put_prop_string(ctx, -2, "ymin");
            duk_push_number(ctx, b.xMax());
            duk_put_prop_string(ctx, -2, "xmax");
            duk_push_number(ctx, b.yMax());
            duk_put_prop_string(ctx, -2, "ymax");
            duk_push_number(ctx, area2d(b));
            duk_put_prop_string(ctx, -2, "area");

            return 1;
        }

        // input: type name
        // output: new geometry
        static duk_ret_t cloneAs(duk_context* ctx)
        {
            // arg#0 : geometry
            std::string geomJSON = duk_json_encode(ctx, 0);
            osg::ref_ptr<Geometry> input = GeometryUtils::geometryFromGeoJSON(geomJSON);
            if ( !input.valid() )
                return DUK_RET_TYPE_ERROR;
        
            // arg#1 : type
            std::string typeName = osgEarth::toLower( duk_get_string(ctx, 1) );
            Geometry::Type type;
            if ( typeName == "point")
                type = Geometry::TYPE_POINT;
            else if (typeName == "multipoint" )
                type = Geometry::TYPE_POINTSET;
            else if (typeName == "linestring" || typeName == "multilinestring")
                type = Geometry::TYPE_LINESTRING;
            else //if (typeName == "polygon")
                type = Geometry::TYPE_POLYGON;

            // run the buffer op:
            osg::ref_ptr<Geometry> output = input->cloneAs(type);
            if ( output.valid() )
            {
                duk_push_string(ctx, GeometryUtils::geometryToGeoJSON(output.get()).c_str());
                duk_json_decode(ctx, -1);
            }
            else
            {
                duk_push_undefined(ctx);
            }
            return 1;
        }
    };

} } } // namespace osgEarth::Drivers::Duktape

#endif // OSGEARTHDRIVERS_DUKTAPE_JS_GEOMETRY_H
