/*
 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#ifndef SPATIAL_HANDLER_H_
#define SPATIAL_HANDLER_H_

#include <gdal/ogrsf_frmts.h>
#include <gdal/ogr_api.h>
#include <gdal/gdal_pam.h>
#include <gdal/memdataset.h>
#include <gdal/gdal_alg.h>
#include <gdal/gdal.h>
#include <deque>
#include "base/geometry.h"
#include "wbpublic_public_interface.h"

#include "mdc.h"
/* Spatial Object Model

 Feature - corresponds to the value of a single geometry column of a row in a resultset
 Identified by the layer (resultset) and row_id and may contain one or more attributes, which are the rest
 of the columns of the resultset.

 Layer - corresponds to a single resutset or data source to be displayed.
 Can be toggled to be shown or not.
 Contains a list of features that are part of that layer.
 Should allow identifying the feature that is located at a specific coordinate.
 */

namespace spatial
{

  struct WBPUBLICBACKEND_PUBLIC_FUNC ProjectionView
  {
    int width;
    int height;
    double MaxLat;
    double MaxLon;
    double MinLat;
    double MinLon;
    friend bool operator== (const ProjectionView &v1, const ProjectionView &v2);
    friend bool operator!= (const ProjectionView &v1, const ProjectionView &v2);
  };

  class WBPUBLICBACKEND_PUBLIC_FUNC Envelope
  {
  public:
    Envelope();
    Envelope(double left, double top, double right, double bottom);
    bool converted;
    base::Point top_left;
    base::Point bottom_right;
    friend bool operator == (const Envelope &env1, const Envelope &env2);
    friend bool operator != (const Envelope &env1, const Envelope &env2);
    bool is_init();
    bool within(const base::Point &p) const;
  };

  bool operator== (const ProjectionView &v1, const ProjectionView &v2);
  bool operator!= (const ProjectionView &v1, const ProjectionView &v2);
  bool operator== (const Envelope &env1, const Envelope &env2);
  bool operator!= (const Envelope &env1, const Envelope &env2);

  enum ProjectionType
  {
    ProjMercator = 1, ProjEquirectangular = 2, ProjRobinson = 3, ProjBonne = 4, ProjGeodetic = 5
  };

  enum ShapeType
  {
    ShapeUnknown, ShapePoint, ShapeLineString, ShapeLinearRing, ShapePolygon
  };

  std::string shape_description(ShapeType shp);


  enum AxisType
  {
    AxisLat = 1, AxisLon = 2
  };

  class WBPUBLICBACKEND_PUBLIC_FUNC ShapeContainer
  {

  protected:
    double distance_linearring(const base::Point &p) const;
    double distance_line(const std::vector<base::Point> &point_list, const base::Point &p) const;
    double distance_polygon(const base::Point &p) const;
    double distance_point(const base::Point &p) const;
  public:
    ShapeContainer();
    ShapeType type;
    std::vector<base::Point> points;
    Envelope bounding_box;
    double distance(const base::Point &p) const;
  };

  class WBPUBLICBACKEND_PUBLIC_FUNC Projection
  {
  protected:
    OGRSpatialReference _mercator_srs;
    OGRSpatialReference _equirectangular_srs;
    OGRSpatialReference _robinson_srs;
    OGRSpatialReference _geodetic_srs;
    OGRSpatialReference _bonne_srs;

  public:
    static Projection& get_instance();
    bool check_libproj_availability();
    OGRSpatialReference* get_projection(ProjectionType);
  private:
    Projection();
    Projection(Projection const&);
    void operator=(Projection const&);

  };


  class WBPUBLICBACKEND_PUBLIC_FUNC Importer
  {
    OGRGeometry *_geometry;
    bool _interrupt;
    void extract_points(OGRGeometry *shape, std::deque<ShapeContainer> &shapes_container);
  public:
    Importer();
    ~Importer();
    int import_from_mysql(const std::string &data);
    int import_from_wkt(std::string data);
    void get_points(std::deque<ShapeContainer> &shapes_container);
    void get_envelope(Envelope &env);
    void interrupt();

    std::string as_wkt();
    std::string as_kml();
    std::string as_json();
    std::string as_gml();

    OGRGeometry *steal_data();
  };

  class WBPUBLICBACKEND_PUBLIC_FUNC Converter
  {
    base::RecMutex _projection_protector;
    double _adf_projection[6];
    double _inv_projection[6];
    OGRCoordinateTransformation *_geo_to_proj;
    OGRCoordinateTransformation *_proj_to_geo;
    OGRSpatialReference *_source_srs;
    OGRSpatialReference *_target_srs;
    ProjectionView _view;
    bool _interrupt;
  public:
    Converter(ProjectionView view, OGRSpatialReference *src_srs, OGRSpatialReference *dst_srs);
    ~Converter();
    void change_projection(OGRSpatialReference *src_srs = NULL, OGRSpatialReference *dst_srs = NULL);
    void change_projection(ProjectionView view, OGRSpatialReference *src_srs = NULL, OGRSpatialReference *dst_srs = NULL);
    void from_projected(double lat, double lon, int &x, int &y);
    void to_projected(int x, int y, double &lat, double &lon);

    bool to_latlon(int x, int y, double &lat, double &lon);
    bool from_latlon(double lat, double lon, int &x, int &y);

    bool from_latlon_to_proj(double &lat, double &lon);
    bool from_proj_to_latlon(double &lat, double &lon);
    static std::string dec_to_dms(double angle, AxisType axis, int precision);
    void transform_points(std::deque<ShapeContainer> &shapes_container);
    void transform_envelope(spatial::Envelope &env);
    void interrupt();
  };


  class Layer;

  class WBPUBLICBACKEND_PUBLIC_FUNC Feature
  {
    Layer *_owner;
    int _row_id;
    Importer _geometry;
    std::deque<ShapeContainer> _shapes;
    spatial::Envelope _env_screen;
  public:
    Feature(Layer *layer, int row_id, const std::string &data, bool wkt);
    ~Feature();

    void interrupt();
    void get_envelope(spatial::Envelope &env, const bool &screen_coords = false);
    void render(spatial::Converter *converter);
    void repaint(mdc::CairoCtx &cr, float scale, const base::Rect &clip_area, base::Color fill_color=base::Color::Invalid());

    int row_id() const { return _row_id; }
    double distance(const base::Point &p, const double &allowed_distance = 4.0);
  };

  typedef int LayerId;
  WBPUBLICBACKEND_PUBLIC_FUNC LayerId new_layer_id();

  class WBPUBLICBACKEND_PUBLIC_FUNC Layer
  {
    friend class Feature;

  protected:
    std::deque<Feature*> _features;

    LayerId _layer_id;
    base::Color _color;
    float _render_progress;
    bool _show;
    bool _interrupt;
    spatial::Envelope _spatial_envelope;
    bool _fill_polygons;

  public:
    Layer(LayerId layer_id, base::Color color);
    virtual ~Layer();

    virtual void load_data() {}

    void interrupt();

    bool hidden();
    LayerId layer_id();

    void set_show(bool flag);

    size_t size() { return _features.size(); }

    base::Color color() { return _color; }
    bool fill() { return _fill_polygons; }

    void add_feature(int row_id, const std::string &geom_data, bool wkt);
    virtual void render(spatial::Converter *converter);
    spatial::Feature *feature_closest(const base::Point &p, const double &allowed_distance = 4.0);
    void set_fill_polygons(bool fill);
    bool get_fill_polygons();
    virtual void repaint(mdc::CairoCtx &cr, float scale, const base::Rect &clip_area);
    float query_render_progress();
    spatial::Envelope get_envelope();
  };
};
#endif /* SPATIAL_HANDLER_H_ */
