/** @file Plotter.cpp Implementation of one plot inside a chart. */
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
 #include <wx/wx.h>
#endif
#include <iostream> 
#include <exception>
#include <cmath> 

#include <wx/dcbuffer.h>

#include "Plotter.h"


/**---------------------------------------------------------------------------
*  event tables and other macros for wxWidgets
*  ----------------------------------------------------------------------------*/
BEGIN_EVENT_TABLE(Plotter, wxPanel) //ScrolledWindow)
    //EVT_RIGHT_DOWN(       Plotter::OnMouseMove)
    EVT_PAINT(              Plotter::OnPaint)  
    EVT_ERASE_BACKGROUND(   Plotter::OnEraseBackground)
END_EVENT_TABLE()


/**
 * Constructor.
 *  Holds the window title and number of graph lines. 
 *  The number of graph line _must_ be assigned correctly. Otherwise it does not work.
 *  @param[in] parent      Pointer to the parent frame.
 *  @param[in] title       The title of the chart.
 *    @warning Be careful about third parameter if the number is wrong, 
 *    @warning some strange lines could be seen. Even if, the worst case can happen, nothing could be seen 
 *  @param[in] LineNumber  Amount of series in a chart. */
Plotter::Plotter(wxWindow *parent, const wxString &title, int LineNumber):
             wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
		wxNO_BORDER|
                /*wxHSCROLL|wxVSCROLL|*/wxNO_FULL_REPAINT_ON_RESIZE|wxCLIP_CHILDREN),                
                LEFT_GAP(10), 
		RIGHT_GAP(30),
                DEFAULT_AXES_LINE(wxColour(_T("WHITE")), 2, wxSOLID),
                DEFAULT_BGROUND(_T("BLACK")),
                DEFAULT_GRID_LINE(_T("LIGHT GREY"), 1, wxDOT),
                DEFAULT_SIGNAL_LINE(wxColour(_T("MAGENTA")), 2),               
                DEFAULT_SWAP_LINE(wxColour(_T("YELLOW")), 2),
                DEFAULT_TEXT_FONT(10, wxSWISS, wxNORMAL, wxBOLD)
{
  maxGraphLineNumber = LineNumber;
  if(LineNumber<=0) maxGraphLineNumber=1;
  SerData = new DataBuffer[maxGraphLineNumber]();
  Sr = new Serie[maxGraphLineNumber]();

  PaintMode = FIXED_F_REPAINT;

  Tx.X.Start = 0.00;
  Tx.X.End =  25.00;
  Tx.Y.Start= -1.00;
  Tx.Y.End=    1.00;    

  TxAux1.X.End = TxAux2.X.End = TxAux1.X.Start = TxAux2.X.Start = 0;   

  AxX.Start = Tx.X.Start;
  AxX.End = Tx.X.End;
  AxX.Depth = 0;
  AxY.Start = -1;
  AxY.End = 1;
  AxY.Depth = 0;
  AxY.Tx=AxX.Tx = &Tx;
  AxX.LinePen = AxY.LinePen = wxPen(DEFAULT_AXES_LINE);

  LblX.Ax=&AxX;
  LblX.ticks=5;
  LblX.LabelFont=GetFont();

  LblY.Ay=&AxY;
  LblY.ticks=4;
  LblY.LabelFont=GetFont();

  GrY.Ticks=5;
  GrX.Ticks=10;
  GrY.Tx=GrX.Tx=&Tx;
  GrY.LinePen=GrX.LinePen=wxPen(DEFAULT_GRID_LINE);

  CurAct.Tx=&Tx;
  CurAct.Position=0;
  CurAct.LinePen=wxPen(DEFAULT_SWAP_LINE);
  
    // Initialise all series used
  for(int i=0; i<maxGraphLineNumber; i++)
    {
    Sr[i].XValues = &time;
    Sr[i].YValues = &SerData[i];  
    Sr[i].Tx = &Tx;
    }

  switch(maxGraphLineNumber)
  {  
    default: for(int j=3; j<maxGraphLineNumber; ++j)
              {
                Sr[j].LinePen = wxPen(DEFAULT_SIGNAL_LINE);
              } 
    case 3: Sr[2].LinePen = wxPen(wxColour(_T("BROWN")),2);  
    case 2: Sr[1].LinePen = wxPen(wxColour(_T("YELLOW")),2);
    case 1: Sr[0].LinePen = wxPen(wxColour(_T("WHITE")),2);
    case 0: break;
  }
    
  backgroundColor = wxColour(DEFAULT_BGROUND);  
  textFont = wxFont(DEFAULT_TEXT_FONT);

  SetTitle(title);
}


Plotter::~Plotter(void)
{
  if(SerData) {delete [] SerData; SerData=NULL;}
  if(Sr) {delete [] Sr; Sr=NULL;}
}


/**
 *  Sets the graphs' <CODE>x</CODE>-axis extreme ranges    
 *  @param[in] xAxis Extreme ranges for <CODE>x</CODE> axis */
void Plotter::SetXrange(double *xAxis)
{  
  Tx.X.Start = xAxis[0];
  Tx.X.End = xAxis[1];
  if(PaintMode==2)
    {
    AxX.Start = Tx.X.Start = xAxis[0];
    AxX.End = Tx.X.End = xAxis[1];
    }
  else
    {        
    AxX.Start = Tx.X.Start = -xAxis[1];
    AxX.End = Tx.X.End = xAxis[0];    
    }
				// set also axes
  dirty=true;
  //Refresh(false);			// Repaint whole chart
}


void Plotter::SetPaintMode(int NewMode)
{
double XRange[2];

  if(NewMode==PaintMode) return;
  GetXrange(XRange);
  PaintMode=NewMode;
  SetXrange(XRange);
}


/**
 *  Gets the graphs' <CODE>x</CODE>-axis extreme ranges    
 *  @param[out] xAxisRange <CODE>x</CODE> Axis range values */
void Plotter::GetXrange(double *xAxisRange) const
{
  if(xAxisRange==NULL) return;
  if(PaintMode==2)
    {
    xAxisRange[0] = Tx.X.Start;
    xAxisRange[1] = Tx.X.End;
    }
  else
    {        
    xAxisRange[1] = -Tx.X.Start;
    xAxisRange[0] = Tx.X.End;
    }  
}


/**
 *  Sets the graphs' <CODE>y</CODE>-axis extreme ranges    
 *  @param[in] yAxis   Extreme ranges for <CODE>y</CODE> axis
 *  @param[in] yCanvas Optional size of underlying canvas. If omitted it is set same as yAxis.
 */
void Plotter::SetYrange(double *yAxis, double *yCanvas)
{
  AxY.Start = Tx.Y.Start = yAxis[0];
  AxY.End = Tx.Y.End = yAxis[1];
  if(yCanvas)
    {
    Tx.Y.Start = yCanvas[0];
    Tx.Y.End = yCanvas[1];
    }
  dirty=true;
  Refresh();
}


/**
 *  Gets the graphs' <CODE>y</CODE>-axis extreme ranges    
 *  @param[out] yAxisRange <CODE>y</CODE> Axis range values */
void Plotter::GetYrange(double *yAxisRange) const
{
  if(yAxisRange==NULL) return;

  yAxisRange[0] = Tx.Y.Start;
  yAxisRange[1] = Tx.Y.End;
}


/**
 *  Adds a point thanks to the signal number. As the point is wanted to add.
 *  @param[in] count   Number of points to be added.
 *  @param[in] Values  Array with points.
 *  @param[in] Paint   Draw immediatelly serie after adding point or not. */
void Plotter::AddPoint(int count, const double *Values, bool Paint)
{
double shiftX;
int i;

  if(count<maxGraphLineNumber) return;

  shiftX = Values[0];
  double xsize = Tx.X.End-Tx.X.Start;  
  
  i=0;
  if(time.GetSize()>1000)
    {
    while(fabs(shiftX-time.GetValue(2)) > xsize)
      {
      time.DeleteItem(0);
      for(int j=0; j<maxGraphLineNumber; j++)
        { 
        SerData[j].DeleteItem(0);      
	}
      if(i++ > 1) break;	//erase only 2 items in each iteration
      }
    }
 
	/* Append new items to the chart. */
    
  time.AppendItem(shiftX);    
  for(i=0; i<maxGraphLineNumber; i++)
    {    
    SerData[i].AppendItem(Values[i+1]);
    }

  if(shiftX>(TxAux2.X.End+xsize))	   //The section is wrapped.
    {    
    if(abs(shiftX-TxAux2.X.End)>2*xsize)   // The frame was lost
      {					   // synchronize it
      TxAux2.X.End = TxAux1.X.Start = shiftX;      
      }
    else				   // shift a frame by xsize
      {      
      TxAux2.X.End += xsize;
      TxAux1.X.Start = TxAux2.X.End;
      }
    }  
  TxAux1.X.End = shiftX;
  TxAux2.X.Start = shiftX - xsize;
  
  if(PaintMode==FIXED_F_REPAINT)   //Immediate update
    {
    if(CurAct.Position==0)	   //first repaint has CurAct.Position==0
      {    
      dirty=true;
      Refresh(false);	           //enforce repaint
      }
    else
      {
      if(Paint && !dirty)
        {
        wxClientDC clientDC(this);
        DrawPieceSignals(clientDC);  // incremental draw
        }      
      }
    }
  else
    dirty=true;
}


/** Calculate scale transforms to reflect a current time. */
void Plotter::CalcTransforms(wxDC &dc)
{
int width;
int height;    
//int rightGap = 20;
int upGap = 10;
int downGap = 20;
wxCoord w,h;  

  GetClientSize(&width, &height);
  dc.GetTextExtent(wxString::Format(wxT("%.2f"),Tx.Y.End),&w,&h); //size of Y labels  

  Tx.X.CanvasStart = LEFT_GAP + w;
  Tx.X.CanvasEnd = width-RIGHT_GAP;
  Tx.Y.CanvasStart = upGap;
  Tx.Y.CanvasEnd = height-upGap-downGap; 

    //graph title 
  SetTextFontProp(dc);  
  dc.GetTextExtent(title,&w,&h);
  if(w>=0 && h>=0)
    Tx.Y.CanvasStart += h;		//shrink a canvas size according to a title

  TxAux1.Y=Tx.Y;			//update frame position of auxiliary transforms
  TxAux2.Y=Tx.Y;

  TxAux1.X.CanvasStart=Tx.X.CanvasStart;
  TxAux2.X.CanvasEnd=Tx.X.CanvasEnd;

  TxAux1.X.CanvasEnd=TxAux2.X.CanvasStart=
     TxAux1.X.CanvasStart +			           //offset
       (Tx.X.CanvasEnd-Tx.X.CanvasStart)*                  //scale
       (TxAux1.X.End-TxAux1.X.Start)/(Tx.X.End-Tx.X.Start);//percentage 
}


/**
 *  Auxiliary function called by <CODE>OnEraseBackground()</CODE> to draw
 *  the <CODE>x</CODE> and <CODE>y</CODE> axes. All the visual parts are drawn by this method 
 *  such as the coordinate lines, grid lines, the values of the coordinates 
 *  and title of the graph.   
 *
 *  @param[in] dc Device context  */
void Plotter::DrawGraph(wxDC &dc)
{
wxCoord w,h;  
  
  CalcTransforms(dc);		///< /todo this should be called only on resize

	/***** START PAINTING ******/  
  
      //graph title 
  SetTextFontProp(dc);  
  dc.GetTextExtent(title,&w,&h);  	
  dc.DrawText(title, (Tx.X.CanvasEnd+Tx.X.CanvasStart-w)/2, 0);	// center a title
        
  GrY.Paint(dc);	//draw horizontal grid lines 
  GrX.Paint(dc);	//draw horizontal grid lines 

  AxX.Paint(dc);	//horizontal baseline
  AxY.Paint(dc);        //right axis line

  LblX.Paint(dc);       //write horizontal (x) axis grid values
  LblY.Paint(dc);       //write vertical (y) axis grid values    
}


/**
 *  The function, invoked by <CODE>OnEraseBackground()</CODE> to plot the signal lines 
 *  Draws the lines from the time base to current time unless graph is shown fully. 
 *  Otherwise, it draws all the line from the time base <CODE>0</CODE> to time range's extreme point.
 *
 *  @param[in] dc Device context */
void Plotter::DrawSignals(wxDC & dc)
{
int i;

switch(PaintMode)
  {
  case FLOATING:
     {
     Transform2D TxAux(Tx);
     TxAux.X.End=time.GetValue(-1);
     TxAux.X.Start = TxAux.X.End - (Tx.X.End-Tx.X.Start);     

     for(i=0;i<maxGraphLineNumber;i++)
       {
       Sr[i].Tx=&TxAux;
       Sr[i].Paint(dc);     
       Sr[i].Tx=&Tx;
       }
     break;
     } 
  case FIXED:
  case FIXED_F_REPAINT:
     {
     for(i=0;i<maxGraphLineNumber;i++)
       {
       Sr[i].Tx=&TxAux1;
       Sr[i].Paint(dc); 
       Sr[i].Tx=&TxAux2;
       Sr[i].Paint(dc);    
       Sr[i].Tx=&Tx;
       }     

     CurAct.Tx=&TxAux1;
     CurAct.Position=TxAux1.X.End;
     CurAct.Paint(dc);    //Draw swap line
     
     break;
     }
  }
        
} 


/**
 * The function, invoked by <CODE>AddPoint()</CODE> to plot the signal lines.
 * Draws the line piece-by-piece as the new point is added.
 * 
 * @param[in] dc Device context  */
void Plotter::DrawPieceSignals(wxDC &dc)
{
double x1,x2,canx1,canx2;
int i;

  CalcTransforms(dc);		/// /todo this should be called only on resize

  x1=time.GetValue(-1);		// end of actualised interval
  x2=CurAct.Position;		// start of actualised interval

  if(x1<x2) return;		// time skew detected - silently return 

  if(x2<=TxAux1.X.Start)
    {				// a chart is wrapped    
    canx1=TxAux2.X.Log2Canvas(CurAct.Position);
    if(canx1>TxAux2.X.CanvasEnd) canx1=TxAux2.X.CanvasEnd;
    if(canx1<TxAux2.X.CanvasStart) canx1=TxAux2.X.CanvasStart; //after a long hold fix interval overflow/underflow
    canx2=TxAux2.X.CanvasEnd;

    i = CurAct.LinePen.GetWidth()/2+1;
    canx1-=i;
    canx2+=i;

    dc.SetClippingRegion(canx1, Tx.Y.CanvasStart,
                         canx2-canx1, Tx.Y.CanvasEnd-Tx.Y.CanvasStart);
    dc.SetBackground(backgroundColor);    
    dc.Clear();

    GrY.Paint(dc);	//draw clipped horizontal grid lines 
    GrX.Paint(dc);	//draw clipped vertical grid lines

    for(i=0;i<maxGraphLineNumber;i++)
      {
      Sr[i].Tx=&TxAux2;
      Sr[i].Paint(dc);
      Sr[i].Tx=&Tx;
      }
    dc.DestroyClippingRegion();

    x2=TxAux1.X.Start;	//fix x2 position  
    }

  canx1 = TxAux1.X.Log2Canvas(x1);
  canx2 = TxAux1.X.Log2Canvas(x2);  

  dc.SetClippingRegion(canx2-ceil(CurAct.LinePen.GetWidth()/2.0), Tx.Y.CanvasStart-1,
                       canx1-canx2+CurAct.LinePen.GetWidth(),Tx.Y.CanvasEnd-Tx.Y.CanvasStart+2);
  dc.SetBackground(backgroundColor);
  dc.Clear();

  GrY.Paint(dc);	//draw clipped horizontal grid lines 
  GrX.Paint(dc);	//draw clipped vertical grid lines 
  AxX.Paint(dc);	//horizontal baseline clipped  
  AxY.Paint(dc);	//vertical axis  

	/* Transform TxAux3 covers only clipped area, it is subset of TxAux1. */
  Transform2D TxAux3(TxAux1);
  if(TxAux1.X.CanvasStart>TxAux1.X.CanvasEnd)
    {
    TxAux3.X.CanvasStart = canx1 - ceil(CurAct.LinePen.GetWidth()/2.0) -1;  
    TxAux3.X.CanvasEnd = canx2 + ceil(CurAct.LinePen.GetWidth()/2.0) +1;
    }
  else
    {
    TxAux3.X.CanvasStart = canx1 + ceil(CurAct.LinePen.GetWidth()/2.0) +1;  
    TxAux3.X.CanvasEnd = canx2 - ceil(CurAct.LinePen.GetWidth()/2.0) -1;
    }

  if(TxAux3.X.CanvasStart<TxAux1.X.CanvasStart) TxAux3.X.CanvasStart=TxAux1.X.CanvasStart;
  if(TxAux3.X.CanvasStart>TxAux1.X.CanvasEnd) TxAux3.X.CanvasStart=TxAux1.X.CanvasEnd;
  if(TxAux3.X.CanvasEnd<TxAux1.X.CanvasStart) TxAux3.X.CanvasEnd=TxAux1.X.CanvasStart;
  if(TxAux3.X.CanvasEnd>TxAux1.X.CanvasEnd) TxAux3.X.CanvasEnd=TxAux1.X.CanvasEnd;

  TxAux3.X.Start = TxAux1.X.Canvas2Log(TxAux3.X.CanvasStart);
  TxAux3.X.End = TxAux1.X.Canvas2Log(TxAux3.X.CanvasEnd);

  for(i=0;i<maxGraphLineNumber;i++)
    {
    Sr[i].Tx=&TxAux3;
    Sr[i].Paint(dc);
    Sr[i].Tx=&Tx;
    }  

  dc.DestroyClippingRegion();    

  TxAux1.X.Start = TxAux2.X.End; // return old values
  TxAux1.X.CanvasStart = Tx.X.CanvasStart;

  CurAct.Tx=&TxAux1;
  CurAct.Position=TxAux1.X.End;
  CurAct.Paint(dc);    //Draw swap line  
}


/**  Sets the graph's title. Default name is "Chart".
 *  @param[in] title The title of graph */
void Plotter::SetTitle(const wxString &NewTitle)
{ 
  if(!NewTitle.IsNull())
    title = NewTitle; 
  else 
    title = _T("Chart");
}


/** Sets background appearance.
  *  @see wxColour is the reference class for any further question about the parameter of method.
  *  @param[in] color wxColour value */
void Plotter::SetBackground(const wxColour &color)
{   
  if(color.Ok())
    backgroundColor = color;    
  else     
    backgroundColor = wxColor(DEFAULT_BGROUND); 
}


/**
 *  Sets the properties of signal line(s).
 *  If the line number does not exist nothing will be happenned. 
 *
 *  @see wxPen is the reference class for any further question about the parameter of method. 
 *  @param[in] lineNumber
 *  @param[in] pen */
void Plotter::SetSignalLine(int lineNumber, const wxPen &pen)
{   
  if(lineNumber>=0 && lineNumber<maxGraphLineNumber)
  {
    if(pen.Ok())
      Sr[lineNumber].LinePen = pen;
    else
      Sr[lineNumber].LinePen = wxPen(DEFAULT_SIGNAL_LINE);
  }
}


void Plotter::ShowSignalLine(int lineNumber, bool Visible)
{   
  if(lineNumber>=0 && lineNumber<maxGraphLineNumber)
  {
    Sr[lineNumber].Visible = Visible;
  }
}


/** Sets the properties of the signal(s) line.    
 *  @param[in] devContent A device context is onto which graphics and text can be drawn
 *  @param[in] lineNumber Shows which line will be drawn onto dc with properties. */
void Plotter::ApplySignalLineProp(wxDC &devContent, int lineNumber)
{   
    devContent.SetPen(Sr[lineNumber].LinePen);
}


/** Sets swap line appearence.
 *  @see wxPen is the reference class  for any further question about the parameter of method. 
 *  @param[in] pen wxPen */
void Plotter::SetSwapLine(const wxPen &pen)
{
  if(pen.Ok())
    CurAct.LinePen = pen;
  else
    CurAct.LinePen = wxPen(DEFAULT_SWAP_LINE); 
}


/** Sets swap line visibility. */
void Plotter::ShowSwapLine(bool Visible)
{
  CurAct.Visible = Visible;  
}


/** Sets the properties of the swap line. 
 *  @param[in] devContent A device context is onto which graphics and text can be drawn */
void Plotter::SetSwapLineProp(wxDC &devContent)
{       
  devContent.SetPen(CurAct.LinePen);
}


/**
 *  Sets axes line appearence.
 *
 *  @see wxPen is the reference class  for any further question about the parameter of method. 
 *  @param[in] pen wxPen */
void Plotter::SetAxesLine(const wxPen &pen)
{
  if(pen.Ok())
    AxX.LinePen = AxY.LinePen = pen;
  else
    AxX.LinePen = AxY.LinePen = wxPen(DEFAULT_AXES_LINE); 
}


/**
 *  Sets grid line appearence.
 *
 *  @see wxPen is the reference class  for any further question about the parameter of method. 
 *  @param[in] pen wxPen */
void Plotter::SetGridLine(const wxPen &pen)
{
  if(pen.Ok())
     GrY.LinePen = GrX.LinePen = pen;
  else
     GrY.LinePen = GrX.LinePen = wxPen(DEFAULT_GRID_LINE); 
}


/** Specifies the font with which the text will be drawn. 
 *  @see wxFont is the reference class for any further question about the parameter of method. 
 *  @param[in] font  */
void Plotter::SetTextFont(const wxFont &font)
{
  if(font.Ok())
    textFont = font;
  else 
    textFont = wxFont(DEFAULT_TEXT_FONT); 
}

/**
 *  Sets the appearance of the text. Sets the properties of the text which is seen on the panel.
 *  Default text which has 10 point size, a decorative font, a normal sytle and, a bolded    
 *  @param[in] devContent Device context    */
void Plotter::SetTextFontProp(wxDC &devContent)
{
  devContent.SetBackgroundMode(wxTRANSPARENT);
  devContent.SetFont(textFont);
  devContent.SetTextForeground(wxColour(_T("WHITE")));
}


/** Initializes all the appearence properties. 
 *  Initializes all the properties which are seen on the panel ,ie, 
 *  Default font features for the text, Default background color for the panel and 
 *  Default attributes for each line.   */
/*
void Plotter::InitAllProp()
{   
  SetBackground(wxColour());
  SetGridLine(wxPen());
  SetSwapLine(wxPen());
  for(int i=0; i<maxGraphLineNumber; i++)
    SetSignalLine(i, wxPen());
  SetTextFont(wxFont());    
}
*/


/** flicker free repaint of whole chart. This repaint could be used only for 
 * handling windows paint requirement. See also PaintMe.
 *  @param[in] event  wxEraseEvent  */
void Plotter::OnPaint(wxPaintEvent& WXUNUSED(event))
{
  dirty=true;		       // signalise dirty during repaint

  wxBufferedPaintDC dc(this);  // use DC with wxBitmap backend
  //wxPaintDC dc(this);
   
  dc.SetBackground(backgroundColor); // set a proper color for a background
  dc.Clear();                  // apply color on background otherwise background would remain white
  
  DrawGraph(dc);
  if(time.GetSize()>0)
    DrawSignals(dc);

  dirty=false;
}


/** Application enforced repaint is using different painting methods than
  OnPaint.  */
void Plotter::PaintMe(void)
{  
  wxClientDC dc(this);  
  dc.SetBackground(backgroundColor);
  dc.Clear();
  DrawGraph(dc);
  if(time.GetSize()>0)
    DrawSignals(dc);
  dirty=false;
}


/** Does nothing to avoid flashing 
 *  @param[in] event  Unused event  */
void Plotter::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
{
    // do nothing to avoid flashing
}


void Plotter::SerVisible(int Index, bool Value)
{
  if(Sr==NULL || Index<0 || Index>=maxGraphLineNumber) return;
  Sr[Index].Visible=Value;  
}


/** Erase all charts data and set auxiliary transforms to paint
  splitter from begining. */
void Plotter::Clear(void)
{
int i;

  time.Clear();
  for(i=0;i<maxGraphLineNumber;i++)
    {
    SerData[i].Clear();
    }  

  TxAux2.X.Start = TxAux2.X.End = TxAux1.X.Start = 
	TxAux1.X.End;
  TxAux1.X.End = TxAux1.X.Start + Tx.X.End-Tx.X.Start;

  dirty = true;
}



