/////////////////////////////////////////////////////////////////////////////
// Name:        wx/generic/private/widthcalc.h
// Purpose:     wxMaxWidthCalculatorBase helper class.
// Author:      Václav Slavík, Kinaou Hervé
// Copyright:   (c) 2015 wxWidgets team
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#ifndef _WX_GENERIC_PRIVATE_WIDTHCALC_H_
#define _WX_GENERIC_PRIVATE_WIDTHCALC_H_

#include "wx/defs.h"

#if wxUSE_DATAVIEWCTRL || wxUSE_LISTCTRL

#include "wx/log.h"
#include "wx/timer.h"

// ----------------------------------------------------------------------------
// wxMaxWidthCalculatorBase: base class for calculating max column width
// ----------------------------------------------------------------------------

class wxMaxWidthCalculatorBase
{
public:
    // column of which calculate the width
    explicit wxMaxWidthCalculatorBase(size_t column)
        : m_column(column),
          m_width(0)
    {
    }

    void UpdateWithWidth(int width)
    {
        m_width = wxMax(m_width, width);
    }

    // Update the max with for the expected row
    virtual void UpdateWithRow(int row) = 0;

    int GetMaxWidth() const { return m_width; }
    size_t GetColumn() const { return m_column; }

    void
    ComputeBestColumnWidth(size_t count,
                           size_t first_visible,
                           size_t last_visible)
    {
        // The code below deserves some explanation. For very large controls, we
        // simply can't afford to calculate sizes for all items, it takes too
        // long. So the best we can do is to check the first and the last N/2
        // items in the control for some sufficiently large N and calculate best
        // sizes from that. That can result in the calculated best width being too
        // small for some outliers, but it's better to get slightly imperfect
        // result than to wait several seconds after every update. To avoid highly
        // visible miscalculations, we also include all currently visible items
        // no matter what.  Finally, the value of N is determined dynamically by
        // measuring how much time we spent on the determining item widths so far.

#if wxUSE_STOPWATCH
        size_t top_part_end = count;
        static const long CALC_TIMEOUT = 20/*ms*/;
        // don't call wxStopWatch::Time() too often
        static const unsigned CALC_CHECK_FREQ = 100;
        wxStopWatch timer;
#else
        // use some hard-coded limit, that's the best we can do without timer
        size_t top_part_end = wxMin(500, count);
#endif // wxUSE_STOPWATCH/!wxUSE_STOPWATCH

        size_t row = 0;

        for ( row = 0; row < top_part_end; row++ )
        {
#if wxUSE_STOPWATCH
            if ( row % CALC_CHECK_FREQ == CALC_CHECK_FREQ-1 &&
                 timer.Time() > CALC_TIMEOUT )
                break;
#endif // wxUSE_STOPWATCH
            UpdateWithRow(row);
        }

        // row is the first unmeasured item now; that's our value of N/2
        if ( row < count )
        {
            top_part_end = row;

            // add bottom N/2 items now:
            const size_t bottom_part_start = wxMax(row, count - row);
            for ( row = bottom_part_start; row < count; row++ )
            {
                UpdateWithRow(row);
            }

            // finally, include currently visible items in the calculation:
            first_visible = wxMax(first_visible, top_part_end);
            last_visible = wxMin(bottom_part_start, last_visible);

            for ( row = first_visible; row < last_visible; row++ )
            {
                UpdateWithRow(row);
            }

            wxLogTrace("items container",
                       "determined best size from %zu top, %zu bottom "
                       "plus %zu more visible items out of %zu total",
                       top_part_end,
                       count - bottom_part_start,
                       last_visible - first_visible,
                       count);
        }
    }

private:
    const size_t m_column;
    int m_width;

    wxDECLARE_NO_COPY_CLASS(wxMaxWidthCalculatorBase);
};

#endif // wxUSE_DATAVIEWCTRL || wxUSE_LISTCTRL

#endif // _WX_GENERIC_PRIVATE_WIDTHCALC_H_
