// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package gen_tasks_logic

import (
	"log"
	"strings"

	"go.skia.org/infra/task_scheduler/go/specs"
)

// jobBuilder provides helpers for creating a job.
type jobBuilder struct {
	*builder
	parts
	Name string
	Spec *specs.JobSpec
}

// newJobBuilder returns a jobBuilder for the given job name.
func newJobBuilder(b *builder, name string) *jobBuilder {
	p, err := b.jobNameSchema.ParseJobName(name)
	if err != nil {
		log.Fatal(err)
	}
	return &jobBuilder{
		builder: b,
		parts:   p,
		Name:    name,
		Spec:    &specs.JobSpec{},
	}
}

// priority sets the priority of the job.
func (b *jobBuilder) priority(p float64) {
	b.Spec.Priority = p
}

// trigger dictates when the job should be triggered.
func (b *jobBuilder) trigger(trigger string) {
	b.Spec.Trigger = trigger
}

// Create a taskBuilder and run the given function for it.
func (b *jobBuilder) addTask(name string, fn func(*taskBuilder)) {
	tb := newTaskBuilder(b, name)
	fn(tb)
	b.MustAddTask(tb.Name, tb.Spec)
	// Add the task to the Job's dependency set, removing any which are
	// accounted for by the new task's dependencies.
	b.Spec.TaskSpecs = append(b.Spec.TaskSpecs, tb.Name)
	newSpecs := make([]string, 0, len(b.Spec.TaskSpecs))
	for _, t := range b.Spec.TaskSpecs {
		if !In(t, tb.Spec.Dependencies) {
			newSpecs = append(newSpecs, t)
		}
	}
	b.Spec.TaskSpecs = newSpecs
}

// uploadCIPDAssetToCAS generates a task to isolate the given CIPD asset. Returns
// the name of the task.
func (b *jobBuilder) uploadCIPDAssetToCAS(asset string) string {
	cfg, ok := ISOLATE_ASSET_MAPPING[asset]
	if !ok {
		log.Fatalf("No isolate task for asset %q", asset)
	}
	b.addTask(cfg.uploadTaskName, func(b *taskBuilder) {
		b.cipd(b.MustGetCipdPackageFromAsset(asset))
		b.cmd("/bin/cp", "-rL", cfg.path, "${ISOLATED_OUTDIR}")
		b.linuxGceDimensions(MACHINE_TYPE_SMALL)
		b.idempotent()
		b.cas(CAS_EMPTY)
	})
	return cfg.uploadTaskName
}

// genTasksForJob generates the tasks needed by this job.
func (b *jobBuilder) genTasksForJob() {
	// Bundle Recipes.
	if b.Name == BUNDLE_RECIPES_NAME {
		b.bundleRecipes()
		return
	}
	if strings.HasPrefix(b.Name, BUILD_TASK_DRIVERS_PREFIX) {
		parts := strings.Split(b.Name, "_")
		b.buildTaskDrivers(parts[1], parts[2])
		return
	}

	// Isolate CIPD assets.
	if b.matchExtraConfig("Isolate") {
		for asset, cfg := range ISOLATE_ASSET_MAPPING {
			if cfg.uploadTaskName == b.Name {
				b.uploadCIPDAssetToCAS(asset)
				return
			}
		}
	}

	// RecreateSKPs.
	if b.extraConfig("RecreateSKPs") {
		b.recreateSKPs()
		return
	}

	// Update Go Dependencies.
	if b.extraConfig("UpdateGoDeps") {
		b.updateGoDeps()
		return
	}

	// Create docker image.
	if b.extraConfig("CreateDockerImage") {
		b.createDockerImage(b.extraConfig("WASM"))
		return
	}

	// Push apps from docker image.
	if b.extraConfig("PushAppsFromSkiaDockerImage") {
		b.createPushAppsFromSkiaDockerImage()
		return
	} else if b.extraConfig("PushAppsFromWASMDockerImage") {
		b.createPushAppsFromWASMDockerImage()
		return
	}

	// Infra tests.
	if b.extraConfig("InfraTests") {
		b.infra()
		return
	}

	// Housekeepers.
	if b.Name == "Housekeeper-PerCommit" {
		b.housekeeper()
		return
	}
	if b.Name == "Housekeeper-PerCommit-CheckGeneratedFiles" {
		b.checkGeneratedFiles()
		return
	}
	if b.Name == "Housekeeper-PerCommit-RunGnToBp" {
		b.checkGnToBp()
		return
	}
	if b.Name == "Housekeeper-OnDemand-Presubmit" {
		b.priority(1)
		b.presubmit()
		return
	}

	// Compile bots.
	if b.role("Build") {
		b.compile()
		return
	}

	// BuildStats bots. This computes things like binary size.
	if b.role("BuildStats") {
		b.buildstats()
		return
	}

	// Valgrind runs at a low priority so that it doesn't occupy all the bots.
	if b.extraConfig("Valgrind") {
		// Priority of 0.085 should result in Valgrind tasks with a blamelist of ~10 commits having the
		// same score as other tasks with a blamelist of 1 commit, when we have insufficient bot
		// capacity to run more frequently.
		b.priority(0.085)
	}

	// Test bots.
	if b.role("Test") {
		if b.extraConfig("WasmGMTests") {
			b.runWasmGMTests()
			return
		}
		b.dm()
		return
	}
	if b.role("FM") {
		b.fm()
		return
	}

	// Canary bots.
	if b.role("Canary") {
		if b.project("G3") {
			b.g3FrameworkCanary()
			return
		} else if b.project("Android") {
			b.canary("android-master-autoroll")
			return
		} else if b.project("Chromium") {
			b.canary("skia-autoroll")
			return
		} else if b.project("Flutter") {
			b.canary("skia-flutter-autoroll")
			return
		}
	}

	if b.extraConfig("Puppeteer") {
		// TODO(kjlubick) make this a new role
		b.puppeteer()
		return
	}

	// Perf bots.
	if b.role("Perf") {
		b.perf()
		return
	}

	log.Fatalf("Don't know how to handle job %q", b.Name)
}

func (b *jobBuilder) finish() {
	// Add the Job spec.
	if b.frequency("Nightly") {
		b.trigger(specs.TRIGGER_NIGHTLY)
	} else if b.frequency("Weekly") {
		b.trigger(specs.TRIGGER_WEEKLY)
	} else if b.extraConfig("Flutter", "CommandBuffer", "CreateDockerImage") {
		b.trigger(specs.TRIGGER_MAIN_ONLY)
	} else if b.frequency("OnDemand") || b.role("Canary") {
		b.trigger(specs.TRIGGER_ON_DEMAND)
	} else {
		b.trigger(specs.TRIGGER_ANY_BRANCH)
	}
	b.MustAddJob(b.Name, b.Spec)
}
