/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.
 */
package chain

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/apache/servicecomb-service-center/pkg/util"
)

const (
	times = 1000000
	count = 10
)

func init() {
	for i := 0; i < count; i++ {
		RegisterHandler("_bench_handlers_", &handler{})
	}
}

type handler struct {
}

func (h *handler) Handle(i *Invocation) {
	i.Next()
}

func syncFunc(i int) {
	if i >= count {
		return
	}
	syncFunc(i + 1)
}

func BenchmarkInvocationOption(b *testing.B) {
	var (
		op   InvocationOp
		f    = func(r Result) {}
		opts = []InvocationOption{WithFunc(f), WithAsyncFunc(f)}
	)
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			for _, opt := range opts {
				op = opt(op)
			}
		}
	})
	b.ReportAllocs()
	// 50000000	        26.8 ns/op	       0 B/op	       0 allocs/op
}

func BenchmarkChain(b *testing.B) {
	var (
		ctx = util.NewStringContext(context.Background())
		f   = func(r Result) {}
	)

	b.N = times
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		inv := NewInvocation(ctx, NewChain("_bench_chain_", Handlers("_bench_handlers_")))
		inv.Invoke(f)

	}
	b.ReportAllocs()
	// 1000000	       735 ns/op	      80 B/op	       1 allocs/op
}

type mockHandler struct {
}

func (h *mockHandler) Handle(i *Invocation) {
	caseN := i.Context().Value(util.CtxKey("x")).(int)
	switch caseN {
	case 1:
		i.Success(caseN)
	case 2:
		i.Fail(errors.New("error"), caseN)
	case 3:
		i.Next(WithFunc(func(r Result) {
			i.WithContext("x", caseN*caseN)
		}))
	case 4:
		i.Next(WithAsyncFunc(func(r Result) {
			i.WithContext("x", caseN*caseN)
			ch, _ := i.Context().Value(util.CtxKey("ch")).(chan struct{})
			ch <- struct{}{}
		}))
	case 5:
		panic(errors.New("error"))
	case 6:
		i.Next(WithFunc(func(r Result) {
			panic(errors.New("error"))
		}))
	case 7:
		i.Next(WithAsyncFunc(func(r Result) {
			panic(errors.New("error"))
		}))
	default:
		i.WithContext("x", caseN-1)
		i.Next()
	}
}

func TestChain_Next(t *testing.T) {
	h := &mockHandler{}
	hs := Handlers("test")
	hs = []Handler{h, h, h}

	x := 0
	ch := NewChain("test", hs)
	if ch.Name() != "test" {
		t.Fatalf("TestChain_Next")
	}
	i := NewInvocation(context.Background(), ch)
	i.WithContext("x", x)
	i.Invoke(func(r Result) {
		if !r.OK || i.Context().Value(util.CtxKey("x")).(int) != -len(hs) {
			t.Fatalf("TestChain_Next")
		}
	})

	x = 1
	ch = NewChain("test", hs)
	i = NewInvocation(context.Background(), ch)
	i.WithContext("x", x)
	i.Invoke(func(r Result) {
		if !r.OK || r.Args[0].(int) != x {
			t.Fatalf("TestChain_Next")
		}
	})

	x = 2
	ch = NewChain("test", hs)
	i = NewInvocation(context.Background(), ch)
	i.WithContext("x", x)
	i.Invoke(func(r Result) {
		if r.OK || r.Args[0].(int) != x {
			t.Fatalf("TestChain_Next")
		}
	})

	x = 3
	ch = NewChain("test", hs)
	i = NewInvocation(context.Background(), ch)
	i.WithContext("x", x)
	i.Invoke(func(r Result) {
		if !r.OK || i.Context().Value(util.CtxKey("x")).(int) != x*x {
			t.Fatalf("TestChain_Next")
		}
		i.WithContext("x", x)
	})
	if i.Context().Value(util.CtxKey("x")).(int) != x {
		t.Fatalf("TestChain_Next")
	}

	x = 4 // async call back
	ch = NewChain("test", hs)
	i = NewInvocation(context.Background(), ch)
	i.WithContext("x", x)
	i.WithContext("ch", make(chan struct{}))
	i.Invoke(func(r Result) {
		if !r.OK || i.Context().Value(util.CtxKey("x")).(int) != x {
			t.Fatalf("TestChain_Next")
		}
	})
	<-i.Context().Value(util.CtxKey("ch")).(chan struct{})
	if i.Context().Value(util.CtxKey("x")).(int) != x*x {
		t.Fatalf("TestChain_Next")
	}

	x = 5
	ch = NewChain("test", hs)
	i = NewInvocation(context.Background(), ch)
	i.WithContext("x", x)
	i.Invoke(func(r Result) {
		if r.OK || r.Err == nil {
			t.Fatalf("TestChain_Next")
		}
	})

	x = 6
	ch = NewChain("test", hs)
	i = NewInvocation(context.Background(), ch)
	i.WithContext("x", x)
	i.Invoke(func(r Result) {
		if !r.OK || r.Err != nil {
			t.Fatalf("TestChain_Next")
		}
	})

	x = 7 // async call back
	ch = NewChain("test", hs)
	i = NewInvocation(context.Background(), ch)
	i.WithContext("x", x)
	i.Invoke(func(r Result) {
		if !r.OK || r.Err != nil {
			t.Fatalf("TestChain_Next")
		}
	})
	<-time.After(500 * time.Millisecond)
}
