// +build go1.7

package oss

import (
	"fmt"
	"io"
	"math"
	"time"

	"golang.org/x/time/rate"
)

const (
	perTokenBandwidthSize int = 1024
)

// OssLimiter wrapper rate.Limiter
type OssLimiter struct {
	limiter *rate.Limiter
}

// GetOssLimiter create OssLimiter
// uploadSpeed KB/s
func GetOssLimiter(uploadSpeed int) (ossLimiter *OssLimiter, err error) {
	limiter := rate.NewLimiter(rate.Limit(uploadSpeed), uploadSpeed)

	// first consume the initial full token,the limiter will behave more accurately
	limiter.AllowN(time.Now(), uploadSpeed)

	return &OssLimiter{
		limiter: limiter,
	}, nil
}

// LimitSpeedReader for limit bandwidth upload
type LimitSpeedReader struct {
	io.ReadCloser
	reader     io.Reader
	ossLimiter *OssLimiter
}

// Read
func (r *LimitSpeedReader) Read(p []byte) (n int, err error) {
	n = 0
	err = nil
	start := 0
	burst := r.ossLimiter.limiter.Burst()
	var end int
	var tmpN int
	var tc int
	for start < len(p) {
		if start+burst*perTokenBandwidthSize < len(p) {
			end = start + burst*perTokenBandwidthSize
		} else {
			end = len(p)
		}

		tmpN, err = r.reader.Read(p[start:end])
		if tmpN > 0 {
			n += tmpN
			start = n
		}

		if err != nil {
			return
		}

		tc = int(math.Ceil(float64(tmpN) / float64(perTokenBandwidthSize)))
		now := time.Now()
		re := r.ossLimiter.limiter.ReserveN(now, tc)
		if !re.OK() {
			err = fmt.Errorf("LimitSpeedReader.Read() failure,ReserveN error,start:%d,end:%d,burst:%d,perTokenBandwidthSize:%d",
				start, end, burst, perTokenBandwidthSize)
			return
		}
		timeDelay := re.Delay()
		time.Sleep(timeDelay)
	}
	return
}

// Close ...
func (r *LimitSpeedReader) Close() error {
	rc, ok := r.reader.(io.ReadCloser)
	if ok {
		return rc.Close()
	}
	return nil
}
