//
//  ringbuffer.h
//
//  Created by hark on 2013/07/02.
//  Copyright (c) 2013年 hark. All rights reserved.
//

#ifndef HA_misc_ringbuffer_h
#define HA_misc_ringbuffer_h

#include <pthread.h>
#include <math.h>
#include <errno.h>

#ifdef __cplusplus

#include <vector>
#include "eventflag.h"
#include "lockobject.h"

template <class T>
    class ringbuffer {
private:

    const unsigned int buf_len;

    std::vector<T> buf;

    unsigned int wpos;
    unsigned int rpos;
    unsigned int data_num;
    
    ::pthread_mutex_t read_mutex;
    ::pthread_mutex_t write_mutex;
    ::pthread_mutex_t buf_mutex;

    EventFlag data_avail;
    EventFlag space_avail;
    
    unsigned int calc_buf_len(const unsigned int buffer_num) {
        // 2のべき乗になるように調整
        const int shift = (log10((double)buffer_num) / log10(2.0));
        return(1 << shift);
    }

    int init() {
        int ret = ::pthread_mutex_init(&buf_mutex, NULL);
        if (ret != 0) {
            return(ret);
        }
        ret = ::pthread_mutex_init(&read_mutex, NULL);
        if (ret != 0) {
            return(ret);
        }
        ret = ::pthread_mutex_init(&write_mutex, NULL);
        return(ret);
    }

    bool isfull() {
        lockobject lockd(&buf_mutex);
        bool retval(false);
        if ((rpos == wpos) && (data_num == buf_len)) {
            space_avail.Reset();
            retval = true;
        } else {
            retval = false;
        }
        return(retval);
    }

    bool isfull(const int wnum) {
        lockobject lockd(&buf_mutex);
        bool retval(false);
        if ((buf_len - data_num) < wnum) {
            space_avail.Reset();
            retval = true;
        } else {
            retval = false;
        }
        return(retval);
    }
        
    bool isempty() {
        lockobject lockd(&buf_mutex);
        bool retval(false);
        if ((rpos == wpos) && (data_num == 0)) {
            data_avail.Reset();
            retval = true;
        } else {
            retval = false;
        }
        return(retval);
    }

    bool isempty(const int rnum) {
        lockobject lockd(&buf_mutex);
        bool retval(false);
        if (data_num < rnum) {
            data_avail.Reset();
            retval = true;
        } else {
            retval = false;
        }
        return(retval);
    }
        
    void incwp() {
        ++wpos &= (buf_len - 1);
        data_num++;
        data_avail.Set();
    }

    void pluswp(const unsigned int delta) {
        wpos += delta;
        wpos &= (buf_len - 1);
        data_num += delta;
        data_avail.Set();
    }
        
    void incrp() {
        ++rpos &= (buf_len - 1);
        data_num--;
        space_avail.Set();
    }

    void plusrp(const unsigned int delta) {
        rpos += delta;
        rpos &= (buf_len - 1);
        data_num -= delta;
        space_avail.Set();
    }
        
public:

    ringbuffer (const ringbuffer&);
    ringbuffer& operator = (const ringbuffer&);
    ringbuffer ();
    explicit ringbuffer(const unsigned int buffer_num = 8192) :
        buf_len(calc_buf_len(buffer_num)),
        buf(calc_buf_len(buffer_num)),
        wpos(0),
        rpos(0),
        data_num(0)
    {
        init();
    }
    virtual ~ringbuffer() {
        ::pthread_mutex_destroy(&buf_mutex);
        ::pthread_mutex_destroy(&read_mutex);
        ::pthread_mutex_destroy(&write_mutex);
    }
    
    unsigned int getlen() const { return(buf_len); }
    unsigned int getdatnum() {
        lockobject lock(&buf_mutex);
        const unsigned int ret = data_num;
        return(ret);
    }
    int clear();
    int write(const T&, const unsigned int timeout_ms);
    int write(const std::vector<T>&, const unsigned int timeout_ms);
    int read(T&, const unsigned int timeout_ms);
    int read(std::vector<T>&, const unsigned int timeout_ms);
    int write(const T&);
    int read(T&);
        
};

template <class T>
    int ringbuffer<T>::write(const T& wd, const unsigned int timeout_ms)
    {
        lockobject lock(&write_mutex);
        if (isfull()) {
            int tret;
            if ((tret = space_avail.TimedWait(timeout_ms)) == 0) {
                lockobject lock(&buf_mutex);
                buf[wpos] = wd;
                incwp();
                return(0);
            } else {
                return(tret);
            }
        } else {
            lockobject lock(&buf_mutex);
            buf[wpos] = wd;
            incwp();
            return(0);
        }
    }

template <class T>
    int ringbuffer<T>::write(const std::vector<T>& wd, const unsigned int timeout_ms)
    {
        lockobject lock(&write_mutex);
        const int wnum = wd.size();

        if (isfull(wnum)) {
            int tret;
            if ((tret = space_avail.TimedWait(timeout_ms)) == 0) {
                lockobject lock(&buf_mutex);
                if (wnum <= (buf_len - data_num)) {
                    for(int i = 0; i < wnum; i++) {
                        buf[(wpos + i) & (buf_len - 1)] = wd[i];
                    }
                    pluswp(wnum);
                    return(0);
                } else {
                    // あきがない
                    return(ETIMEDOUT);
                }
            } else {
                return(tret);
            }
        } else {
            lockobject lock(&buf_mutex);
            if (wnum <= (buf_len - data_num)) {
                for(int i = 0; i < wnum; i++) {
                    buf[(wpos + i) & (buf_len - 1)] = wd[i];
                }
                pluswp(wnum);
                return(0);
            } else {
                // あきがない
                return(ETIMEDOUT);
            }
        }
    }
    
template <class T>
    int ringbuffer<T>::read(T& rd, const unsigned int timeout_ms)
    {
        lockobject lock(&read_mutex);
        if (isempty()) {
            int tret;
            if ((tret = data_avail.TimedWait(timeout_ms)) == 0) {
                lockobject lock(&buf_mutex);
                rd = buf[rpos];
                incrp();
                return(0);
            } else {
                return(tret);
            }
        } else {
            lockobject lock(&buf_mutex);
            rd = buf[rpos];
            incrp();            
            return(0);
        }
    }

template <class T>
    int ringbuffer<T>::read(std::vector<T>& rd, const unsigned int timeout_ms)
    {
        lockobject lock(&read_mutex);
        const int rnum = rd.size();

        if (isempty(rnum)) {
            int tret;
            if ((tret = data_avail.TimedWait(timeout_ms)) == 0) {
                lockobject lock(&buf_mutex);
                if (rnum <= data_num) {
                    for(int i = 0; i < rnum; i++) {
                        rd[i] = buf[(rpos + i) & (buf_len - 1)];
                    }
                    plusrp(rnum);
                    return(0);
                } else {
                    // たまってない
                    return(ETIMEDOUT);
                }
            } else {
                return(tret);
            }
        } else {
            lockobject lock(&buf_mutex);
            if (rnum <= data_num) {
                for(int i = 0; i < rnum; i++) {
                    rd[i] = buf[(rpos + i) & (buf_len - 1)];
                }
                plusrp(rnum);
                return(0);
            } else {
                // たまってない
                return(ETIMEDOUT);
            }
        }
    }
    
template <class T>
    int ringbuffer<T>::write(const T& wd)
    {
        lockobject lock(&write_mutex);
        if (isfull()) {
            int tret;
            if ((tret = space_avail.Wait()) == 0) {
                lockobject lock(&buf_mutex);
                buf[wpos] = wd;
                incwp();
                return(0);
            } else {
                return(tret);
            }
        } else {
            lockobject lock(&buf_mutex);
            buf[wpos] = wd;
            incwp();
            return(0);
        }
    }
    
template <class T>
    int ringbuffer<T>::read(T& rd)
    {
        lockobject lock(&read_mutex);
        if (isempty()) {
            int tret;
            if ((tret = data_avail.Wait()) == 0) {
                lockobject lock(&buf_mutex);
                rd = buf[rpos];
                incrp();
                return(0);
            } else {
                return(tret);
            }
        } else {
            lockobject lock(&buf_mutex);
            rd = buf[rpos];
            incrp();
            return(0);
        }
    }

    
template <class T>
    int ringbuffer<T>::clear()
    {

        lockobject locka(&read_mutex);
        lockobject lockb(&write_mutex);
        lockobject lockc(&buf_mutex);

        rpos = 0;
        wpos = 0;
        data_num = 0;
        
        data_avail.Reset();
        space_avail.Set();
        
        return(0);
    }
    

#endif /* __cplusplus */

#endif
