/////////////////////////////////////////////////////////////////////////////
// Name:        oscopectrl.h
// Purpose:     wxOScopeData and wxOScopeCtrl
// Author:      Francesco Montorsi
// Created:     2006/11/14
// RCS-ID:      $Id: oscopectrl.h,v 1.1.1.1 2006/12/12 09:40:44 frm Exp $
// Copyright:   (c) 2006 Francesco Montorsi
//              (inspired from the aMule's OScopeCtrl which is copyrighted
//               by the aMule Team - http://www.amule.org)
// Licence:     wxWidgets licence
/////////////////////////////////////////////////////////////////////////////

#ifndef OSCOPECTRL_H
#define OSCOPECTRL_H

#include <wx/defs.h>
#include <wx/dynarray.h>
#include <wx/timer.h>
#include <wx/pen.h>
#include <wx/brush.h>



// ----------------------------------------------------------------------------
// wxOScopeData
// ----------------------------------------------------------------------------

WX_DECLARE_OBJARRAY(float, wxArrayFloat);

// A simple class which manages "sampled" data meant to be plotted in
// a wxOScopeCtrl widget.
class wxOScopeData
{
protected:

    // The data collected so far.
    // The i-th value of this array has been sampled at the 
    // m_nSampleRate*i time instant.
    wxArrayFloat m_data;

    // The pen used to plot this data.
	wxPen m_penPlot;

    // The sample rate for this data (in milliseconds).
    size_t m_nSampleRate;

    // The lower & upper values to use for the y-scale when plotting this data.
	float m_fLowerLimit, m_fUpperLimit;

    // The minimum & maximum values of this array.
    // These are always updated by wxOScopeData automatically and
    // are usually used to update m_fUpperLimit/m_fLowerLimit.
    float m_fMin, m_fMax;

public:
    wxOScopeData(size_t rate = 0, float lower = 0.0, float upper = 1.0,
                 const wxColour &colour = *wxRED)
    {
        m_nSampleRate = rate;
        SetPlotColor(colour);
		SetRange(lower, upper);
        m_fMin = m_fMax = 0;
    }

    void AddSample(float f)
    { 
        m_data.Add(f); 
        m_fMin = wxMin(m_fMin, f);
        m_fMax = wxMax(m_fMax, f);
    }

    void AddSampleAndUpdateRange(float f, 
                                 float lowerOffset = 0.0, 
                                 float upperOffset = 0.0);

public:     // setters

	void SetRange(float dLower, float dUpper)
        { 
            m_fLowerLimit=dLower; 
            m_fUpperLimit=dUpper; 
            wxASSERT(m_fLowerLimit < m_fUpperLimit);
        }

    void SetPlotColor(const wxColour &color)
        { m_penPlot.SetColour(color); }
    void SetPlotPen(const wxPen &pen)
        { m_penPlot=pen; }

    // scans the data and finds the lower and upper limits
    // then adds the given offsets to them and set them as current range.
    void SetBestRange(float lowerOffset = 0.0, float upperOffset = 0.0);

public:     // getters

    float GetLowerLimit() const
        { return m_fLowerLimit; }
    float GetUpperLimit() const
        { return m_fUpperLimit; }

    float GetMax() const
        { return m_fMax; }
    float GetMin() const
        { return m_fMin; }

    size_t GetSampleRate() const
        { return m_nSampleRate; }

    // the Y of the sampled point
    float GetSample(size_t i) const
        { return m_data[i]; }

    // the X of the sampled point
    size_t GetTimeFor(size_t i) const
        { return i * m_nSampleRate; }

    size_t GetSampleCount() const
        { return m_data.GetCount(); }

    // returns the index of the first sampled point which 
    // precedes the given time instant (in milliseconds)
    size_t GetIdxOfFirstSamplePreceding(size_t time) const
        { return time / m_nSampleRate; }

    const wxPen& GetPlotPen() const
        { return m_penPlot; }
};

WX_DECLARE_OBJARRAY(wxOScopeData, wxOScopeDataArray);



// ----------------------------------------------------------------------------
// wxOScopeCtrl
// ----------------------------------------------------------------------------

#define wxOSC_DEFAULT_STYLE             0
#define wxOSC_NO_GRID                   1
#define wxOSC_NO_AXIS_LABELS            2

// wxOScopeCtrl is basically a simple control which plots in 2D an array of
// points represented by wxOScopeData class.
// The control is optimized for drawing points incrementally: i.e. typically
// you will call the AddSampleData() function to add a new point and update
// the graph periodically.
// Another peculiarity of this control is its "auto-scrolling" feature: adding
// new points, when there is no more space in the X axis, determines that "oldest"
// points are automatically hidden: just as if the control scrolled on right.
// The control also supports labels and multiple data sets.
class wxOScopeCtrl : public wxControl
{
public:
    wxOScopeCtrl() { Init(); }

	wxOScopeCtrl(wxWindow *parent, wxWindowID id = wxID_ANY,
                 const wxPoint &pos = wxDefaultPosition,
                 const wxSize &sz = wxDefaultSize,
                 long style = wxOSC_DEFAULT_STYLE,
                 const wxString &name = wxT("oScopeCtrl"))
    {
        Init();

        Create(parent, id, pos, sz, style, name);
    }

	bool Create(wxWindow *parent, wxWindowID id = wxID_ANY,
                const wxPoint &pos = wxDefaultPosition,
                const wxSize &sz = wxDefaultSize,
                long style = wxOSC_DEFAULT_STYLE,
                const wxString &name = wxT("oScopeCtrl"));

public:     // data management

    // REMEMBER: call UpdateGraph() after using these functions!
    void AddData(const wxOScopeData &data)
        { m_trends.Add(data); }
    void RemoveData(unsigned int index)
        { m_trends.RemoveAt(index); }

    // updates the i-th trend with a new data point
    // and updates the graph.
    // This function is typically called periodically to
    // show how the i-th trend varies with time
    void AddSampleData(size_t i, float newvalue);

    // like AddSampleData but also updates the data range
    void AddSampleDataAndUpdateRange(size_t i, float newvalue,
                                     float lowerOffset = 0.0,
                                     float upperOffset = 0.0);

    // NOTE: don't use Update() function name as it would conflict 
    //       with wxWindow::Update!
    void UpdateGraph();
    void UpdateAll();

    virtual void Update()
    {
        UpdateAll();
        return wxWindow::Update();
    }

    virtual void Refresh()
    {
        // never refresh this window erasing the background:
        // in any case our OnPaint() will redraw all pixels
        wxWindow::Refresh(false, NULL);
    }

public:     // setters
            // remember to call UpdateAll() and Refresh() after using
            // these functions!

    // tells the control to avoid to dynamically choose
    // the best x range and rather use the given one (in milliseconds).
    // NB: if you don't call this function, the control will
    //     automatically shrink the maximum X as the data is
    //     added so that all data can be shown at the same time.
    //     When this function is called you turn on the "auto-scroll"
    //     work mode: the X range is fixed and the "oldest" points
    //     are clipped so that the control seems to scroll on the right
    void SetFixedXRange(size_t newrange);

    // tells the control to avoid to dynamically calculate
    // the lower & upper limits and rather use the given ones
	void SetFixedYRange(float dLower, float dUpper)
    {
        m_fLowerLimit = dLower;
        m_fUpperLimit = dUpper;
        m_bFixedYLimits = true;
    }

	void SetXUnits(const wxString& string, 
                   const wxString& XMin = wxEmptyString, 
                   const wxString& XMax = wxEmptyString)
    {
        m_strXUnits = string;
        m_strXMax = XMax;
        m_strXMin = XMin;
    }

	void SetYUnits(const wxString& string, 
                   const wxString& YMin = wxEmptyString, 
                   const wxString& YMax = wxEmptyString)
    {
        m_strYUnits = string;
        m_strYMax = YMax;
        m_strYMin = YMin;
    }

	void SetGridColour(const wxColour &color)
        { m_penGrid.SetColour(color); }
	void SetGridPen(const wxPen &pen)
        { m_penGrid = pen; }

    void SetGridLineDistance(size_t xstep, size_t ystep)
        { m_nXGridLineGap=xstep; m_nYGridLineGap=ystep; }

public:     // getters

    const wxOScopeData &GetData(size_t i) const
        { return m_trends[i]; }

    unsigned int GetDataCount() const
        { return m_trends.GetCount(); }

    // the y min and y max shown on this graph
    float GetUpperLimit() const
        { return m_fUpperLimit; }
    float GetLowerLimit() const
        { return m_fLowerLimit; }

    // returns how many milliseconds of samples are currently 
    // shown on the X axis; i.e. if the last X of the axis is
    // associated with the 1000-th millisec while the first X
    // is associated with the 200-th millisec this function will
    // return 800.
    size_t GetTimeRange() const;

    // returns the time (in milliseconds) of the last sampled
    // data looking at all our data arrays.
    size_t GetMaxTime() const;

    wxString GetXMin() const;
    wxString GetYMin() const;
    wxString GetXMax() const;
    wxString GetYMax() const;

    wxString GetYUnits() const
        { return m_strYUnits; }
    wxString GetXUnits() const;

    virtual wxSize DoGetBestSize() const;

public:     // checkers

    bool IsYRangeFixed() const
        { return m_bFixedYLimits; }
    bool IsXRangeFixed() const
        { return m_nTimeRange != 0; }

public:     // event handlers

	void OnPaint(wxPaintEvent& evt);
	void OnSize(wxSizeEvent& evt);

protected:      // internal utils

    void CreateBitmaps(const wxSize &);
	void UpdateGrid();
    void UpdatePlotRect();

    // returns true if upper & lower limits have been changed
    bool UpdateLimits();

protected:      // internal data

	wxOScopeDataArray m_trends;

    wxPen m_penGrid;
	wxBitmap m_bmpGrid;
	wxBitmap m_bmpPlot;

    // small optimization: UpdateGraph() is the most-often called 
    // function so instead of creating a memory DC there and then
    // select m_bmpPlot there all the times we do that only once
    // and then reuse the DC.
    wxMemoryDC m_dcPlot;

	wxString m_strXUnits, m_strXMin, m_strXMax;
	wxString m_strYUnits, m_strYMin, m_strYMax;

    wxRect m_rectPlot;

	size_t m_nXGridLineGap;
    size_t m_nYGridLineGap;

	size_t m_nYDecimals;

    // the lower and upper limits for the Y
    float m_fLowerLimit, m_fUpperLimit;
    bool m_bFixedYLimits;        // see SetFixedYRange

    // the time range, in milliseconds
    size_t m_nTimeRange;

private:

    void Init()
    {
        m_nXGridLineGap = 5;
        m_nYGridLineGap = 5;
        m_nYDecimals = 0;

        // auto adapt the time range dynamically to 
        // be able to show all data at once
        m_nTimeRange = 0;

        // mark lower & upper limits as out-of-date
        m_fLowerLimit = m_fUpperLimit = 0;
        m_bFixedYLimits = false;

        // default colour
    	m_penGrid.SetColour(wxTheColourDatabase->Find(wxT("DARK GREEN")));
    }

	DECLARE_EVENT_TABLE()
};

#endif // OSCOPECTRL_H
