/********************************************************************/
/** @file CamView/PlotHistogram.cpp
 * Description: Show and update image's histogram 
 *              contains two subcharts.				   *
 * Dependency: wxWidgets				    	   *
 *                 Copyright 2007-2024 Jaroslav Fojtik             *
 *******************************************************************/
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
 #include <wx/wx.h>
#endif

#include "../library/hardware/Timer.h"
#include "PlotHistogram.h"
#include "RT_EXEC.h"

#define REPAINT_TIMER_MS 691	///< Period of repainting chart. A chart is full repainted only when it is marked as dirty.

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


DEFINE_EVENT_TYPE(wxEVT_FORM_HIST_CLOSED)

/** Constructor.
 * @param[in] parent the parent wxWindow pointer.  */
PlotHistogram::PlotHistogram(wxFrame *parent, const char *Label):
    wxFrame(parent, -1, (Label==NULL)?"Histogram":Label, wxDefaultPosition, wxSize(675, 550), wxDEFAULT_FRAME_STYLE),    
    mTimer(this, TIMER_ID),
    TmrRepaint(this, ID_REPAINT),   
    MnuHold(NULL),
    FilterData(false),
    Image(NULL)
{
  ImageIsChanged = true;	// Allow first paint.
  ColorMode = true;
  Instances.push_back(this);

    // 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, _T("&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: x axis contains intensity and y occurances. Red, Green Blue lines are for channels."));

  this->SetBackgroundColour(wxColour(0, 0, 0));    

  plotA = new Plotter(this,wxT(" ")); //, wxT("Histogram")/*, graphLineNumberA */ ); /* Number of Graph line inside the panel */
  //plotA->SetTitle(wxT(""));
  plotA->ShowSwapLine(false);

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

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

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

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

  double Xrange[2]={0,255};
  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);    

	/* 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();
}


void PlotHistogram::AssignImage(Raster2DAbstract *newImage)
{
  if(Image==newImage) return;
  if(Image)
  {
    if(InterlockedDecrement(&Image->UsageCount) <= 0)
    {
      delete Image;
    }
    Image = NULL;
  }
  if(newImage==NULL) return;
  InterlockedIncrement(&newImage->UsageCount);
  Image = newImage;
}


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

  AssignImage(NULL);

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

  plotA->Clear();

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

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


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

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

    EVT_MENU_OPEN(              PlotHistogram::OnMenuOpen)    
END_EVENT_TABLE()


void PlotHistogram::OnActivate(wxActivateEvent & WXUNUSED(event))
{
  if(!MnuHold->IsChecked())
    {    
    if(!TmrRepaint.IsRunning())
        TmrRepaint.Start(REPAINT_TIMER_MS);    
    }
  else
    {
    if(TmrRepaint.IsRunning())
        TmrRepaint.Stop();    
    }
}


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



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



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


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


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



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

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

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

  Show(false);  
}


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


void PlotHistogram::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 PlotHistogram::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 PlotHistogram::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 PlotHistogram::OnGrabTimer(wxTimerEvent &WXUNUSED(event))
{
/*double Values[4];
unsigned ElToPaint;
unsigned Dilution=1;
static double OldVal; */
    // do whatever you want to do every period of timer here    

/* !!!!!!!!!!
  ElToPaint = MRT.CB_Time.Elements();
  
  if(ElToPaint>220) Dilution=2; 
  if(ElToPaint>420) Dilution=3; 
  if(ElToPaint>620) Dilution=4;
  if(ElToPaint>820) Dilution=5;
  if(ElToPaint>920) Dilution=6;
  if(ElToPaint==0) 
    {
    ElToPaint++;
    MRT.PumpRawCB();    
    }  

  for(unsigned i=0;i<ElToPaint;i++)
    {
    Values[0] = MRT.CB_Time.Pop();
    Values[1] = MRT.CB_OutputMag.Pop();
    Values[2] = MRT.CB_BallPos.Pop();		   // Circle X-axis
    Values[3] = MRT.CB_Req.Pop();		   // Setpoint X-axis

    if(FilterData && ElToPaint>1)      
      Values[1] = 0.9*OldVal + 0.1*Values[1];	  // action          

    if(i%Dilution == 0 || i>=ElToPaint-1)
      plotA->AddPoint(4,Values,i>=ElToPaint-1);

    OldVal = Values[1];
    }  
*/

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


/** Repaint a histogram plot. */
void PlotHistogram::OnRepaintTimer(wxTimerEvent & WXUNUSED(event))
{
double nRGB[256][4];
double Yrange[2]={1,0};
int i, j;

  if(!ImageIsChanged) return;	// No image change - no data recalculation needed.
  if(Image==NULL) return;
  if(Image->UsageCount>=WRITE_BARIER) return;	// Image is asynchronously reshaped.

  //plotA->PaintMe();
  if(IsIconized()) return;	//do not waste time when iconised  

  //if(Image->GetPlanes()!=24) return;	//Other image types are not supported yet.

  plotA->Clear();

  for(i=0; i<255; i++)
  {
    nRGB[i][0] = i;				// x axis ticks.
    nRGB[i][1] = nRGB[i][2] = nRGB[i][3] = 0;
  }

  switch(Image->GetPlanes())
  {
    case 8:  ColorMode = false;
             InterlockedExchangeAdd(&Image->UsageCount,READ_BARIER);
             for(i=0; i<Image->Size2D; i++)
             {
               uint8_t *ImgData = (uint8_t *)Image->GetRow(i);    
	       j = Image->Size1D;
	       while(j-- > 0)
               {     
                 nRGB[*ImgData++][1] += 1;
               }
             }
             InterlockedExchangeAdd(&Image->UsageCount,-READ_BARIER);             
             break;

    case 24: InterlockedExchangeAdd(&Image->UsageCount,READ_BARIER);
	     if(ColorMode)
             {               
               for(i=0; i<Image->Size2D; i++)
               {
                 uint8_t *ImgData = (uint8_t *)Image->GetRow(i);
	         j = Image->Size1D;
                 while(j-- > 0)
                 {     
                   nRGB[*ImgData++][1] += 1;
                   nRGB[*ImgData++][2] += 1;
                   nRGB[*ImgData++][3] += 1;
                 }
               }
             }
             else
             {
               for(i=0; i<Image->Size2D; i++)
               {
                 unsigned char *ImgData = (unsigned char *)Image->GetRow(i);
                 j = Image->Size1D;
                 while(j-- > 0)
                 {     
                   nRGB[*ImgData++][1] += 1;	// we assume here that all compounds have a same intensity
                   ImgData += 2;
                 }
               }
            }
            InterlockedExchangeAdd(&Image->UsageCount,-READ_BARIER); 
            break;

    default: return;
  }

  for(i=0; i<255; i++)
  {
    plotA->AddPoint(4, nRGB[i],false);

    if(Yrange[0]<nRGB[i][1]) Yrange[0]=nRGB[i][1];
    if(Yrange[0]<nRGB[i][2]) Yrange[0]=nRGB[i][2];
    if(Yrange[0]<nRGB[i][3]) Yrange[0]=nRGB[i][3];
  }

  //plotA->TxAux1.X.End = 255;
  //plotA->TxAux1.X.Start = 0;   
  
  plotA->SetYrange(Yrange);

  if(plotA->dirty) plotA->Refresh(false);
  ImageIsChanged = false;
}


void PlotHistogram::OnMnuHold(wxCommandEvent & event)
{
  if(!event.IsChecked())
    {    
    if(!TmrRepaint.IsRunning())
        TmrRepaint.Start(REPAINT_TIMER_MS);    
    }
  else
    {
    if(TmrRepaint.IsRunning())
        TmrRepaint.Stop();    
    }
}


/** Shows the mouse position
 * @param[in] event Unused event
 * \warning remove OnMouseMove() before submit  */
void PlotHistogram::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 PlotHistogram::OnMenuOpen(wxMenuEvent& event)
{
wxMenu *MNU = event.GetMenu();

  //wxString str;
  //str.Printf( wxT("PlotHistogram::MenuSel %d"), event.GetMenuId() );
  //SetStatusText( str );
  if(MNU==menuClose)
    {
    wxCommandEvent event2(wxEVT_FORM_HIST_CLOSED,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 PlotHistogram::OnEraseBackground(wxEraseEvent & WXUNUSED(event))
{  
	// do not paint anything
  if(MnuHold)
    if(MnuHold->IsChecked()) return;
}


bool PlotHistogram::QueryTimer(bool TimerStatus)
{
const bool PrevStatus = TmrRepaint.IsRunning();
  if(TimerStatus) TmrRepaint.Start(REPAINT_TIMER_MS);
  else TmrRepaint.Stop();
return PrevStatus;
}


/** Return a pointer to histowram Window pertaining to a given 2D abstract raster */
PlotHistogram *PlotHistogram::FindHistogram(const Raster2DAbstract *pImg)
{
  for(std::list<PlotHistogram*>::iterator it=Instances.begin(); it!=Instances.end(); ++it)
  {
    if(*it==NULL) continue;
    if((*it)->Image == pImg)
    {
      return *it;
    }
  }
  return NULL;
}


void PlotHistogram::MarkDirtyImage(const Raster2DAbstract *ImageChanged, bool NewColorMode)
{
  if(ImageChanged==NULL) return;
  for(std::list<PlotHistogram*>::iterator it=Instances.begin(); it!=Instances.end(); ++it)
  {
    if(*it==NULL) continue;
    if((*it)->Image == ImageChanged)
    {
      (*it)->ImageIsChanged = true;
      if((*it)->ColorMode != NewColorMode)
      {
        (*it)->ColorMode = NewColorMode;
        if(NewColorMode)
        {
          wxColour lineColor = wxColour("CYAN"); 
          wxPen pen = wxPen(lineColor, 2, wxSOLID);
          (*it)->plotA->SetSignalLine(0, pen);
          (*it)->plotA->ShowSignalLine(1, true);
          (*it)->plotA->ShowSignalLine(2, true);
        }
        else
        {
          wxColour lineColor = wxColour("WHITE");
          wxPen pen = wxPen(lineColor, 2, wxSOLID);
          (*it)->plotA->SetSignalLine(0, pen);
          (*it)->plotA->ShowSignalLine(1, false);	// Hide series 1,2 in color mode.
          (*it)->plotA->ShowSignalLine(2, false);
        }
      }
    }
  }
}
