/*******************************************************************/
/** @file PlotGraphFrame.cpp 
 * Description: Implementation of real time chart that 
 *              contains two subcharts.				   *
 * Dependency: wxWindows				    	   *
 *                 Copyright 2005-2007 Humusoft s.r.o.             *
 *                 Copyright 2007-2025 Jaroslav Fojtik             *
 *******************************************************************/

#include <wx/wxprec.h>
#ifndef WX_PRECOMP
 #include <wx/wx.h>
#endif

#include "../library/hardware/Timer.h"
#include "PlotGraphFrame.h"

#define DATA_GRAB_MS 1000        ///< Period of grabbing timer that pump data from circular buffer to a chart.
#define REPAINT_TIMER_MS 1000   ///< Period of repainting chart. A chart is full repainted only when it is marked as dirty.


std::list<PlotGraphFrame *> PlotGraphFrame::Instances; ///< List of opened chart objects.


DEFINE_EVENT_TYPE(wxEVT_FORMCLOSED)

/**
 * Constructor
 * @param[in] parent the parent wxWindow pointer.  */
PlotGraphFrame::PlotGraphFrame(wxFrame *parent):
    wxFrame(parent, -1, _("Plotting frame rate [fps]"), wxDefaultPosition, wxSize(675, 550), wxDEFAULT_FRAME_STYLE),    
    mTimer(this, TIMER_ID),
    TmrRepaint(this, ID_REPAINT),
    Frequency1(NULL), Frequency2(NULL),
    MnuHold(NULL),
    FilterData(false)
{    
    Instances.push_back(this);
    RealTime.SetDouble(0);

    // Make a menubar
    wxMenuBar *menuBar = new wxMenuBar;    

          // now append the freshly created menu to the menu bar...
    // create Ranges
    menuClose = new wxMenu;
    menuClose->Append(ID_MENU_CLOSE, _("&Close Chart"));
    menuBar->Append(menuClose, _("&Close"));  // append menu item to menubar

    wxMenu *menuRange = new wxMenu;
    menuRange->Append(ID_MENU_RANGE, _("&Range of Time Axis"));            
    menuRange->AppendSeparator();
/*
    wxMenuItem *MnuSmoothAct = 
       new wxMenuItem(menuRange,ID_MENU_SMOOTH_ACTION, _("&Smooth Action Curve"), _("Filter values inside action curve."),wxITEM_CHECK);
    menuRange->Append(MnuSmoothAct);    
    if(FilterData) MnuSmoothAct->Check();
*/
    wxMenuItem *HideAct = 
       new wxMenuItem(menuRange,ID_MENU_HIDE_ACTION, _("&Hide Action Curve"), _("Action curve is not painted."),wxITEM_CHECK);
    menuRange->Append(HideAct);    
    HideAct->Check();
    //if(!FilterData) MnuSmoothAct->Check();

    menuBar->Append(menuRange, _("&Range"));  // append menu item to menubar
    
    wxMenu *menuMode = new wxMenu;
    menuBar->Append(menuMode, _("&Paint Mode"));  // append menu item to menubar
        
    	//a default item must be placed first - no other check is made
    menuMode->AppendRadioItem(ID_MENU_MODE_FLINE, _("&Fixed Fast Repaint"), _("Current time is placed on a cursor."));
    menuMode->AppendRadioItem(ID_MENU_MODE_FLOW, _("&Moving"),_("Current sample is placed on right axis."));
    //menuMode->AppendRadioItem(ID_MENU_MODE_LINE, _("&Fixed"));    

    wxMenu *menuClear = new wxMenu;
    menuClear->Append(ID_MENU_CLEAR, _("&Clear Chart"), _("Erase all chart's data.") );
    menuBar->Append(menuClear, _("&Clear"));  // append menu item to menubar   

    wxMenu *menuHold = new wxMenu;
    MnuHold = new wxMenuItem(menuHold,ID_MENU_HOLD,_("&Hold Chart"),_("Stop painting chart."),wxITEM_CHECK);
    menuHold->Append(MnuHold);    
    menuBar->Append(menuHold, _("&Hold"));  // append menu item to menubar   


		// Associate the menu bar with the frame
    SetMenuBar(menuBar);
    CreateStatusBar();
    SetStatusText(_("Legend: cyan-video 1; green-video 2"));
    
    this->SetBackgroundColour(wxColour(0, 0, 0));    
   
    plotA = new Plotter(this,  wxT(" ") /* wxT("Chart of framerate [fps]")*/, 2/*, graphLineNumberA */ ); /* Number of Graph line inside the panel */    

/*    wxColour lineColor = wxColour("RED"); 
    wxPen pen = wxPen(lineColor, 2, wxSOLID);
    plotA->SetSignalLine(2, pen);   */

    wxColour lineColor = wxColour("GREEN");
    wxPen pen = wxPen(lineColor, 2, wxSOLID);
    plotA->SetSignalLine(1, pen);    

    lineColor = wxColour("CYAN");
    pen = wxPen(lineColor, 2, wxSOLID);
    plotA->SetSignalLine(0, pen);

    double Yrange[2]={1,0};
    plotA->SetYrange(Yrange);
    

    double Xrange[2]={0,100};
    plotA->SetXrange(Xrange);

    plotA->SetXTicks(10);
    plotA->SetXLblTicks(5);
    plotA->SetYTicks(10);
    plotA->SetYLblTicks(5);

    //plotA->SerVisible(0,!HideAct->IsChecked());    
 
    //wxColour backgColor = wxColour(100, 0, 123);
    //plotB->SetBackground(wxColour("BLACK"));

	/* Create layout of chart */
    Szr_Chart = new wxBoxSizer(wxVERTICAL);    

    Szr_Chart->Add(plotA, 1, wxEXPAND|wxALL, 0);    

    const wxSize& minSize = wxSize(350, 350); 
    SetSizeHints(minSize);
    SetSizer(Szr_Chart);

    SetIcon(parent->GetIcon());  //prefer same icons for all opened frames

    SetAutoLayout(true);
    Layout();
    //Fit();
}


/** destructor emits wxEVT_FORMCLOSED event, that could be catched from parent forms. */
PlotGraphFrame::~PlotGraphFrame()
{
  Instances.remove(this);

  if(TmrRepaint.IsRunning())
    TmrRepaint.Stop();
  if(mTimer.IsRunning())
    mTimer.Stop();

  plotA->Clear();

     /* Emit event from destructor. */
  wxCommandEvent event(wxEVT_FORMCLOSED,GetId());
  event.SetEventObject(this);  
//  event.SetText( wxT("Destroy") );  // Give it some contents
  GetEventHandler()->ProcessEvent(event);

  if(plotA)
    {delete plotA;plotA=NULL;}
  //delete plotB;
}


BEGIN_EVENT_TABLE(PlotGraphFrame, wxFrame)
    EVT_TIMER(TIMER_ID,         PlotGraphFrame::OnGrabTimer)
    EVT_TIMER(ID_REPAINT,       PlotGraphFrame::OnRepaintTimer)
    EVT_ERASE_BACKGROUND(       PlotGraphFrame::OnEraseBackground)

    EVT_BUTTON(ID_BUTTON_CLEAR, PlotGraphFrame::OnBtnClear)
    EVT_BUTTON(ID_BUTTON_CLOSE, PlotGraphFrame::OnBtnClose)    
    EVT_SIZE(                   PlotGraphFrame::OnSize)
    EVT_MENU(ID_MENU_RANGE,     PlotGraphFrame::OnRangeEntry)
    EVT_MENU(ID_MENU_SMOOTH_ACTION,PlotGraphFrame::OnSmoothAction)
//    EVT_MENU(ID_MENU_HIDE_ACTION,PlotGraphFrame::OnHideAction)
    EVT_MENU(ID_MENU_CLOSE,     PlotGraphFrame::OnBtnClose)
//    EVT_MOTION(                 PlotGraphFrame::OnMouseMove)        
    EVT_ACTIVATE(		PlotGraphFrame::OnActivate)
    EVT_CLOSE(			PlotGraphFrame::OnClose)
    EVT_MENU(ID_MENU_MODE_FLOW, PlotGraphFrame::OnMnuFlow)
    EVT_MENU(ID_MENU_MODE_LINE, PlotGraphFrame::OnMnuLine)
    EVT_MENU(ID_MENU_MODE_FLINE,PlotGraphFrame::OnMnuFLine)
    EVT_MENU(ID_MENU_CLEAR,     PlotGraphFrame::OnBtnClear)
    EVT_MENU(ID_MENU_HOLD,      PlotGraphFrame::OnMnuHold)    

    EVT_MENU_OPEN(              PlotGraphFrame::OnMenuOpen)    
END_EVENT_TABLE()


void PlotGraphFrame::OnActivate(wxActivateEvent & WXUNUSED(event))
{   
  if(!MnuHold->IsChecked())
    { 
    if(!mTimer.IsRunning())
      mTimer.Start(DATA_GRAB_MS);  // Grab snapshot periodically and asynnchronously    
    }
  if(!TmrRepaint.IsRunning())
      TmrRepaint.Start(REPAINT_TIMER_MS);
}


void PlotGraphFrame::OnClose(wxCloseEvent & WXUNUSED(event))
{
  TmrRepaint.Stop(); 
  mTimer.Stop();
  Show(false);  
  delete this;
}



/** Clears the plot window
 *  @param[in] event Unused event   */
void PlotGraphFrame::OnBtnClear(wxCommandEvent& WXUNUSED(event))
{
  plotA->Clear();  
  plotA->Refresh();  
}



void PlotGraphFrame::OnMnuLine(wxCommandEvent & WXUNUSED(event))
{
  plotA->SetPaintMode(0);
  plotA->Refresh(false);
}


void PlotGraphFrame::OnMnuFLine(wxCommandEvent & WXUNUSED(event))
{
  plotA->SetPaintMode(2);
  plotA->Refresh(false);  
}


void PlotGraphFrame::OnMnuFlow(wxCommandEvent & WXUNUSED(event))
{
  plotA->SetPaintMode(1);
  plotA->Refresh(false);
}



/** Close button - only hides a PlotGraphFrame window, it lives further ?!?
 *  @param[in] event Unused event   */
void PlotGraphFrame::OnBtnClose(wxCommandEvent& event)
{
  event.StopPropagation();

  if(TmrRepaint.IsRunning())
    TmrRepaint.Stop();

  wxCommandEvent PlotCloseEvent(wxEVT_FORMCLOSED,GetId());
  PlotCloseEvent.SetEventObject( this );  
//event.SetText( wxT("Destroy") );  // Give it some contents
  GetEventHandler()->ProcessEvent(PlotCloseEvent);

  Show(false);  
}


void PlotGraphFrame::OnSmoothAction(wxCommandEvent& event)
{   
 FilterData = event.IsChecked();
}


/*
void PlotGraphFrame::OnHideAction(wxCommandEvent& event)
{   
 plotA->SerVisible(0,!event.IsChecked());
 plotA->Refresh(false);
}
*/


/**
 *  Takes a value via wxTextEntryDialog() for the graph's time axis
 *  @param[in] event from wxWindows
 */
void PlotGraphFrame::OnRangeEntry(wxCommandEvent& event)
{   
    if (event.IsCommandEvent())
    {
        double value[2];
        plotA->GetXrange(value);
        wxTextEntryDialog dialog(this,
                                _("Range of time axis[s]:\n"),
                                _("Set Time Range"),
                                wxString::Format(wxT("%.2f"), value[1]),
                                wxOK | wxCANCEL);
        dialog.SetIcon(GetIcon());

        if (dialog.ShowModal() == wxID_OK)
        {   
            wxString valText = dialog.GetValue();
            //wxMessageBox(valText, _("Got string"), wxOK | wxICON_INFORMATION, this); //debugging
            double val;
            if( !valText.ToDouble(&val) || val <= 0)
            {    
                wxMessageBox(_("Invalid entry was typed\n") , _("Error"), wxOK | wxICON_ERROR, this);
                OnRangeEntry(event);
                return;
            }
            value[1] = val;
            plotA->SetXrange(value);
            if(val==9)
              {
	      plotA->SetXTicks(9);
              plotA->SetXLblTicks(3);
	      }
            else if(val==8 || val==4)
              {
	      plotA->SetXTicks(8);
              plotA->SetXLblTicks(4);
	      }
	    else if(val==6 || val==12)
              {
	      plotA->SetXTicks(12);
              plotA->SetXLblTicks(6);
	      }
            else
	      {
	      plotA->SetXTicks(10);
              plotA->SetXLblTicks(5);
	      }
        }
    }
    else
        event.Skip();
}

/**
 * When the size is changed, it is called and then it refreshs the screen and sets the layouts   
 * @param[in] event Unused event
 *  \warning If your drawing depends on the size of the window, 
 *  \warning you may need to clear the DC explicitly and repaint the whole window. 
 *  \warning In which case, you may need to call wxWindow::Refresh() to invalidate the entire window. */
void PlotGraphFrame::OnSize(wxSizeEvent& WXUNUSED(event))
{
  Refresh();
  SetAutoLayout(true);
  Layout();
}


/** Do whatever you want to do every second here
 * Uses for testing graph
 * @param[in] event Unused event 
  \todo - this should be rewritten to be event driven */
void PlotGraphFrame::OnGrabTimer(wxTimerEvent &WXUNUSED(event))
{
double Values[3];
double YValues[2]={1,0};

    // do whatever you want to do every period of timer here
  plotA->GetYrange(&YValues[0]);

  Values[0] = GetTickCount() / 1000.0f;
  Values[1] =(Frequency1==NULL)?0:Frequency1->GetDouble();
  Values[2] = (Frequency1==NULL)?0:Frequency2->GetDouble();	// Circle X-axis  

  if(YValues[0]<Values[1] || YValues[0]<Values[2])
  {
    if(Values[1]>YValues[0]) YValues[0]=Values[1];
    if(Values[2]>YValues[0]) YValues[0]=Values[2];
    plotA->SetYrange(YValues);
  }
  plotA->AddPoint(2,Values,true);

 //wxString str;
 //str.Printf( wxT("Elements: %d %d"), ElToPaint, MRT.CB_Time.Elements() );
 //SetStatusText( str );
}


/** Repaint both plots */
void PlotGraphFrame::OnRepaintTimer(wxTimerEvent & WXUNUSED(event))
{ 
  //plotA->PaintMe();
  if(IsIconized()) return;	//do not waste time when iconised
  
  if(plotA->dirty) plotA->Refresh(false);    
//  if(plotB->dirty) plotB->Refresh(false);  
}


void PlotGraphFrame::OnMnuHold(wxCommandEvent & event)
{
  if(event.IsChecked())
    {
    if(mTimer.IsRunning()) mTimer.Stop();
    }
  else
    {
    if(!mTimer.IsRunning()) mTimer.Start(DATA_GRAB_MS);
    }
}


/**
 * Shows the mouse position
 * @param[in] event Unused event
 * \warning remove OnMouseMove() before submit  */
void PlotGraphFrame::OnMouseMove(wxMouseEvent& event)
{   
  wxClientDC dc(this);
  wxPoint pos = event.GetPosition();
  long x = dc.DeviceToLogicalX( pos.x );
  long y = dc.DeviceToLogicalY( pos.y );
  wxString str;
  str.Printf(wxT("Current mouse position: %d,%d"), (int)x, (int)y );
  SetStatusText( str );    
}


void PlotGraphFrame::OnMenuOpen(wxMenuEvent& event)
{
wxMenu *MNU = event.GetMenu();

  //wxString str;
  //str.Printf( wxT("PlotGraphFrame::MenuSel %d"), event.GetMenuId() );
  //SetStatusText( str );
  if(MNU==menuClose)
    {
    wxCommandEvent event2(wxEVT_FORMCLOSED,GetId());
    event2.SetEventObject(this);  
    OnBtnClose(event2);
    }
    
}


/** When a size is changed, it is called and then it refreshs the screen and sets the layouts 
 *  @param[in] event Unused event
 *  \warning If the drawing depends on the size of the window, 
 *  \warning clearing the DC explicitly may be needed and repainting the whole window. 
 *  \warning In which case, calling <CODE>wxWindow::Refresh()</CODE> may be needed to invalidate the entire window. */
void PlotGraphFrame::OnEraseBackground(wxEraseEvent & WXUNUSED(event))
{  
 // do not paint anything

  if(MnuHold)
    if(MnuHold->IsChecked()) return;
/*
  if(MRT.CB_Time.Elements()>100)    
    {
    wxTimerEvent evt;
    OnGrabTimer(evt);
    }
*/
}


/** Return a pointer to histowram Window pertaining to a given 2D abstract raster */
bool PlotGraphFrame::FindGraphFrame(const PlotGraphFrame *pF)
{
  if(pF)
  {
    for(std::list<PlotGraphFrame*>::iterator it=Instances.begin(); it!=Instances.end(); ++it)
    {
      if(*it==NULL) continue;
      if((*it) == pF) return true;
    }
  }
  return false;
}
