// Copyright (c) 2015 Baidu, Inc.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Author: Ge,Jun (gejun@baidu.com)
// Date: Wed Jul 29 23:25:43 CST 2015

#ifndef  BVAR_WINDOW_H
#define  BVAR_WINDOW_H

#include <limits>                                 // std::numeric_limits
#include <math.h>                                 // round
#include <gflags/gflags_declare.h>
#include "butil/logging.h"                         // LOG
#include "bvar/detail/sampler.h"
#include "bvar/detail/series.h"
#include "bvar/variable.h"

namespace bvar {

DECLARE_int32(bvar_dump_interval);

enum SeriesFrequency {
    SERIES_IN_WINDOW = 0,
    SERIES_IN_SECOND = 1
};

namespace detail {
// Just for constructor reusing of Window<>
template <typename R, SeriesFrequency series_freq>
class WindowBase : public Variable {
public:
    typedef typename R::value_type value_type;
    typedef typename R::sampler_type sampler_type;

    class SeriesSampler : public detail::Sampler {
    public:
        struct Op {
            explicit Op(R* var) : _var(var) {}
            void operator()(value_type& v1, const value_type& v2) const {
                _var->op()(v1, v2);
            }
        private:
            R* _var;
        };
        SeriesSampler(WindowBase* owner, R* var)
            : _owner(owner), _series(Op(var)) {}
        ~SeriesSampler() {}
        void take_sample() {
            if (series_freq == SERIES_IN_SECOND) {
                // Get one-second window value for PerSecond<>, otherwise the
                // "smoother" plot may hide peaks.
                _series.append(_owner->get_value(1));
            } else {
                // Get the value inside the full window. "get_value(1)" is 
                // incorrect when users intend to see aggregated values of
                // the full window in the plot.
                _series.append(_owner->get_value());
            }
        }
        void describe(std::ostream& os) { _series.describe(os, NULL); }
    private:
        WindowBase* _owner;
        detail::Series<value_type, Op> _series;
    };
    
    WindowBase(R* var, time_t window_size)
        : _var(var)
        , _window_size(window_size > 0 ? window_size : FLAGS_bvar_dump_interval)
        , _sampler(var->get_sampler())
        , _series_sampler(NULL) {
        CHECK_EQ(0, _sampler->set_window_size(_window_size));
    }
    
    ~WindowBase() {
        hide();
        if (_series_sampler) {
            _series_sampler->destroy();
            _series_sampler = NULL;
        }
    }

    bool get_span(time_t window_size, detail::Sample<value_type>* result) const {
        return _sampler->get_value(window_size, result);
    }

    bool get_span(detail::Sample<value_type>* result) const {
        return get_span(_window_size, result);
    }

    virtual value_type get_value(time_t window_size) const {
        detail::Sample<value_type> tmp;
        if (get_span(window_size, &tmp)) {
            return tmp.data;
        }
        return value_type();
    }

    value_type get_value() const { return get_value(_window_size); }
    
    // Implement Variable::describe() and Variable::get_value().
    void describe(std::ostream& os, bool quote_string) const {
        if (butil::is_same<value_type, std::string>::value && quote_string) {
            os << '"' << get_value() << '"';
        } else {
            os << get_value();
        }
    }
    
#ifdef BAIDU_INTERNAL
    void get_value(boost::any* value) const { *value = get_value(); }
#endif

    time_t window_size() const { return _window_size; }

    int describe_series(std::ostream& os, const SeriesOptions& options) const {
        if (_series_sampler == NULL) {
            return 1;
        }
        if (!options.test_only) {
            _series_sampler->describe(os);
        }
        return 0;
    }

    void get_samples(std::vector<value_type> *samples) const {
        samples->clear();
        samples->reserve(_window_size);
        return _sampler->get_samples(samples, _window_size);
    }

protected:
    // @Variable
    int expose_impl(const butil::StringPiece& prefix,
                    const butil::StringPiece& name,
                    DisplayFilter display_filter) {
        const int rc = Variable::expose_impl(prefix, name, display_filter);
        if (rc == 0 &&
            _series_sampler == NULL &&
            FLAGS_save_series) {
            _series_sampler = new SeriesSampler(this, _var);
            _series_sampler->schedule();
        }
        return rc;
    }

    R* _var;
    time_t _window_size;
    sampler_type* _sampler;
    SeriesSampler* _series_sampler;
};

}  // namespace detail

// Get data within a time window.
// The time unit is 1 second fixed.
// Window relies on other bvar which should be constructed before this window
// and destructs after this window.

// R must:
// - have get_sampler() (not require thread-safe)
// - defined value_type and sampler_type
template <typename R, SeriesFrequency series_freq = SERIES_IN_WINDOW>
class Window : public detail::WindowBase<R, series_freq> {
    typedef detail::WindowBase<R, series_freq> Base;
    typedef typename R::value_type value_type;
    typedef typename R::sampler_type sampler_type;
public:
    // Different from PerSecond, we require window_size here because get_value
    // of Window is largely affected by window_size while PerSecond is not.
    Window(R* var, time_t window_size) : Base(var, window_size) {}
    Window(const butil::StringPiece& name, R* var, time_t window_size)
        : Base(var, window_size) {
        this->expose(name);
    }
    Window(const butil::StringPiece& prefix,
           const butil::StringPiece& name, R* var, time_t window_size)
        : Base(var, window_size) {
        this->expose_as(prefix, name);
    }
};

// Get data per second within a time window.
// The only difference between PerSecond and Window is that PerSecond divides
// the data by time duration.
template <typename R>
class PerSecond : public detail::WindowBase<R, SERIES_IN_SECOND> {
    typedef detail::WindowBase<R, SERIES_IN_SECOND> Base;
    typedef typename R::value_type value_type;
    typedef typename R::sampler_type sampler_type;
public:
    // If window_size is non-positive or absent, use FLAGS_bvar_dump_interval.
    PerSecond(R* var) : Base(var, -1) {}
    PerSecond(R* var, time_t window_size) : Base(var, window_size) {}
    PerSecond(const butil::StringPiece& name, R* var) : Base(var, -1) {
        this->expose(name);
    }
    PerSecond(const butil::StringPiece& name, R* var, time_t window_size)
        : Base(var, window_size) {
        this->expose(name);
    }
    PerSecond(const butil::StringPiece& prefix,
              const butil::StringPiece& name, R* var)
        : Base(var, -1) {
        this->expose_as(prefix, name);
    }
    PerSecond(const butil::StringPiece& prefix,
              const butil::StringPiece& name, R* var, time_t window_size)
        : Base(var, window_size) {
        this->expose_as(prefix, name);
    }

    virtual value_type get_value(time_t window_size) const {
        detail::Sample<value_type> s;
        this->get_span(window_size, &s);
        // We may test if the multiplication overflows and use integral ops
        // if possible. However signed/unsigned 32-bit/64-bit make the solution
        // complex. Since this function is not called often, we use floating
        // point for simplicity.
        if (s.time_us <= 0) {
            return static_cast<value_type>(0);
        }
        if (butil::is_floating_point<value_type>::value) {
            return static_cast<value_type>(s.data * 1000000.0 / s.time_us);
        } else {
            return static_cast<value_type>(round(s.data * 1000000.0 / s.time_us));
        }
    }
};

}  // namespace bvar

#endif  //BVAR_WINDOW_H
