/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                           License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's 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.
//
//   * The name of the copyright holders may not 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 Intel Corporation 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.
//
//M*/
//################################################################################
//
//                    Created by Nuno Moutinho
//
//################################################################################

#include "precomp.hpp"

namespace cv
{
    namespace plot
    {
        using namespace std;

        class Plot2dImpl CV_FINAL : public Plot2d
        {
            public:

            Plot2dImpl(InputArray plotData)
            {
                Mat _plotData = plotData.getMat();
                //if the matrix is not Nx1 or 1xN
                if(_plotData.cols > 1 && _plotData.rows > 1)
                    CV_Error(Error::StsBadArg, "ERROR: Plot data must be a 1xN or Nx1 matrix.\n");

                CV_Assert(_plotData.type() == CV_64F);

                //in case we have a row matrix than needs to be transposed
                if(_plotData.cols > _plotData.rows)
                {
                    _plotData = _plotData.t();
                }

                plotDataY=_plotData;
                plotDataX = plotDataY*0;
                for (int i=0; i<plotDataY.rows; i++)
                {
                    plotDataX.at<double>(i,0) = i;
                }

                //calling the main constructor
                plotHelper(plotDataX, plotDataY);

            }

            Plot2dImpl(InputArray plotDataX_, InputArray plotDataY_)
            {
                Mat _plotDataX = plotDataX_.getMat();
                Mat _plotDataY = plotDataY_.getMat();
                //f the matrix is not Nx1 or 1xN
                if((_plotDataX.cols > 1 && _plotDataX.rows > 1) || (_plotDataY.cols > 1 && _plotDataY.rows > 1))
                    CV_Error(Error::StsBadArg, "ERROR: Plot data must be a 1xN or Nx1 matrix.\n");

                CV_Assert(_plotDataX.type() == CV_64F && _plotDataY.type() == CV_64F);

                //in case we have a row matrix than needs to be transposed
                if(_plotDataX.cols > _plotDataX.rows)
                {
                    _plotDataX = _plotDataX.t();
                }
                if(_plotDataY.cols > _plotDataY.rows)
                {
                    _plotDataY = _plotDataY.t();
                }

                plotHelper(_plotDataX, _plotDataY);
            }

            //set functions
            void setMinX(double _plotMinX) CV_OVERRIDE
            {
                plotMinX = _plotMinX;
                plotMinX_plusZero = _plotMinX;
            }
            void setMaxX(double _plotMaxX) CV_OVERRIDE
            {
                plotMaxX = _plotMaxX;
                plotMaxX_plusZero = _plotMaxX;
            }
            void setMinY(double _plotMinY) CV_OVERRIDE
            {
                plotMinY = _plotMinY;
                plotMinY_plusZero = _plotMinY;
            }
            void setMaxY(double _plotMaxY) CV_OVERRIDE
            {
                plotMaxY = _plotMaxY;
                plotMaxY_plusZero = _plotMaxY;
            }
            void setPlotLineWidth(int _plotLineWidth) CV_OVERRIDE
            {
                plotLineWidth = _plotLineWidth;
            }
            void setInvertOrientation(bool _invertOrientation) CV_OVERRIDE
            {
                invertOrientation = _invertOrientation;
            }
            void setNeedPlotLine(bool _needPlotLine) CV_OVERRIDE
            {
                needPlotLine = _needPlotLine;
            }
            void setPlotLineColor(Scalar _plotLineColor) CV_OVERRIDE
            {
                plotLineColor=_plotLineColor;
            }
            void setPlotBackgroundColor(Scalar _plotBackgroundColor) CV_OVERRIDE
            {
                plotBackgroundColor=_plotBackgroundColor;
            }
            void setPlotAxisColor(Scalar _plotAxisColor) CV_OVERRIDE
            {
                plotAxisColor=_plotAxisColor;
            }
            void setPlotGridColor(Scalar _plotGridColor) CV_OVERRIDE
            {
                plotGridColor=_plotGridColor;
            }
            void setPlotTextColor(Scalar _plotTextColor) CV_OVERRIDE
            {
                plotTextColor=_plotTextColor;
            }
            void setPlotSize(int _plotSizeWidth, int _plotSizeHeight) CV_OVERRIDE
            {
                if(_plotSizeWidth > 400)
                    plotSizeWidth = _plotSizeWidth;
                else
                    plotSizeWidth = 400;

                if(_plotSizeHeight > 300)
                    plotSizeHeight = _plotSizeHeight;
                else
                    plotSizeHeight = 300;
            }
            void setShowGrid(bool _needShowGrid) CV_OVERRIDE
            {
                needShowGrid = _needShowGrid;
            }
            void setShowText(bool _needShowText) CV_OVERRIDE
            {
                needShowText = _needShowText;
            }
            void setGridLinesNumber(int _gridLinesNumber) CV_OVERRIDE
            {
                if(_gridLinesNumber <= 0)
                    _gridLinesNumber = 1;
                gridLinesNumber = _gridLinesNumber;
            }
            void setPointIdxToPrint(int _cursorPos) CV_OVERRIDE
            {
                if(_cursorPos >= plotDataX.rows || _cursorPos < 0)
                    _cursorPos = plotDataX.rows - 1;
                cursorPos = _cursorPos;
            }
            //render the plotResult to a Mat
            void render(OutputArray _plotResult) CV_OVERRIDE
            {
                //create the plot result
                _plotResult.create(plotSizeHeight, plotSizeWidth, CV_8UC3);
                plotResult = _plotResult.getMat();
                plotResult.setTo(plotBackgroundColor);

                int NumVecElements = plotDataX.rows;

                Mat InterpXdata = linearInterpolation(plotMinX, plotMaxX, 0, plotSizeWidth, plotDataX);
                Mat InterpYdata = invertOrientation ?
                                  linearInterpolation(plotMaxY, plotMinY, 0, plotSizeHeight, plotDataY) :
                                  linearInterpolation(plotMinY, plotMaxY, 0, plotSizeHeight, plotDataY);

                //Find the zeros in image coordinates
                Mat InterpXdataFindZero = linearInterpolation(plotMinX_plusZero, plotMaxX_plusZero, 0, plotSizeWidth, plotDataX_plusZero);
                Mat InterpYdataFindZero = invertOrientation ?
                                          linearInterpolation(plotMaxY_plusZero, plotMinY_plusZero, 0, plotSizeHeight, plotDataY_plusZero) :
                                          linearInterpolation(plotMinY_plusZero, plotMaxY_plusZero, 0, plotSizeHeight, plotDataY_plusZero);

                int ImageXzero = (int)InterpXdataFindZero.at<double>(NumVecElements,0);
                int ImageYzero = (int)InterpYdataFindZero.at<double>(NumVecElements,0);

                double CurrentX = plotDataX.at<double>(cursorPos,0);
                double CurrentY = plotDataY.at<double>(cursorPos,0);

                drawAxis(ImageXzero,ImageYzero, CurrentX, CurrentY, plotAxisColor, plotGridColor);

                if(needPlotLine)
                {
                    //Draw the plot by connecting lines between the points
                    Point p1;
                    p1.x = (int)InterpXdata.at<double>(0,0);
                    p1.y = (int)InterpYdata.at<double>(0,0);

                    for (int r=1; r<InterpXdata.rows; r++)
                    {
                        Point p2;
                        p2.x = (int)InterpXdata.at<double>(r,0);
                        p2.y = (int)InterpYdata.at<double>(r,0);

                        line(plotResult, p1, p2, plotLineColor, plotLineWidth, 8, 0);

                        p1 = p2;
                    }
                }
                else
                {
                    for (int r=0; r<InterpXdata.rows; r++)
                    {
                        Point p;
                        p.x = (int)InterpXdata.at<double>(r,0);
                        p.y = (int)InterpYdata.at<double>(r,0);

                        circle(plotResult, p, 1, plotLineColor, plotLineWidth, 8, 0);
                    }
                }
            }

            protected:

            Mat plotDataX;
            Mat plotDataY;
            Mat plotDataX_plusZero;
            Mat plotDataY_plusZero;
            const char * plotName;

            //dimensions and limits of the plot
            int plotSizeWidth;
            int plotSizeHeight;
            double plotMinX;
            double plotMaxX;
            double plotMinY;
            double plotMaxY;
            double plotMinX_plusZero;
            double plotMaxX_plusZero;
            double plotMinY_plusZero;
            double plotMaxY_plusZero;
            int plotLineWidth;
            bool invertOrientation;
            bool needShowGrid;
            bool needShowText;
            int gridLinesNumber;
            int cursorPos;

            //colors of each plot element
            Scalar plotLineColor;
            Scalar plotBackgroundColor;
            Scalar plotAxisColor;
            Scalar plotGridColor;
            Scalar plotTextColor;

            //the final plot result
            Mat plotResult;

            //flag which enables/disables connection of plotted points by lines
            bool needPlotLine;

            void plotHelper(Mat _plotDataX, Mat _plotDataY)
            {
                plotDataX=_plotDataX;
                plotDataY=_plotDataY;

                int NumVecElements = plotDataX.rows;

                plotDataX_plusZero = Mat::zeros(NumVecElements+1,1,CV_64F);
                plotDataY_plusZero = Mat::zeros(NumVecElements+1,1,CV_64F);

                for(int i=0; i<NumVecElements; i++){
                    plotDataX_plusZero.at<double>(i,0) = plotDataX.at<double>(i,0);
                    plotDataY_plusZero.at<double>(i,0) = plotDataY.at<double>(i,0);
                }

                double MinX;
                double MaxX;
                double MinY;
                double MaxY;
                double MinX_plusZero;
                double MaxX_plusZero;
                double MinY_plusZero;
                double MaxY_plusZero;

                needPlotLine = true;
                invertOrientation = false;

                //Obtain the minimum and maximum values of Xdata
                minMaxLoc(plotDataX,&MinX,&MaxX);

                //Obtain the minimum and maximum values of Ydata
                minMaxLoc(plotDataY,&MinY,&MaxY);

                //Obtain the minimum and maximum values of Xdata plus zero
                minMaxLoc(plotDataX_plusZero,&MinX_plusZero,&MaxX_plusZero);

                //Obtain the minimum and maximum values of Ydata plus zero
                minMaxLoc(plotDataY_plusZero,&MinY_plusZero,&MaxY_plusZero);

                //setting the min and max values for each axis
                plotMinX = MinX;
                plotMaxX = MaxX;
                plotMinY = MinY;
                plotMaxY = MaxY;
                plotMinX_plusZero = MinX_plusZero;
                plotMaxX_plusZero = MaxX_plusZero;
                plotMinY_plusZero = MinY_plusZero;
                plotMaxY_plusZero = MaxY_plusZero;

                //setting the default size of a plot figure
                setPlotSize(600, 400);

                //setting the default plot line size
                setPlotLineWidth(1);

                //setting default colors for the different elements of the plot
                setPlotAxisColor(Scalar(0, 0, 255));
                setPlotGridColor(Scalar(255, 255, 255));
                setPlotBackgroundColor(Scalar(0, 0, 0));
                setPlotLineColor(Scalar(0, 255, 255));
                setPlotTextColor(Scalar(255, 255, 255));
                setShowGrid(true);
                setShowText(true);
                setGridLinesNumber(10);
                setPointIdxToPrint(-1);
            }

            void drawAxis(int ImageXzero, int ImageYzero, double CurrentX, double CurrentY, Scalar axisColor, Scalar gridColor)
            {
                if(needShowText)
                {
                    drawValuesAsText(0, ImageXzero, ImageYzero, 10, 20);
                    drawValuesAsText(0, ImageXzero, ImageYzero, -20, 20);
                    drawValuesAsText(0, ImageXzero, ImageYzero, 10, -10);
                    drawValuesAsText(0, ImageXzero, ImageYzero, -20, -10);
                    drawValuesAsText((format("X_%d = ", cursorPos) + "%g").c_str(), CurrentX, 0, 0, 40, 20);
                    drawValuesAsText((format("Y_%d = ", cursorPos) + "%g").c_str(), CurrentY, 0, 20, 40, 20);
                }

                //Horizontal X axis and equispaced horizontal lines
                int LineSpace = cvRound(plotSizeHeight / (float)gridLinesNumber);
                int TraceSize = 5;
                drawLine(0, plotSizeWidth, ImageYzero, ImageYzero, axisColor);

               if(needShowGrid)
               for(int i=-plotSizeHeight; i<plotSizeHeight; i=i+LineSpace){

                    if(i!=0){
                        int Trace=0;
                        while(Trace<plotSizeWidth){
                            drawLine(Trace, Trace+TraceSize, ImageYzero+i, ImageYzero+i, gridColor);
                            Trace = Trace+2*TraceSize;
                        }
                    }
                }

                //Vertical Y axis
                drawLine(ImageXzero, ImageXzero, 0, plotSizeHeight, axisColor);
                LineSpace = cvRound(LineSpace * (float)plotSizeWidth / plotSizeHeight );

                if(needShowGrid)
                for(int i=-plotSizeWidth; i<plotSizeWidth; i=i+LineSpace){

                    if(i!=0){
                        int Trace=0;
                        while(Trace<plotSizeHeight){
                            drawLine(ImageXzero+i, ImageXzero+i, Trace, Trace+TraceSize, gridColor);
                            Trace = Trace+2*TraceSize;
                        }
                    }
                }
            }

            Mat linearInterpolation(double Xa, double Xb, double Ya, double Yb, Mat Xdata){

                Mat Ydata = Xdata*0;

                for (int i=0; i<Xdata.rows; i++){

                    double X = Xdata.at<double>(i,0);
                    Ydata.at<double>(i,0) = int(Ya + (Yb-Ya)*(X-Xa)/(Xb-Xa));

                    if(Ydata.at<double>(i,0)<0)
                        Ydata.at<double>(i,0)=0;
                }

                return Ydata;
            }

            void drawValuesAsText(double Value, int Xloc, int Yloc, int XMargin, int YMargin){

                char AxisX_Min_Text[20];
                double TextSize = 1;

                sprintf(AxisX_Min_Text, "%g", Value);
                Point AxisX_Min_Loc;
                AxisX_Min_Loc.x = Xloc+XMargin;
                AxisX_Min_Loc.y = Yloc+YMargin;

                putText(plotResult,AxisX_Min_Text, AxisX_Min_Loc, FONT_HERSHEY_COMPLEX_SMALL, TextSize, plotTextColor, 1, 8);
            }

            void drawValuesAsText(const char *Text, double Value, int Xloc, int Yloc, int XMargin, int YMargin){

                char AxisX_Min_Text[20];
                int TextSize = 1;

                sprintf(AxisX_Min_Text, Text, Value);
                Point AxisX_Min_Loc;
                AxisX_Min_Loc.x = Xloc+XMargin;
                AxisX_Min_Loc.y = Yloc+YMargin;

                putText(plotResult,AxisX_Min_Text, AxisX_Min_Loc, FONT_HERSHEY_COMPLEX_SMALL, TextSize, plotTextColor, 1, 8);
            }


            void drawLine(int Xstart, int Xend, int Ystart, int Yend, Scalar lineColor){

                Point Axis_start;
                Point Axis_end;
                Axis_start.x = Xstart;
                Axis_start.y = Ystart;
                Axis_end.x = Xend;
                Axis_end.y = Yend;

                line(plotResult, Axis_start, Axis_end, lineColor, plotLineWidth, 8, 0);
            }
        };

        Ptr<Plot2d> Plot2d::create(InputArray _plotData)
        {
            return Ptr<Plot2dImpl> (new Plot2dImpl (_plotData));
        }

        Ptr<Plot2d> Plot2d::create(InputArray _plotDataX, InputArray _plotDataY)
        {
            return Ptr<Plot2dImpl> (new Plot2dImpl (_plotDataX, _plotDataY));
        }
    }
}