package worker

import (
	"fmt"
	"io"
	"os"
	"strconv"
	"sync"
	"time"

	"tygit.tuyoo.com/gocomponents/galog"
)

var startTime = time.Now()

// now returns time.Duration using stdlib time
func now() time.Duration { return time.Since(startTime) }

// Max size of the buffer of result channel.
const maxResult = 1000000

type result struct {
	offset   time.Duration
	duration time.Duration
}

type Work struct {
	// N is the total number of requests to make.
	N int

	// C is the concurrency level, the number of concurrent workers to run.
	C int

	// Qps is the rate limit in queries per second.
	QPS float64

	// Output represents the output type. If "csv" is provided, the
	// output will be dumped as a csv stream.
	Output string

	// Writer is where results will be written. If nil, results are written to stdout.
	Writer io.Writer

	initOnce sync.Once
	results  chan *result
	stopCh   chan struct{}
	start    time.Duration

	report *report
}

func (b *Work) writer() io.Writer {
	if b.Writer == nil {
		return os.Stdout
	}
	return b.Writer
}

// Init initializes internal data-structures
func (b *Work) Init() {
	b.initOnce.Do(func() {
		b.results = make(chan *result, min(b.C*1000, maxResult))
		b.stopCh = make(chan struct{}, b.C)
		b.initLogger()
	})
}

// Run makes all the requests, prints the summary. It blocks until
// all work is done.
func (b *Work) Run() {
	b.Init()
	b.start = now()
	b.report = newReport(b.writer(), b.results, b.Output, b.N)
	// Run the reporter first, it polls the result channel until it is closed.
	go func() {
		runReporter(b.report)
	}()
	b.runWorkers()
	b.Finish()
}

func (b *Work) Stop() {
	// Send stop signal so that workers can stop gracefully.
	for i := 0; i < b.C; i++ {
		b.stopCh <- struct{}{}
	}
}

func (b *Work) Finish() {
	close(b.results)
	total := now() - b.start
	// Wait until the reporter is done.
	<-b.report.done
	b.report.finalize(total)
}

var logger *galog.GALogger

func (b *Work) initLogger() {
	projectID := "28"
	clientID := "Android_4.827_tyGuest,nearme.nearme.0-hall28.oppo.bydzz"
	logger, _ = galog.NewGALogger("logs", projectID, clientID, galog.LogTypeAsset)
}

func (b *Work) doSth() {
	s := now()

	// doSth here.
	asset := galog.AssetProperties{}
	asset.SetAssetID("13101").
		SetAssetName("\u9501\u5b9a").
		SetAssetType("6").
		SetAssetFinal("2").
		SetAssetAssociated("3").
		SetAssetStartTime("0").
		SetAssetTimeLimit("0").
		SetAssetSource("").
		SetKV("uuid", strconv.Itoa(int(time.Now().UnixNano())))
	logger.
		GetEntry("asset_increase").
		SetDeviceID("").
		SetUserID("10086").
		SetProperties(asset).
		Flush()

	t := now()
	finish := t - s
	b.results <- &result{
		offset:   s,
		duration: finish,
	}
}

func (b *Work) runWorker(n int) {
	var throttle <-chan time.Time
	if b.QPS > 0 {
		t := time.NewTicker(time.Duration(1e6/(b.QPS)) * time.Microsecond)
		throttle = t.C
	}

	for i := 0; i < n; i++ {
		// Check if application is stopped. Do not send into a closed channel.
		select {
		case <-b.stopCh:
			return
		default:
			if b.QPS > 0 {
				<-throttle
			}
			b.doSth()
		}
	}
}

func (b *Work) runWorkers() {
	var wg sync.WaitGroup
	wg.Add(b.C)

	fmt.Printf("==RUN worker==> N:%d C:%d QPS:%v b.N / b.C:%v", b.N, b.C, b.QPS, b.N/b.C)

	// Ignore the case where b.N % b.C != 0.
	for i := 0; i < b.C; i++ {
		go func() {
			b.runWorker(b.N / b.C)
			wg.Done()
		}()
	}
	wg.Wait()
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
