// Copyright 2015 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 genrule

import (
	"github.com/google/blueprint"

	"android/soong"
	"android/soong/common"
)

func init() {
	soong.RegisterModuleType("gensrcs", GenSrcsFactory)
	soong.RegisterModuleType("genrule", GenRuleFactory)

	common.RegisterBottomUpMutator("genrule_deps", genruleDepsMutator)
}

var (
	pctx = common.NewPackageContext("android/soong/genrule")
)

func init() {
	pctx.SourcePathVariable("srcDir", "")
	pctx.HostBinToolVariable("hostBin", "")
}

type SourceFileGenerator interface {
	GeneratedSourceFiles() common.Paths
}

type HostToolProvider interface {
	HostToolPath() common.OptionalPath
}

type generatorProperties struct {
	// command to run on one or more input files.  Available variables for substitution:
	// $in: one or more input files
	// $out: a single output file
	// $srcDir: the root directory of the source tree
	// The host bin directory will be in the path
	Cmd string

	// name of the module (if any) that produces the host executable.   Leave empty for
	// prebuilts or scripts that do not need a module to build them.
	Tool string
}

type generator struct {
	common.AndroidModuleBase

	properties generatorProperties

	tasks taskFunc

	deps common.Paths
	rule blueprint.Rule

	outputFiles common.Paths
}

type taskFunc func(ctx common.AndroidModuleContext) []generateTask

type generateTask struct {
	in  common.Paths
	out common.ModuleGenPath
}

func (g *generator) GeneratedSourceFiles() common.Paths {
	return g.outputFiles
}

func genruleDepsMutator(ctx common.AndroidBottomUpMutatorContext) {
	if g, ok := ctx.Module().(*generator); ok {
		if g.properties.Tool != "" {
			ctx.AddFarVariationDependencies([]blueprint.Variation{
				{"host_or_device", common.Host.String()},
				{"host_type", common.CurrentHostType().String()},
			}, g.properties.Tool)
		}
	}
}

func (g *generator) GenerateAndroidBuildActions(ctx common.AndroidModuleContext) {
	g.rule = ctx.Rule(pctx, "generator", blueprint.RuleParams{
		Command: "PATH=$$PATH:$hostBin " + g.properties.Cmd,
	})

	ctx.VisitDirectDeps(func(module blueprint.Module) {
		if t, ok := module.(HostToolProvider); ok {
			p := t.HostToolPath()
			if p.Valid() {
				g.deps = append(g.deps, p.Path())
			} else {
				ctx.ModuleErrorf("host tool %q missing output file", ctx.OtherModuleName(module))
			}
		} else {
			ctx.ModuleErrorf("unknown dependency %q", ctx.OtherModuleName(module))
		}
	})

	for _, task := range g.tasks(ctx) {
		g.generateSourceFile(ctx, task)
	}
}

func (g *generator) generateSourceFile(ctx common.AndroidModuleContext, task generateTask) {
	ctx.ModuleBuild(pctx, common.ModuleBuildParams{
		Rule:      g.rule,
		Output:    task.out,
		Inputs:    task.in,
		Implicits: g.deps,
	})

	g.outputFiles = append(g.outputFiles, task.out)
}

func generatorFactory(tasks taskFunc, props ...interface{}) (blueprint.Module, []interface{}) {
	module := &generator{
		tasks: tasks,
	}

	props = append(props, &module.properties)

	return common.InitAndroidModule(module, props...)
}

func GenSrcsFactory() (blueprint.Module, []interface{}) {
	properties := &genSrcsProperties{}

	tasks := func(ctx common.AndroidModuleContext) []generateTask {
		srcFiles := ctx.ExpandSources(properties.Srcs, nil)
		tasks := make([]generateTask, 0, len(srcFiles))
		for _, in := range srcFiles {
			tasks = append(tasks, generateTask{
				in:  common.Paths{in},
				out: common.GenPathWithExt(ctx, in, properties.Output_extension),
			})
		}
		return tasks
	}

	return generatorFactory(tasks, properties)
}

type genSrcsProperties struct {
	// list of input files
	Srcs []string

	// extension that will be substituted for each output file
	Output_extension string
}

func GenRuleFactory() (blueprint.Module, []interface{}) {
	properties := &genRuleProperties{}

	tasks := func(ctx common.AndroidModuleContext) []generateTask {
		return []generateTask{
			{
				in:  ctx.ExpandSources(properties.Srcs, nil),
				out: properties.Out,
			},
		}
	}

	return generatorFactory(tasks, properties)
}

type genRuleProperties struct {
	// list of input files
	Srcs []string

	// name of the output file that will be generated
	Out common.ModuleGenPath
}