/* osgEarth
 * Copyright 2008-2014 Pelican Mapping
 * MIT License
 */
#include "JoinFeatureFilterOptions"

#include <osgEarth/Filter>
#include <osgEarth/FeatureCursor>

#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgEarth/Registry>
#include <osgEarth/ImageUtils>
#include <osgEarth/FeatureSource>
#include <osgEarth/FilterContext>
#include <osgEarth/Geometry>
#include <osgEarth/Metrics>

#define LC "[Intersect FeatureFilter] "

using namespace osgEarth;
using namespace osgEarth::Drivers;


/**
 * Spatial Join: This feature finds all the features in a secondary feature source
 * that intersect the input feature's extent, and adds their attributes to the
 * input feature.
 */
class JoinFeatureFilter : public FeatureFilter, public JoinFeatureFilterOptions
{
public:
    JoinFeatureFilter(const ConfigOptions& options)
        : FeatureFilter(), JoinFeatureFilterOptions(options)
    {
    }

public: // FeatureFilter

    Status initialize(const osgDB::Options* readOptions)
    {
        Status fsStatus = featureSource().open(readOptions);
        if (fsStatus.isError())
            return fsStatus;

        return Status::OK();
    }

    /**
     * Gets all the features that intersect the extent
     */
    void getFeatures(const GeoExtent& extent, FeatureList& features, ProgressCallback* progress)
    {
        FeatureSource* fs = featureSource().getLayer();
        if (!fs)
            return;

        OE_PROFILING_ZONE;

        //TODO: should this be Profile::transformAndClampExtent instead?
        GeoExtent extentInFeatureSRS = extent.transform( fs->getFeatureProfile()->getSRS() );

        if (extentInFeatureSRS.intersects( fs->getFeatureProfile()->getExtent()))
        {
            Query query;
            query.bounds() = extentInFeatureSRS.bounds();

            osg::ref_ptr<FeatureCursor> cursor = fs->createFeatureCursor(query, {}, nullptr, progress);
            if (cursor)
            {
                cursor->fill( features );
            }
        }     
    }

    //! Joins the boundaries attributes into the features by doing a
    //! spatial intersection.
    void combine(FeatureList& boundaries, FeatureList& input, FilterContext& context) const
    {
        OE_PROFILING_ZONE;
        

        // Transform the boundaries into the coordinate system of the features
        // for fast intersection testing
        for (auto& boundary : boundaries)
        {
            boundary->transform(context.featureProfile()->getSRS());
        }

        // For each feature, check for a spatial join:
        for (auto& feature : input)
        {
            if (feature.valid() && feature->getGeometry())
            {
                if (*rough())
                {
                    osg::ref_ptr< Feature> boundary = boundaries[0].get();
                    // Copy the attributes from the boundary to the feature (and overwrite)
                    for (const auto& attr : boundary->getAttrs())
                    {
                        feature->set(attr.first, attr.second);
                    }
                }
                else
                {
                    for (const auto& boundary : boundaries)
                    {
                        if (boundary->getGeometry()->intersects(feature->getGeometry()))
                        {
                            // Copy the attributes from the boundary to the feature (and overwrite)
                            for (const auto& attr : boundary->getAttrs())
                            {
                                feature->set(attr.first, attr.second);
                            }

                            // upon success, don't check any more boundaries:
                            break;
                        }
                    }
                }
            }
        }
    }


    FilterContext push(FeatureList& input, FilterContext& context)
    {
        OE_PROFILING_ZONE;

        if (featureSource().getLayer())
        {
            // Get any features that intersect this query.
            FeatureList boundaries;
            getFeatures(context.workingExtent().get(), boundaries, nullptr); // TODO: progress...

            if (!boundaries.empty())
            {
                combine(boundaries, input, context);
            }
        }

        return context;
    }
};


class JoinFeatureFilterPlugin : public FeatureFilterDriver
{
public:
    JoinFeatureFilterPlugin() : FeatureFilterDriver()
    {
        this->supportsExtension("osgearth_featurefilter_join", className() );
    }
    
    const char* className() const
    {
        return "JoinFeatureFilterPlugin";
    }

    ReadResult readObject(const std::string& file_name, const Options* options) const
    {
        if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
            return ReadResult::FILE_NOT_HANDLED;

        return new JoinFeatureFilter( getConfigOptions(options) );
    }
};

REGISTER_OSGPLUGIN(osgearth_featurefilter_join, JoinFeatureFilterPlugin);


