/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2015 Artem Pavlenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

#ifndef RASTER_FEATURESET_HPP
#define RASTER_FEATURESET_HPP

#include "raster_datasource.hpp"
#include "raster_info.hpp"

// mapnik
#include <mapnik/feature.hpp>
#include <mapnik/debug.hpp>

// stl
#include <vector>

// boost
#include <boost/utility.hpp>

class single_file_policy
{
    raster_info info_;
public:
    class const_iterator
    {
        enum iterator_e {start,end};
        bool status_;
        const single_file_policy* p_;
    public:
        explicit const_iterator(const single_file_policy* p)
            : status_(start),
              p_(p) {}

        const_iterator()
            : status_(end) {}

        const_iterator(const const_iterator& other)
            : status_(other.status_),
              p_(other.p_) {}

        const_iterator& operator++()
        {
            status_ = end;
            return *this;
        }

        const raster_info& operator*() const
        {
            return p_->info_;
        }

        const raster_info* operator->() const
        {
            return &(p_->info_);
        }

        bool operator!=(const const_iterator& itr)
        {
            return status_ != itr.status_;
        }
    };

    explicit single_file_policy(const raster_info& info)
        : info_(info) {}

    const_iterator begin()
    {
        return const_iterator(this);
    }

    const_iterator query(const box2d<double>& box)
    {
        if (box.intersects(info_.envelope()))
        {
            return begin();
        }
        return end();
    }

    const_iterator end()
    {
        return const_iterator();
    }

    inline int img_width(int reader_width) const
    {
        return reader_width;
    }

    inline int img_height(int reader_height) const
    {
        return reader_height;
    }

    inline box2d<double> transform(box2d<double> &) const
    {
        return box2d<double>(0, 0, 0, 0);
    }
};

class tiled_file_policy
{
public:

    using const_iterator = std::vector<raster_info>::const_iterator;

    tiled_file_policy(std::string const& file,
                      std::string const& format,
                      unsigned tile_size,
                      box2d<double> const& extent,
                      box2d<double> const& bbox,
                      unsigned width,
                      unsigned height)
    {
        double lox = extent.minx();
        double loy = extent.miny();

        int max_x = int(std::ceil(double(width) / double(tile_size)));
        int max_y = int(std::ceil(double(height) / double(tile_size)));

        double pixel_x = extent.width() / double(width);
        double pixel_y = extent.height() / double(height);

        MAPNIK_LOG_DEBUG(raster) << "tiled_file_policy: Raster Plugin PIXEL SIZE("<< pixel_x << "," << pixel_y << ")";

        box2d<double> e = bbox.intersect(extent);

        for (int x = 0; x < max_x; ++x)
        {
            for (int y = 0; y < max_y; ++y)
            {
                double x0 = lox + x * tile_size * pixel_x;
                double y0 = loy + y * tile_size * pixel_y;
                double x1 = x0 + tile_size * pixel_x;
                double y1 = y0 + tile_size * pixel_y;

                if (e.intersects(box2d<double>(x0, y0, x1, y1)))
                {
                    box2d<double> tile_box = e.intersect(box2d<double>(x0,y0,x1,y1));
                    raster_info info(file,format,tile_box,tile_size,tile_size);
                    infos_.push_back(info);
                }
            }
        }

        MAPNIK_LOG_DEBUG(raster) << "tiled_file_policy: Raster Plugin INFO SIZE=" << infos_.size() << " " << file;
    }

    const_iterator begin()
    {
        return infos_.begin();
    }

    const_iterator end()
    {
        return infos_.end();
    }

    inline int img_width(int reader_width) const
    {
        return reader_width;
    }

    inline int img_height(int reader_height) const
    {
        return reader_height;
    }

    inline box2d<double> transform(box2d<double>&) const
    {
        return box2d<double>(0, 0, 0, 0);
    }

private:

    std::vector<raster_info> infos_;
};

class tiled_multi_file_policy
{
public:

    using const_iterator = std::vector<raster_info>::const_iterator;

    tiled_multi_file_policy(std::string const& file_pattern,
                            std::string const& format,
                            unsigned tile_size,
                            box2d<double> extent,
                            box2d<double> bbox,
                            unsigned width,
                            unsigned height,
                            unsigned tile_stride)
        : image_width_(width),
          image_height_(height),
          tile_size_(tile_size),
          tile_stride_(tile_stride)
    {
        double lox = extent.minx();
        double loy = extent.miny();

        //int max_x = int(std::ceil(double(width) / double(tile_size)));
        //int max_y = int(std::ceil(double(height) / double(tile_size)));

        double pixel_x = extent.width() / double(width);
        double pixel_y = extent.height() / double(height);

        MAPNIK_LOG_DEBUG(raster) << "tiled_multi_file_policy: Raster Plugin PIXEL SIZE(" << pixel_x << "," << pixel_y << ")";

        // intersection of query with extent => new query
        box2d<double> e = bbox.intersect(extent);

        const int x_min = int(std::floor((e.minx() - lox) / (tile_size * pixel_x)));
        const int y_min = int(std::floor((e.miny() - loy) / (tile_size * pixel_y)));
        const int x_max = int(std::ceil((e.maxx() - lox) / (tile_size * pixel_x)));
        const int y_max = int(std::ceil((e.maxy() - loy) / (tile_size * pixel_y)));

        for (int x = x_min; x < x_max; ++x)
        {
            for (int y = y_min; y < y_max; ++y)
            {
                // x0, y0, x1, y1 => projection-space image coordinates.
                double x0 = lox + x*tile_size*pixel_x;
                double y0 = loy + y*tile_size*pixel_y;
                double x1 = x0 + tile_size*pixel_x;
                double y1 = y0 + tile_size*pixel_y;

                // check if it intersects the query
                if (e.intersects(box2d<double>(x0,y0,x1,y1)))
                {
                    // tile_box => intersection of tile with query in projection-space.
                    box2d<double> tile_box = e.intersect(box2d<double>(x0,y0,x1,y1));
                    std::string file = interpolate(file_pattern, x, y);
                    raster_info info(file,format,tile_box,tile_size,tile_size);
                    infos_.push_back(info);
                }
            }
        }

        MAPNIK_LOG_DEBUG(raster) << "tiled_multi_file_policy: Raster Plugin INFO SIZE=" << infos_.size() << " " << file_pattern;
    }

    const_iterator begin()
    {
        return infos_.begin();
    }

    const_iterator end()
    {
        return infos_.end();
    }

    inline int img_width(int) const
    {
        return image_width_;
    }

    inline int img_height(int) const
    {
        return image_height_;
    }

    inline box2d<double> transform(box2d<double>& box) const
    {
        int x_offset = int(std::floor(box.minx() / tile_size_));
        int y_offset = int(std::floor(box.miny() / tile_size_));
        box2d<double> rem(x_offset * tile_size_,
                          y_offset * tile_size_,
                          x_offset * tile_size_,
                          y_offset * tile_size_);
        box.init(box.minx() - rem.minx(),
                 box.miny() - rem.miny(),
                 box.maxx() - rem.maxx(),
                 box.maxy() - rem.maxy());
        return rem;
    }

private:

    std::string interpolate(std::string const& pattern, int x, int y) const;

    unsigned int image_width_, image_height_, tile_size_, tile_stride_;
    std::vector<raster_info> infos_;
};

template <typename LookupPolicy>
class raster_featureset : public mapnik::Featureset
{
    using iterator_type = typename LookupPolicy::const_iterator;

public:
    raster_featureset(LookupPolicy const& policy,
                      box2d<double> const& exttent,
                      mapnik::query const& q);
    virtual ~raster_featureset();
    mapnik::feature_ptr next();

private:
    LookupPolicy policy_;
    mapnik::value_integer feature_id_;
    mapnik::context_ptr ctx_;
    mapnik::box2d<double> extent_;
    mapnik::box2d<double> bbox_;
    iterator_type curIter_;
    iterator_type endIter_;
};

#endif // RASTER_FEATURESET_HPP