// 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.

// Choose which ninja file (stage) to run next
//
// In the common case, this program takes a list of ninja files, compares their
// mtimes against their $file.timestamp mtimes, and picks the last up to date
// ninja file to output. That stage is expected to rebuild the next file in the
// list and call this program again. If none of the ninja files are considered
// dirty, the last stage is output.
//
// One exception is if the current stage's ninja file was rewritten, it will be
// run again.
//
// Another exception is if the source bootstrap file has been updated more
// recently than the first stage, the source file will be copied to the first
// stage, and output. This would be expected with a new source drop via git.
// The timestamp of the first file is not updated so that it can be regenerated
// with any local changes.

package choosestage

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
)

var (
	outputFile    string
	currentFile   string
	bootstrapFile string
	verbose       bool
)

func init() {
	flag.StringVar(&outputFile, "o", "", "Output file")
	flag.StringVar(&currentFile, "current", "", "Current stage's file")
	flag.StringVar(&bootstrapFile, "bootstrap", "", "Bootstrap file checked into source")
	flag.BoolVar(&verbose, "v", false, "Verbose mode")
}

func compareFiles(a, b string) (bool, error) {
	aData, err := ioutil.ReadFile(a)
	if err != nil {
		return false, err
	}

	bData, err := ioutil.ReadFile(b)
	if err != nil {
		return false, err
	}

	return bytes.Equal(aData, bData), nil
}

// If the source bootstrap reference file is newer, then we may have gotten
// other source updates too. So we need to restart everything with the file
// that was checked in instead of the bootstrap that we last built.
func copyBootstrapIfNecessary(bootstrapFile, filename string) (bool, error) {
	if bootstrapFile == "" {
		return false, nil
	}

	bootstrapStat, err := os.Stat(bootstrapFile)
	if err != nil {
		return false, err
	}

	fileStat, err := os.Stat(filename)
	if err != nil {
		return false, err
	}

	time := fileStat.ModTime()
	if !bootstrapStat.ModTime().After(time) {
		return false, nil
	}

	fmt.Printf("Newer source version of %s. Copying to %s\n", filepath.Base(bootstrapFile), filepath.Base(filename))
	if verbose {
		fmt.Printf("Source: %s\nBuilt:  %s\n", bootstrapStat.ModTime(), time)
	}

	data, err := ioutil.ReadFile(bootstrapFile)
	if err != nil {
		return false, err
	}

	err = ioutil.WriteFile(filename, data, 0666)
	if err != nil {
		return false, err
	}

	// Restore timestamp to force regeneration of the bootstrap.ninja.in
	err = os.Chtimes(filename, time, time)
	return true, err
}

func main() {
	flag.Parse()

	if flag.NArg() == 0 {
		fmt.Fprintf(os.Stderr, "Must specify at least one ninja file\n")
		os.Exit(1)
	}

	if outputFile == "" {
		fmt.Fprintf(os.Stderr, "Must specify an output file\n")
		os.Exit(1)
	}

	gotoFile := flag.Arg(0)
	if copied, err := copyBootstrapIfNecessary(bootstrapFile, flag.Arg(0)); err != nil {
		fmt.Fprintf(os.Stderr, "Failed to copy bootstrap ninja file: %s\n", err)
		os.Exit(1)
	} else if !copied {
		for _, fileName := range flag.Args() {
			timestampName := fileName + ".timestamp"

			// If we're currently running this stage, and the build.ninja.in
			// file differs from the current stage file, then it has been rebuilt.
			// Restart the stage.
			if filepath.Clean(currentFile) == filepath.Clean(fileName) {
				if _, err := os.Stat(outputFile); !os.IsNotExist(err) {
					if ok, err := compareFiles(fileName, outputFile); err != nil {
						fmt.Fprintf(os.Stderr, "Failure when comparing files: %s\n", err)
						os.Exit(1)
					} else if !ok {
						fmt.Printf("Stage %s has changed, restarting\n", filepath.Base(fileName))
						gotoFile = fileName
						break
					}
				}
			}

			fileStat, err := os.Stat(fileName)
			if err != nil {
				// Regenerate this stage on error
				break
			}

			timestampStat, err := os.Stat(timestampName)
			if err != nil {
				// This file may not exist. There's no point for
				// the first stage to have one, as it should be
				// a subset of the second stage dependencies,
				// and both will return to the first stage.
				continue
			}

			if verbose {
				fmt.Printf("For %s:\n  file: %s\n  time: %s\n", fileName, fileStat.ModTime(), timestampStat.ModTime())
			}

			// If the timestamp file has a later modification time, that
			// means that this stage needs to be regenerated. Break, so
			// that we run the last found stage.
			if timestampStat.ModTime().After(fileStat.ModTime()) {
				break
			}

			gotoFile = fileName
		}
	}

	fmt.Printf("Choosing %s for next stage\n", filepath.Base(gotoFile))

	data, err := ioutil.ReadFile(gotoFile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Can't read file: %s", err)
		os.Exit(1)
	}

	err = ioutil.WriteFile(outputFile, data, 0666)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Can't write file: %s", err)
		os.Exit(1)
	}
}