// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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.

// +build windows

package eventlog

import (
	"bytes"
	"flag"
	"math/rand"
	"os/exec"
	"strconv"
	"testing"
	"time"

	elog "github.com/andrewkroh/sys/windows/svc/eventlog"
	"github.com/dustin/go-humanize"
)

// Benchmark tests with customized output. (`go test -v -benchtime 10s -benchtest .`)

var (
	benchTest    = flag.Bool("benchtest", false, "Run benchmarks for the eventlog package")
	injectAmount = flag.Int("inject", 50000, "Number of events to inject before running benchmarks")
)

// TestBenchmarkBatchReadSize tests the performance of different
// batch_read_size values.
func TestBenchmarkBatchReadSize(t *testing.T) {
	if !*benchTest {
		t.Skip("-benchtest not enabled")
	}

	log, err := initLog(providerName, sourceName, eventCreateMsgFile)
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		err := uninstallLog(providerName, sourceName, log)
		if err != nil {
			t.Fatal(err)
		}
	}()

	// Increase the log size so that it can hold these large events.
	output, err := exec.Command("wevtutil.exe", "sl", "/ms:1073741824", providerName).CombinedOutput()
	if err != nil {
		t.Fatal(err, string(output))
	}

	// Publish test messages:
	for i := 0; i < *injectAmount; i++ {
		err = log.Report(elog.Info, uint32(rand.Int63()%1000), []string{strconv.Itoa(i) + " " + randomSentence(256)})
		if err != nil {
			t.Fatal("ReportEvent error", err)
		}
	}

	benchTest := func(batchSize int) {
		var err error
		result := testing.Benchmark(func(b *testing.B) {
			eventlog, tearDown := setupWinEventLog(t, 0, map[string]interface{}{
				"name":            providerName,
				"batch_read_size": batchSize,
			})
			defer tearDown()
			b.ResetTimer()

			// Each iteration reads one batch.
			for i := 0; i < b.N; i++ {
				_, err = eventlog.Read()
				if err != nil {
					return
				}
			}
		})

		if err != nil {
			t.Fatal(err)
			return
		}

		t.Logf("batch_size=%v, total_events=%v, batch_time=%v, events_per_sec=%v, bytes_alloced_per_event=%v, total_allocs=%v",
			batchSize,
			result.N*batchSize,
			time.Duration(result.NsPerOp()),
			float64(batchSize)/time.Duration(result.NsPerOp()).Seconds(),
			humanize.Bytes(result.MemBytes/(uint64(result.N)*uint64(batchSize))),
			result.MemAllocs)
	}

	benchTest(10)
	benchTest(100)
	benchTest(500)
	benchTest(1000)
}

// Utility Functions

var randomWords = []string{
	"recover",
	"article",
	"highway",
	"bargain",
	"trolley",
	"college",
	"attract",
	"wriggle",
	"feather",
	"neutral",
	"percent",
	"quality",
	"manager",
	"hunting",
	"arrange",
}

func randomSentence(n uint) string {
	buf := bytes.NewBuffer(make([]byte, n))
	buf.Reset()

	for {
		idx := rand.Uint32() % uint32(len(randomWords))
		word := randomWords[idx]

		if buf.Len()+len(word) <= buf.Cap() {
			buf.WriteString(randomWords[idx])
		} else {
			break
		}

		if buf.Len()+1 <= buf.Cap() {
			buf.WriteByte(' ')
		} else {
			break
		}
	}

	return buf.String()
}
