// Copyright 2019 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 android

import (
	"fmt"
	"io"
	"reflect"
	"testing"
)

type customModule struct {
	ModuleBase
	data      AndroidMkData
	distFiles TaggedDistFiles
}

func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) {
	m.distFiles = m.GenerateTaggedDistFiles(ctx)
}

func (m *customModule) AndroidMk() AndroidMkData {
	return AndroidMkData{
		Custom: func(w io.Writer, name, prefix, moduleDir string, data AndroidMkData) {
			m.data = data
		},
	}
}

func (m *customModule) OutputFiles(tag string) (Paths, error) {
	switch tag {
	case "":
		return PathsForTesting("one.out"), nil
	case ".multiple":
		return PathsForTesting("two.out", "three/four.out"), nil
	case ".another-tag":
		return PathsForTesting("another.out"), nil
	default:
		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
	}
}

func (m *customModule) AndroidMkEntries() []AndroidMkEntries {
	return []AndroidMkEntries{
		{
			Class:     "CUSTOM_MODULE",
			DistFiles: m.distFiles,
		},
	}
}

func customModuleFactory() Module {
	module := &customModule{}
	InitAndroidModule(module)
	return module
}

func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
	bp := `
	custom {
		name: "foo",
		required: ["bar"],
		host_required: ["baz"],
		target_required: ["qux"],
	}
	`

	config := TestConfig(buildDir, nil, bp, nil)
	config.inMake = true // Enable androidmk Singleton

	ctx := NewTestContext()
	ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
	ctx.RegisterModuleType("custom", customModuleFactory)
	ctx.Register(config)

	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
	FailIfErrored(t, errs)
	_, errs = ctx.PrepareBuildActions(config)
	FailIfErrored(t, errs)

	m := ctx.ModuleForTests("foo", "").Module().(*customModule)

	assertEqual := func(expected interface{}, actual interface{}) {
		if !reflect.DeepEqual(expected, actual) {
			t.Errorf("%q expected, but got %q", expected, actual)
		}
	}
	assertEqual([]string{"bar"}, m.data.Required)
	assertEqual([]string{"baz"}, m.data.Host_required)
	assertEqual([]string{"qux"}, m.data.Target_required)
}

func TestGetDistForGoals(t *testing.T) {
	testCases := []struct {
		bp                     string
		expectedAndroidMkLines []string
	}{
		{
			bp: `
			custom {
				name: "foo",
				dist: {
					targets: ["my_goal"]
				}
			}
			`,
			expectedAndroidMkLines: []string{
				".PHONY: my_goal\n",
				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
			},
		},
		{
			bp: `
			custom {
				name: "foo",
				dist: {
					targets: ["my_goal"],
					tag: ".another-tag",
				}
			}
			`,
			expectedAndroidMkLines: []string{
				".PHONY: my_goal\n",
				"$(call dist-for-goals,my_goal,another.out:another.out)\n",
			},
		},
		{
			bp: `
			custom {
				name: "foo",
				dists: [
					{
						targets: ["my_goal"],
						tag: ".another-tag",
					},
				],
			}
			`,
			expectedAndroidMkLines: []string{
				".PHONY: my_goal\n",
				"$(call dist-for-goals,my_goal,another.out:another.out)\n",
			},
		},
		{
			bp: `
			custom {
				name: "foo",
				dists: [
					{
						targets: ["my_goal"],
					},
					{
						targets: ["my_second_goal", "my_third_goal"],
					},
				],
			}
			`,
			expectedAndroidMkLines: []string{
				".PHONY: my_goal\n",
				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
				".PHONY: my_second_goal my_third_goal\n",
				"$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
			},
		},
		{
			bp: `
			custom {
				name: "foo",
				dist: {
					targets: ["my_goal"],
				},
				dists: [
					{
						targets: ["my_second_goal", "my_third_goal"],
					},
				],
			}
			`,
			expectedAndroidMkLines: []string{
				".PHONY: my_second_goal my_third_goal\n",
				"$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
				".PHONY: my_goal\n",
				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
			},
		},
		{
			bp: `
			custom {
				name: "foo",
				dist: {
					targets: ["my_goal", "my_other_goal"],
					tag: ".multiple",
				},
				dists: [
					{
						targets: ["my_second_goal"],
						tag: ".multiple",
					},
					{
						targets: ["my_third_goal"],
						dir: "test/dir",
					},
					{
						targets: ["my_fourth_goal"],
						suffix: ".suffix",
					},
					{
						targets: ["my_fifth_goal"],
						dest: "new-name",
					},
					{
						targets: ["my_sixth_goal"],
						dest: "new-name",
						dir: "some/dir",
						suffix: ".suffix",
					},
				],
			}
			`,
			expectedAndroidMkLines: []string{
				".PHONY: my_second_goal\n",
				"$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
				"$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
				".PHONY: my_third_goal\n",
				"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
				".PHONY: my_fourth_goal\n",
				"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
				".PHONY: my_fifth_goal\n",
				"$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
				".PHONY: my_sixth_goal\n",
				"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
				".PHONY: my_goal my_other_goal\n",
				"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
				"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
			},
		},
	}

	for _, testCase := range testCases {
		config := TestConfig(buildDir, nil, testCase.bp, nil)
		config.inMake = true // Enable androidmk Singleton

		ctx := NewTestContext()
		ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
		ctx.RegisterModuleType("custom", customModuleFactory)
		ctx.Register(config)

		_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
		FailIfErrored(t, errs)
		_, errs = ctx.PrepareBuildActions(config)
		FailIfErrored(t, errs)

		module := ctx.ModuleForTests("foo", "").Module().(*customModule)
		entries := AndroidMkEntriesForTest(t, config, "", module)
		if len(entries) != 1 {
			t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
		}
		androidMkLines := entries[0].GetDistForGoals(module)

		if len(androidMkLines) != len(testCase.expectedAndroidMkLines) {
			t.Errorf(
				"Expected %d AndroidMk lines, got %d:\n%v",
				len(testCase.expectedAndroidMkLines),
				len(androidMkLines),
				androidMkLines,
			)
		}
		for idx, line := range androidMkLines {
			expectedLine := testCase.expectedAndroidMkLines[idx]
			if line != expectedLine {
				t.Errorf(
					"Expected AndroidMk line to be '%s', got '%s'",
					line,
					expectedLine,
				)
			}
		}
	}
}
