/*
 * Software License Agreement (BSD License)
 *
 *  Point Cloud Library (PCL) - www.pointclouds.org
 *  Copyright (c) 2012-, Open Perception, Inc.
 *
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the copyright holder(s) nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 *
 */

#pragma once

#include <iostream>
#include <map>
#include <vector>
#include <pcl/pcl_exports.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkSmartPointer.h>
#include <vtkObjectFactory.h>
#include <vtkContext2D.h>
#include <vtkTransform2D.h>
#include <vtkContextItem.h>
#include <vtkContextView.h>
#include <vtkContextScene.h>
#include <vtkPen.h>
#include <vtkBrush.h>
#include <vtkTextProperty.h>
#include <vtkOpenGLContextDevice2D.h>
#include <vtkPoints2D.h>
#include "vtkCommand.h"

namespace pcl
{
  namespace visualization
  {

   /** \brief Abstract class for storing figure information. All the derived class uses the same method draw() to invoke different drawing function of vtkContext2D
     * \author Kripasindhu Sarkar
     * \ingroup visualization
     */
    struct Figure2D
    {
      std::vector<float> info_;     //information stored in a general form for every object
      vtkPen *pen_;                 //the corresponding pen and brush for the figure
      vtkBrush *brush_;
      vtkTransform2D *transform_;
      
      Figure2D (std::vector<float> info, vtkPen *p, vtkBrush * b, vtkTransform2D *t)
      {
        this->pen_ = vtkPen::New ();
        this->brush_ = vtkBrush::New ();
        this->transform_ = vtkTransform2D::New();

        this->pen_->DeepCopy (p);
        this->brush_->DeepCopy (b);
        this->transform_->SetMatrix (t->GetMatrix());
        this->info_ = info; //note: it copies :-)
      }

      Figure2D (vtkPen *p, vtkBrush * b, vtkTransform2D *t)
      {
        this->pen_ = vtkPen::New ();
        this->brush_ = vtkBrush::New ();
        this->transform_ = vtkTransform2D::New();

        this->pen_->DeepCopy (p);
        this->brush_->DeepCopy (b);
        this->transform_->SetMatrix (t->GetMatrix());
      }
      
      void applyInternals (vtkContext2D *painter) const
      {
        painter->ApplyPen (pen_);
        painter->ApplyBrush (brush_);
        painter->GetDevice ()->SetMatrix (transform_->GetMatrix());
      }
		  
      virtual void draw (vtkContext2D *) {}
    };
    
   /** \brief Class for PolyLine
     */
    struct FPolyLine2D : public Figure2D
    {

      FPolyLine2D (std::vector<float> info, vtkPen *p, vtkBrush * b, vtkTransform2D *t) : Figure2D (info, p, b, t){}

      void draw (vtkContext2D * painter) override
      {
        applyInternals(painter);  
        painter->DrawPoly (&info_[0], static_cast<unsigned int> (info_.size ()) / 2);
      }
    };

   /** \brief Class for storing Points
     */
    struct FPoints2D : public Figure2D
    {

      FPoints2D (std::vector<float> info, vtkPen *p, vtkBrush * b, vtkTransform2D *t) : Figure2D (info, p, b, t) {}

      void draw (vtkContext2D * painter) override
      {
        applyInternals(painter);  
        painter->DrawPoints (&info_[0], static_cast<unsigned int> (info_.size ()) / 2);
      }
    };

   /** \brief Class for storing Quads
     */
    struct FQuad2D : public Figure2D
    {

      FQuad2D (std::vector<float> info, vtkPen *p, vtkBrush * b, vtkTransform2D *t) : Figure2D (info, p, b, t) {}

      void draw (vtkContext2D * painter) override
      {
        applyInternals(painter);  
        painter->DrawQuad (&info_[0]);
      }
    };
    
    /** \brief Class for Polygon
     */
    struct FPolygon2D : public Figure2D
    {

      FPolygon2D (std::vector<float> info, vtkPen *p, vtkBrush * b, vtkTransform2D *t) : Figure2D (info, p, b, t){}

      void draw (vtkContext2D * painter) override
      {
        applyInternals(painter);  
        painter->DrawPolygon (&info_[0], static_cast<unsigned int> (info_.size ()) / 2);
      }
    };
    
   /** \brief Class for storing EllipticArc; every ellipse , circle are covered by this
     */
    struct FEllipticArc2D : public Figure2D
    {

      FEllipticArc2D (std::vector<float> info, vtkPen *p, vtkBrush * b, vtkTransform2D *t) : Figure2D (info, p, b, t) {}

      FEllipticArc2D (float x, float y, float rx, float ry, float sa, float ea, vtkPen *p, vtkBrush * b, vtkTransform2D *t) : Figure2D (p, b, t)
      {
        info_.resize (6);
        info_[0] = x;
        info_[1] = y;
        info_[2] = rx;
        info_[3] = ry;
        info_[4] = sa;
        info_[5] = ea;
      }

      void draw (vtkContext2D * painter) override
      {
        applyInternals(painter);  
        painter->DrawEllipticArc (info_[0], info_[1], info_[2], info_[3], info_[4], info_[5]);
      }
    };


    ////////////////////////////////////The Main Painter Class begins here//////////////////////////////////////
    /** \brief PCL Painter2D main class. Class for drawing 2D figures
     * \author Kripasindhu Sarkar
     * \ingroup visualization
     */
    class PCL_EXPORTS PCLPainter2D: public vtkContextItem
    {
    public:

      //static PCLPainter2D *New();
      
      /** \brief Constructor of the class
       */
      PCLPainter2D (char const * name = "PCLPainter2D");
      vtkTypeMacro (PCLPainter2D, vtkContextItem);

      /** \brief Paint event for the chart, called whenever the chart needs to be drawn
       *  \param[in] painter Name of the window
       */
      bool 
      Paint (vtkContext2D *painter) override;

      /** \brief Draw a line between the specified points.
       * \param[in] x1 X coordinate of the starting point of the line
       * \param[in] y1 Y coordinate of the starting point of the line
       * \param[in] x2 X coordinate of the ending point of the line
       * \param[in] y2 Y coordinate of the ending point of the line
       */
      void 
      addLine (float x1, float y1, float x2, float y2);
      
      /** \brief Draw line(s) between the specified points 
       *  \param[in] p a vector of size 2*n and the points are packed x1, y1, x2, y2 etc.
       */
      void 
      addLine (std::vector<float> p);

      
      /** \brief Draw specified point(s).
       * \param[in] x X coordinate of the point
       * \param[in] y Y coordinate of the point
       */      
      void 
      addPoint (float x, float y);
      /** \brief Draw specified point(s).
       * \param[in] points a vector of size 2*n and the points are packed x1, y1, x2, y2 etc.
       */
      
      void 
      addPoints (std::vector<float> points);
      
      
      /** \brief Draw a rectangle based on the given points
       * \param[in] x X coordinate of the origin
       * \param[in] y Y coordinate of the origin
       * \param[in] width width of the rectangle
       * \param[in] height height of the rectangle
       */
      void 
      addRect (float x, float y, float width, float height);
      
      /** \brief Draw a quadrilateral based on the given points
       * \param[in] p a vector of size 8 and the points are packed x1, y1, x2, y2, x3, y3 and x4, y4.
       */
      void 
      addQuad (std::vector<float> p);
      
        /** \brief Draw a polygon between the specified points 
       *  \param[in] p a vector of size 2*n and the points are packed x1, y1, x2, y2 etc.
       */
      void 
      addPolygon (std::vector<float> p);

      
      /** \brief Draw an ellipse based on the inputs
       * \param[in] x X coordinate of the origin
       * \param[in] y Y coordinate of the origin
       * \param[in] rx X radius of the ellipse
       * \param[in] ry Y radius of the ellipse
       */
      void 
      addEllipse (float x, float y, float rx, float ry);
      
      /** \brief Draw a circle based on the inputs
       * \param[in] x X coordinate of the origin
       * \param[in] y Y coordinate of the origin
       * \param[in] r radius of the circle
       */
      void 
      addCircle (float x, float y, float r);
      
      /** \brief Draw an elliptic arc based on the inputs
       * \param[in] x X coordinate of the origin
       * \param[in] y Y coordinate of the origin
       * \param[in] rx X radius of the ellipse
       * \param[in] ry Y radius of the ellipse
       * \param[in] start_angle the starting angle of the arc expressed in degrees
       * \param[in] end_angle the ending angle of the arc expressed in degrees
       */
      void 
      addEllipticArc (float x, float y, float rx, float ry, float start_angle, float end_angle);
      
      /** \brief Draw an arc based on the inputs
       * \param[in] x X coordinate of the origin
       * \param[in] y Y coordinate of the origin
       * \param[in] r radius of the circle
       * \param[in] start_angle the starting angle of the arc expressed in degrees
       * \param[in] end_angle the ending angle of the arc expressed in degrees
       */
      void 
      addArc (float x, float y, float r, float start_angle, float end_angle);


      /** \brief Create a translation matrix and concatenate it with the current transformation.
       * \param[in] x translation along X axis
       * \param[in] y translation along Y axis
       */
      void 
      translatePen (double x, double y);
      
      /** \brief Create a rotation matrix and concatenate it with the current transformation.
       * \param[in] angle angle in degrees
       */
      void 
      rotatePen(double angle);
      
      /** \brief Create a scale matrix and concatenate it with the current transformation.
       * \param[in] x translation along X axis
       * \param[in] y translation along Y axis
       */
      void 
      scalePen(double x, double y);
      
      /** \brief Create a translation matrix and concatenate it with the current transformation.
       * \param[in] matrix the transformation matrix
       */
      void 
      setTransform(vtkMatrix3x3 *matrix);
      
      /** \brief Returns the current transformation matrix.
       */
      vtkMatrix3x3 * 
      getTransform();
      
      /** \brief Clears all the transformation applied. Sets the transformation matrix to Identity
       */
      void 
      clearTransform();
      
      /** \brief remove all the figures from the window
       */
       void
       clearFigures();

      /** \brief set/get methods for current working vtkPen
       */
      void setPenColor (unsigned char r, unsigned char g, unsigned char b, unsigned char a);
      void setPenWidth (float w);
      void setPenType (int type);

      /** \brief set/get methods for current working vtkPen
       */
      unsigned char* getPenColor ();
      float getPenWidth ();
      int getPenType ();
      void setPen (vtkPen *pen);
      vtkPen* getPen ();

      /** \brief set/get methods for current working vtkBrush
       */
      void setBrush (vtkBrush *brush);
      vtkBrush* getBrush ();
      void setBrushColor (unsigned char r, unsigned char g, unsigned char b, unsigned char a);
      unsigned char* getBrushColor ();

      /** \brief set/get method for the viewport's background color.
       * \param[in] r the red component of the RGB color
       * \param[in] g the green component of the RGB color
       * \param[in] b the blue component of the RGB color
       */
      void
      setBackgroundColor (const double r, const double g, const double b);

      /** \brief set/get method for the viewport's background color.
       * \param [in] color the array containing the 3 component of the RGB color
       */
      void
      setBackgroundColor (const double color[3]);

      /** \brief set/get method for the viewport's background color.
       * \return [out] color the array containing the 3 component of the RGB color
       */
      double *
      getBackgroundColor ();


      /** \brief set/get method for the window size.
       * \param[in] w the width of the window
       * \param[in] h the height of the window
       */
      void
      setWindowSize (int w, int h);

      /** \brief set/get method for the window size.
       * \return[in] array containing the width and height of the window
       */
      int *
      getWindowSize () const;

      /** \brief displays all the figures added in a window.
       */    
      void display ();
      
      /** \brief spins (runs the event loop) the interactor for spin_time amount of time. The name is confusing and will be probably obsolete in the future release with a single overloaded spin()/display() function.
        *  \param[in] spin_time - How long (in ms) should the visualization loop be allowed to run.
        */
      void spinOnce ( const int spin_time = 0 );
        
      /** \brief spins (runs the event loop) the interactor indefinitely. Same as display() - added to retain the similarity between other existing visualization classes
       */
      void spin ();

    private:
      //std::map< int, std::vector< std::vector<float> > > figures_; //FIG_TYPE -> std::vector<array>

      //All the figures drawn till now gets stored here
      std::vector<Figure2D *> figures_;
    
      //state variables of the class
      vtkPen *current_pen_;
      vtkBrush *current_brush_;
      vtkTransform2D *current_transform_;
      int win_width_, win_height_;
      double bkg_color_[3];

      vtkContextView *view_;
      
      //####event callback class####
        struct ExitMainLoopTimerCallback : public vtkCommand
        {
          static ExitMainLoopTimerCallback* New ()
          {
            return (new ExitMainLoopTimerCallback);
          }
          void 
          Execute (vtkObject* vtkNotUsed (caller), unsigned long event_id, void* call_data) override
          {
            if (event_id != vtkCommand::TimerEvent)
              return;
            int timer_id = *(reinterpret_cast<int*> (call_data));

            if (timer_id != right_timer_id)
              return;

            // Stop vtk loop and send notification to app to wake it up
            interactor->TerminateApp ();
          }
          int right_timer_id;
          vtkRenderWindowInteractor *interactor;
        };
        
        /** \brief Callback object enabling us to leave the main loop, when a timer fires. */
        vtkSmartPointer<ExitMainLoopTimerCallback> exit_loop_timer_;
    };

  }
}
