// Copyright 2016 PingCAP, 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package session

import (
	"context"
	"fmt"
	"math/rand"
	"testing"
	"time"

	"github.com/pingcap/tidb/domain"
	"github.com/pingcap/tidb/kv"
	"github.com/pingcap/tidb/store/mockstore"
	"github.com/pingcap/tidb/util/sqlexec"
	log "github.com/sirupsen/logrus"
)

var smallCount = 100
var bigCount = 10000

func prepareBenchSession() (Session, *domain.Domain, kv.Storage) {
	store, err := mockstore.NewMockTikvStore()
	if err != nil {
		log.Fatal(err)
	}
	domain, err := BootstrapSession(store)
	if err != nil {
		log.Fatal(err)
	}
	log.SetLevel(log.ErrorLevel)
	se, err := CreateSession4Test(store)
	if err != nil {
		log.Fatal(err)
	}
	mustExecute(se, "use test")
	return se, domain, store
}

func prepareBenchData(se Session, colType string, valueFormat string, valueCount int) {
	mustExecute(se, "drop table if exists t")
	mustExecute(se, fmt.Sprintf("create table t (pk int primary key auto_increment, col %s, index idx (col))", colType))
	mustExecute(se, "begin")
	for i := 0; i < valueCount; i++ {
		mustExecute(se, "insert t (col) values ("+fmt.Sprintf(valueFormat, i)+")")
	}
	mustExecute(se, "commit")
}

func prepareSortBenchData(se Session, colType string, valueFormat string, valueCount int) {
	mustExecute(se, "drop table if exists t")
	mustExecute(se, fmt.Sprintf("create table t (pk int primary key auto_increment, col %s)", colType))
	mustExecute(se, "begin")
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < valueCount; i++ {
		if i%1000 == 0 {
			mustExecute(se, "commit")
			mustExecute(se, "begin")
		}
		mustExecute(se, "insert t (col) values ("+fmt.Sprintf(valueFormat, r.Intn(valueCount))+")")
	}
	mustExecute(se, "commit")
}

func prepareJoinBenchData(se Session, colType string, valueFormat string, valueCount int) {
	mustExecute(se, "drop table if exists t")
	mustExecute(se, fmt.Sprintf("create table t (pk int primary key auto_increment, col %s)", colType))
	mustExecute(se, "begin")
	for i := 0; i < valueCount; i++ {
		mustExecute(se, "insert t (col) values ("+fmt.Sprintf(valueFormat, i)+")")
	}
	mustExecute(se, "commit")
}

func readResult(ctx context.Context, rs sqlexec.RecordSet, count int) {
	req := rs.NewRecordBatch()
	for count > 0 {
		err := rs.Next(ctx, req)
		if err != nil {
			log.Fatal(err)
		}
		if req.NumRows() == 0 {
			log.Fatal(count)
		}
		count -= req.NumRows()
	}
	rs.Close()
}

func BenchmarkBasic(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select 1")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], 1)
	}
	b.StopTimer()
}

func BenchmarkTableScan(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "int", "%v", smallCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], smallCount)
	}
	b.StopTimer()
}

func BenchmarkExplainTableScan(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "int", "%v", 0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "explain select * from t")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], 1)
	}
	b.StopTimer()
}

func BenchmarkTableLookup(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "int", "%d", smallCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t where pk = 64")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], 1)
	}
	b.StopTimer()
}

func BenchmarkExplainTableLookup(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "int", "%d", 0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "explain select * from t where pk = 64")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], 1)
	}
	b.StopTimer()
}

func BenchmarkStringIndexScan(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "varchar(255)", "'hello %d'", smallCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t where col > 'hello'")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], smallCount)
	}
	b.StopTimer()
}

func BenchmarkExplainStringIndexScan(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "varchar(255)", "'hello %d'", 0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "explain select * from t where col > 'hello'")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], 1)
	}
	b.StopTimer()
}

func BenchmarkStringIndexLookup(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "varchar(255)", "'hello %d'", smallCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t where col = 'hello 64'")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], 1)
	}
	b.StopTimer()
}

func BenchmarkIntegerIndexScan(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "int", "%v", smallCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t where col >= 0")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], smallCount)
	}
	b.StopTimer()
}

func BenchmarkIntegerIndexLookup(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "int", "%v", smallCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t where col = 64")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], 1)
	}
	b.StopTimer()
}

func BenchmarkDecimalIndexScan(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "decimal(32,6)", "%v.1234", smallCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t where col >= 0")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], smallCount)
	}
	b.StopTimer()
}

func BenchmarkDecimalIndexLookup(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareBenchData(se, "decimal(32,6)", "%v.1234", smallCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t where col = 64.1234")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], 1)
	}
	b.StopTimer()
}

func BenchmarkInsertWithIndex(b *testing.B) {
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	mustExecute(se, "drop table if exists t")
	mustExecute(se, "create table t (pk int primary key, col int, index idx (col))")
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		mustExecute(se, fmt.Sprintf("insert t values (%d, %d)", i, i))
	}
	b.StopTimer()
}

func BenchmarkInsertNoIndex(b *testing.B) {
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	mustExecute(se, "drop table if exists t")
	mustExecute(se, "create table t (pk int primary key, col int)")
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		mustExecute(se, fmt.Sprintf("insert t values (%d, %d)", i, i))
	}
	b.StopTimer()
}

func BenchmarkSort(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareSortBenchData(se, "int", "%v", bigCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t order by col limit 50")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], 50)
	}
	b.StopTimer()
}

func BenchmarkJoin(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareJoinBenchData(se, "int", "%v", smallCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t a join t b on a.col = b.col")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], smallCount)
	}
	b.StopTimer()
}

func BenchmarkJoinLimit(b *testing.B) {
	ctx := context.Background()
	se, do, st := prepareBenchSession()
	defer func() {
		se.Close()
		st.Close()
		do.Close()
	}()
	prepareJoinBenchData(se, "int", "%v", smallCount)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rs, err := se.Execute(ctx, "select * from t a join t b on a.col = b.col limit 1")
		if err != nil {
			b.Fatal(err)
		}
		readResult(ctx, rs[0], 1)
	}
	b.StopTimer()
}
