// Copyright 2020 Google Inc. All rights reserved.
//
// 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,
// 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 blueprint

import (
	"fmt"
	"reflect"
	"strings"
	"testing"
)

type providerTestModule struct {
	ModuleBase
	SimpleName
	properties struct {
		Deps []string
	}

	mutatorProviderValues              []string
	generateBuildActionsProviderValues []string
}

func newProviderTestModule() (Module, []interface{}) {
	m := &providerTestModule{}
	return m, []interface{}{&m.properties, &m.SimpleName.Properties}
}

var providerTestSingletonProvider = NewSingletonProvider[bool]()

type providerTestParallelSingleton struct {
	visitAllSingletons bool
}

func (s *providerTestParallelSingleton) GenerateBuildActions(ctx SingletonContext) {
	if s.visitAllSingletons {
		ctx.VisitAllSingletons(func(s SingletonProxy) {})
	}
	ctx.SetSingletonProvider(providerTestSingletonProvider, true)
}

func (s *providerTestParallelSingleton) IncrementalSupported() bool {
	return true
}

type providerTestSequentialSingleton struct {
	getModuleProvider bool
	setModuleProvider bool
}

func (s *providerTestSequentialSingleton) GenerateBuildActions(ctx SingletonContext) {
	if s.getModuleProvider {
		ctx.VisitAllSingletons(func(s SingletonProxy) {
			ctx.OtherSingletonProvider(s, providerTestMutatorInfoProvider)
		})
	}
	if s.setModuleProvider {
		ctx.SetSingletonProvider(providerTestMutatorInfoProvider, nil)
	}
	ctx.VisitAllSingletons(func(s SingletonProxy) {
		ctx.OtherSingletonProvider(s, providerTestSingletonProvider)
	})
}

func (s *providerTestSequentialSingleton) IncrementalSupported() bool {
	return true
}

type providerTestMutatorInfo struct {
	Values []string
}

type providerTestGenerateBuildActionsInfo struct {
	Value string
}

type providerTestUnsetInfo string

var providerTestMutatorInfoProvider = NewMutatorProvider[*providerTestMutatorInfo]("provider_mutator")
var providerTestGenerateBuildActionsInfoProvider = NewProvider[*providerTestGenerateBuildActionsInfo]()
var providerTestUnsetInfoProvider = NewMutatorProvider[providerTestUnsetInfo]("provider_mutator")
var providerTestUnusedMutatorProvider = NewMutatorProvider[*struct{ unused string }]("nonexistent_mutator")

func (p *providerTestModule) GenerateBuildActions(ctx ModuleContext) {
	unset, ok := ModuleProvider(ctx, providerTestUnsetInfoProvider)
	if ok {
		panic(fmt.Errorf("expected false return value for providerTestGenerateBuildActionsInfoProvider before it was set"))
	}
	if unset != "" {
		panic(fmt.Errorf("expected zero value for providerTestGenerateBuildActionsInfoProvider before it was set, got %q",
			unset))
	}

	// Verify reading providerTestUnusedMutatorProvider doesn't panic
	_, _ = ModuleProvider(ctx, providerTestUnusedMutatorProvider)

	SetProvider(ctx, providerTestGenerateBuildActionsInfoProvider, &providerTestGenerateBuildActionsInfo{
		Value: ctx.ModuleName(),
	})

	mp, ok := ModuleProvider(ctx, providerTestMutatorInfoProvider)
	if ok {
		p.mutatorProviderValues = mp.Values
	}

	ctx.VisitDirectDeps(func(module Module) {
		gbap, _ := OtherModuleProvider(ctx, module, providerTestGenerateBuildActionsInfoProvider)
		if gbap != nil {
			p.generateBuildActionsProviderValues = append(p.generateBuildActionsProviderValues, gbap.Value)
		}
	})
}

func providerTestDepsMutator(ctx BottomUpMutatorContext) {
	if p, ok := ctx.Module().(*providerTestModule); ok {
		ctx.AddDependency(ctx.Module(), nil, p.properties.Deps...)
	}
}

func providerTestMutator(ctx BottomUpMutatorContext) {
	values := []string{strings.ToLower(ctx.ModuleName())}

	ctx.VisitDirectDeps(func(module Module) {
		mp, _ := OtherModuleProvider(ctx, module, providerTestMutatorInfoProvider)
		if mp != nil {
			values = append(values, mp.Values...)
		}
	})

	SetProvider(ctx, providerTestMutatorInfoProvider, &providerTestMutatorInfo{
		Values: values,
	})
}

func providerTestAfterMutator(ctx BottomUpMutatorContext) {
	// Verify reading providerTestUnusedMutatorProvider doesn't panic
	_, _ = ModuleProvider(ctx, providerTestMutatorInfoProvider)
}

func TestProviders(t *testing.T) {
	ctx := NewContext()
	ctx.RegisterModuleType("provider_module", newProviderTestModule)
	ctx.RegisterBottomUpMutator("provider_deps_mutator", providerTestDepsMutator)
	ctx.RegisterBottomUpMutator("provider_mutator", providerTestMutator)
	ctx.RegisterBottomUpMutator("provider_after_mutator", providerTestAfterMutator)

	ctx.MockFileSystem(map[string][]byte{
		"Android.bp": []byte(`
			provider_module {
				name: "A",
				deps: ["B"],
			}
	
			provider_module {
				name: "B",
				deps: ["C", "D"],
			}
	
			provider_module {
				name: "C",
				deps: ["D"],
			}
	
			provider_module {
				name: "D",
			}
		`),
	})

	_, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
	if len(errs) == 0 {
		_, errs = ctx.ResolveDependencies(nil)
	}
	if len(errs) == 0 {
		_, errs = ctx.PrepareBuildActions(nil)
	}
	if len(errs) > 0 {
		t.Errorf("unexpected errors:")
		for _, err := range errs {
			t.Errorf("  %s", err)
		}
		t.FailNow()
	}

	aModule := ctx.moduleGroupFromName("A", nil).moduleByVariantName("").logicModule.(*providerTestModule)
	if g, w := aModule.generateBuildActionsProviderValues, []string{"B"}; !reflect.DeepEqual(g, w) {
		t.Errorf("expected A.generateBuildActionsProviderValues %q, got %q", w, g)
	}
	if g, w := aModule.mutatorProviderValues, []string{"a", "b", "c", "d", "d"}; !reflect.DeepEqual(g, w) {
		t.Errorf("expected A.mutatorProviderValues %q, got %q", w, g)
	}

	bModule := ctx.moduleGroupFromName("B", nil).moduleByVariantName("").logicModule.(*providerTestModule)
	if g, w := bModule.generateBuildActionsProviderValues, []string{"C", "D"}; !reflect.DeepEqual(g, w) {
		t.Errorf("expected B.generateBuildActionsProviderValues %q, got %q", w, g)
	}
	if g, w := bModule.mutatorProviderValues, []string{"b", "c", "d", "d"}; !reflect.DeepEqual(g, w) {
		t.Errorf("expected B.mutatorProviderValues %q, got %q", w, g)
	}
}

type invalidProviderUsageMutatorInfo string
type invalidProviderUsageGenerateBuildActionsInfo string

var invalidProviderUsageMutatorInfoProvider = NewMutatorProvider[invalidProviderUsageMutatorInfo]("mutator_under_test")
var invalidProviderUsageGenerateBuildActionsInfoProvider = NewProvider[invalidProviderUsageGenerateBuildActionsInfo]()

type invalidProviderUsageTestModule struct {
	ModuleBase
	SimpleName
	properties struct {
		Deps []string

		Early_mutator_set_of_mutator_provider       bool
		Late_mutator_set_of_mutator_provider        bool
		Late_build_actions_set_of_mutator_provider  bool
		Early_mutator_set_of_build_actions_provider bool

		Early_mutator_get_of_mutator_provider       bool
		Early_mutator_get_of_build_actions_provider bool

		Duplicate_set bool
	}
}

func invalidProviderUsageDepsMutator(ctx BottomUpMutatorContext) {
	if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
		ctx.AddDependency(ctx.Module(), nil, i.properties.Deps...)
	}
}

func invalidProviderUsageBeforeMutator(ctx BottomUpMutatorContext) {
	if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
		if i.properties.Early_mutator_set_of_mutator_provider {
			// A mutator attempting to set the value of a provider associated with a later mutator.
			SetProvider(ctx, invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
		}
		if i.properties.Early_mutator_get_of_mutator_provider {
			// A mutator attempting to get the value of a provider associated with a later mutator.
			_, _ = ModuleProvider(ctx, invalidProviderUsageMutatorInfoProvider)
		}
	}
}

func invalidProviderUsageMutatorUnderTest(ctx BottomUpMutatorContext) {
	if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
		if i.properties.Early_mutator_set_of_build_actions_provider {
			// A mutator attempting to set the value of a non-mutator provider.
			SetProvider(ctx, invalidProviderUsageGenerateBuildActionsInfoProvider, invalidProviderUsageGenerateBuildActionsInfo(""))
		}
		if i.properties.Early_mutator_get_of_build_actions_provider {
			// A mutator attempting to get the value of a non-mutator provider.
			_, _ = ModuleProvider(ctx, invalidProviderUsageGenerateBuildActionsInfoProvider)
		}
	}
}

func invalidProviderUsageAfterMutator(ctx BottomUpMutatorContext) {
	if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
		if i.properties.Late_mutator_set_of_mutator_provider {
			// A mutator trying to set the value of a provider associated with an earlier mutator.
			SetProvider(ctx, invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
		}
		if i.properties.Late_mutator_set_of_mutator_provider {
			// A mutator trying to set the value of a provider associated with an earlier mutator.
			SetProvider(ctx, invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
		}
	}
}

func (i *invalidProviderUsageTestModule) GenerateBuildActions(ctx ModuleContext) {
	if i.properties.Late_build_actions_set_of_mutator_provider {
		// A GenerateBuildActions trying to set the value of a provider associated with a mutator.
		SetProvider(ctx, invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
	}
	if i.properties.Duplicate_set {
		SetProvider(ctx, invalidProviderUsageGenerateBuildActionsInfoProvider, invalidProviderUsageGenerateBuildActionsInfo(""))
		SetProvider(ctx, invalidProviderUsageGenerateBuildActionsInfoProvider, invalidProviderUsageGenerateBuildActionsInfo(""))
	}
}

func TestInvalidProvidersUsage(t *testing.T) {
	run := func(t *testing.T, module string, prop string, panicMsg string) {
		t.Helper()
		ctx := NewContext()
		ctx.RegisterModuleType("invalid_provider_usage_test_module", func() (Module, []interface{}) {
			m := &invalidProviderUsageTestModule{}
			return m, []interface{}{&m.properties, &m.SimpleName.Properties}
		})
		ctx.RegisterBottomUpMutator("deps", invalidProviderUsageDepsMutator)
		ctx.RegisterBottomUpMutator("before", invalidProviderUsageBeforeMutator)
		ctx.RegisterBottomUpMutator("mutator_under_test", invalidProviderUsageMutatorUnderTest)
		ctx.RegisterBottomUpMutator("after", invalidProviderUsageAfterMutator)

		// Don't invalidate the parent pointer and before GenerateBuildActions.
		ctx.SkipCloneModulesAfterMutators = true

		var parentBP, moduleUnderTestBP, childBP string

		prop += ": true,"

		switch module {
		case "parent":
			parentBP = prop
		case "module_under_test":
			moduleUnderTestBP = prop
		case "child":
			childBP = prop
		}

		bp := fmt.Sprintf(`
			invalid_provider_usage_test_module {
				name: "parent",
				deps: ["module_under_test"],
				%s
			}
	
			invalid_provider_usage_test_module {
				name: "module_under_test",
				deps: ["child"],
				%s
			}
	
			invalid_provider_usage_test_module {
				name: "child",
				%s
			}

		`,
			parentBP,
			moduleUnderTestBP,
			childBP)

		ctx.MockFileSystem(map[string][]byte{
			"Android.bp": []byte(bp),
		})

		_, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)

		if len(errs) == 0 {
			_, errs = ctx.ResolveDependencies(nil)
		}

		if len(errs) == 0 {
			_, errs = ctx.PrepareBuildActions(nil)
		}

		if len(errs) == 0 {
			t.Fatal("expected an error")
		}

		if len(errs) > 1 {
			t.Errorf("expected a single error, got %d:", len(errs))
			for i, err := range errs {
				t.Errorf("%d:  %s", i, err)
			}
			t.FailNow()
		}

		if panicErr, ok := errs[0].(panicError); ok {
			if panicErr.panic != panicMsg {
				t.Fatalf("expected panic %q, got %q", panicMsg, panicErr.panic)
			}
		} else {
			t.Fatalf("expected a panicError, got %T: %s", errs[0], errs[0].Error())
		}

	}

	tests := []struct {
		prop   string
		module string

		panicMsg string
		skip     string
	}{
		{
			prop:     "early_mutator_set_of_mutator_provider",
			module:   "module_under_test",
			panicMsg: "Can't set value of provider blueprint.invalidProviderUsageMutatorInfo before mutator mutator_under_test started",
		},
		{
			prop:     "late_mutator_set_of_mutator_provider",
			module:   "module_under_test",
			panicMsg: "Can't set value of provider blueprint.invalidProviderUsageMutatorInfo after mutator mutator_under_test finished",
		},
		{
			prop:     "late_build_actions_set_of_mutator_provider",
			module:   "module_under_test",
			panicMsg: "Can't set value of provider blueprint.invalidProviderUsageMutatorInfo after mutator mutator_under_test finished",
		},
		{
			prop:     "early_mutator_set_of_build_actions_provider",
			module:   "module_under_test",
			panicMsg: "Can't set value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo before GenerateBuildActions started",
		},

		{
			prop:     "early_mutator_get_of_mutator_provider",
			module:   "module_under_test",
			panicMsg: "Can't get value of provider blueprint.invalidProviderUsageMutatorInfo before mutator mutator_under_test finished",
		},
		{
			prop:     "early_mutator_get_of_build_actions_provider",
			module:   "module_under_test",
			panicMsg: "Can't get value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo before GenerateBuildActions finished",
		},
		{
			prop:     "duplicate_set",
			module:   "module_under_test",
			panicMsg: "Value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo is already set",
		},
	}

	for _, tt := range tests {
		t.Run(tt.prop, func(t *testing.T) {
			run(t, tt.module, tt.prop, tt.panicMsg)
		})
	}
}

func TestInvalidSingletonProvidersUsage(t *testing.T) {
	type singletonType struct {
		name     string
		factory  SingletonFactory
		parallel bool
	}

	run := func(t *testing.T, singletons []singletonType, panicMsg string) {
		t.Helper()
		ctx := NewContext()
		for _, s := range singletons {
			ctx.RegisterSingletonType(s.name, s.factory, s.parallel)
		}

		ctx.MockFileSystem(map[string][]byte{
			"Android.bp": {},
		})

		_, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)

		if len(errs) == 0 {
			_, errs = ctx.ResolveDependencies(nil)
		}

		if len(errs) == 0 {
			_, errs = ctx.PrepareBuildActions(nil)
		}

		if len(panicMsg) == 0 {
			if len(errs) != 0 {
				t.Fatal("got an error: ", errs)
			}
			return
		}

		if len(errs) == 0 {
			t.Fatal("expected an error")
		}

		if len(errs) > 1 {
			t.Errorf("expected a single error, got %d:", len(errs))
			for i, err := range errs {
				t.Errorf("%d:  %s", i, err)
			}
			t.FailNow()
		}

		if panicErr, ok := errs[0].(panicError); ok {
			if panicErr.panic != panicMsg {
				t.Fatalf("expected panic %q, got %q", panicMsg, panicErr.panic)
			}
		} else {
			t.Fatalf("expected a panicError, got %T: %s", errs[0], errs[0].Error())
		}

	}

	tests := []struct {
		testName   string
		singletons []singletonType
		panicMsg   string
	}{
		{
			testName: "get_non_singleton_provider",
			singletons: []singletonType{
				{
					name: "parallel_singleton",
					factory: func() Singleton {
						return &providerTestParallelSingleton{}
					},
					parallel: true,
				},
				{
					name: "sequential_singleton",
					factory: func() Singleton {
						return &providerTestSequentialSingleton{
							getModuleProvider: true,
						}
					},
					parallel: false,
				},
			},
			panicMsg: "Can't get value of non-singleton provider *blueprint.providerTestMutatorInfo usign singleton specific get method",
		},
		{
			testName: "set_non_singleton_provider",
			singletons: []singletonType{
				{
					name: "sequential_singleton",
					factory: func() Singleton {
						return &providerTestSequentialSingleton{
							setModuleProvider: true,
						}
					},
					parallel: false,
				},
			},
			panicMsg: "Can't set value of non-singleton provider *blueprint.providerTestMutatorInfo inside singleton sequential_singleton",
		},
		{
			testName: "set_get_singleton_provider",
			singletons: []singletonType{
				{
					name: "parallel_singleton",
					factory: func() Singleton {
						return &providerTestParallelSingleton{}
					},
					parallel: true,
				},
				{
					name: "sequential_singleton",
					factory: func() Singleton {
						return &providerTestSequentialSingleton{}
					},
					parallel: false,
				},
			},
			panicMsg: "",
		},
		{
			testName: "visit_all_singletons_in_parallel_singleton",
			singletons: []singletonType{
				{
					name: "parallel_singleton",
					factory: func() Singleton {
						return &providerTestParallelSingleton{visitAllSingletons: true}
					},
					parallel: true,
				},
			},
			panicMsg: "VisitAllSingletons not allowed in parallel singletons parallel_singleton",
		},
	}

	for _, tt := range tests {
		t.Run(tt.testName, func(t *testing.T) {
			run(t, tt.singletons, tt.panicMsg)
		})
	}
}
