/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
package main

// Example use:
//   git clone https://skia.googlesource.com/skia.git
//   cd skia
//   SKIA=$PWD
//   cd experimental/fiddle
//   go get github.com/skia-dev/glog
//   go get go.skia.org/infra/go/util
//   go build fiddler.go
//   # compile prerequisites
//   ./fiddler "$SKIA"
//   # compile and run a fiddle
//   ./fiddler "$SKIA" draw.cpp | ./parse-fiddle-output
//   # compile and run a different fiddle
//   ./fiddler "$SKIA" ANOTHER_FIDDLE.cpp | ./parse-fiddle-output

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"syscall"

	"github.com/skia-dev/glog"
	"go.skia.org/infra/go/util"
)

func setResourceLimits() error {
	const maximumTimeInSeconds = 5
	limit := syscall.Rlimit{maximumTimeInSeconds, maximumTimeInSeconds}
	if err := syscall.Setrlimit(syscall.RLIMIT_CPU, &limit); err != nil {
		return err
	}
	const maximumMemoryInBytes = 1 << 28
	limit = syscall.Rlimit{maximumMemoryInBytes, maximumMemoryInBytes}
	return syscall.Setrlimit(syscall.RLIMIT_AS, &limit)
}

// execCommand runs command and returns an error if it fails.  If there is no
// error, all output is discarded.
func execCommand(input io.Reader, dir string, name string, arg ...string) error {
	var buffer bytes.Buffer
	cmd := exec.Command(name, arg...)
	cmd.Dir = dir
	cmd.Stdout = &buffer
	cmd.Stderr = &buffer
	cmd.Stdin = input
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("execution failed:\n\n%s\n[%v]", buffer.String(), err)
	}
	return nil
}

func compileArgs(skiaSrc string) string {
	return "@" + path.Join(skiaSrc, "cmake", "skia_compile_arguments.txt")
}

func linkArgs(skiaSrc string) string {
	return "@" + path.Join(skiaSrc, "cmake", "skia_link_arguments.txt")
}

// fiddler compiles the input, links against skia, and runs the executable.
// @param skiaSrc: the base directory of the Skia repository
// @param inputReader: C++ fiddle source
// @param output: stdout of executable sent here
// @param tempDir: where to place the compiled executable
func fiddler(skiaSrc string, inputReader io.Reader, output io.Writer, tempDir string) error {
	binarypath := path.Join(tempDir, "fiddle")
	fiddle_dir := path.Join(skiaSrc, "experimental", "fiddle")
	if err := execCommand(inputReader, fiddle_dir,
		"c++",
		compileArgs(skiaSrc),
		"-I", fiddle_dir,
		"-o", binarypath,
		"-x", "c++", "-", "-x", "none",
		"fiddle_main.o",
		"-lOSMesa",
		linkArgs(skiaSrc),
	); err != nil {
		return err
	}
	var buffer bytes.Buffer
	runCmd := exec.Cmd{Path: binarypath, Stdout: output, Stderr: &buffer}
	if err := runCmd.Run(); err != nil {
		return fmt.Errorf("execution failed:\n\n%s\n[%v]", buffer.String(), err)
	}
	return nil
}

// Compile Skia library and fiddle_main.cpp
// @param skiaSrc: the base directory of the Skia repository.
func fiddlerPrerequisites(skiaSrc string) error {
	cmakeDir := path.Join(skiaSrc, "cmake")
	if err := execCommand(nil, cmakeDir, "cmake", "-G", "Ninja", "."); err != nil {
		return err
	}
	if err := execCommand(nil, cmakeDir, "ninja", "skia"); err != nil {
		return err
	}
	fiddle_dir := path.Join(skiaSrc, "experimental", "fiddle")
	if err := execCommand(nil, fiddle_dir, "c++", compileArgs(skiaSrc),
		"fiddle_main.h"); err != nil {
		return err
	}
	return execCommand(nil, fiddle_dir, "c++", compileArgs(skiaSrc),
		"-c", "-o", "fiddle_main.o", "fiddle_main.cpp")
}

func main() {
	if len(os.Args) < 2 {
		glog.Fatalf("usage: %s SKIA_SRC_PATH [PATH_TO_DRAW.CPP]", os.Args[0])
	}
	skiaSrc := os.Args[1]
	if len(os.Args) < 3 {
		// execCommand(nil, skiaSrc, "git", "fetch")
		// execCommand(nil, skiaSrc, "git", "checkout", "origin/master")
		if err := fiddlerPrerequisites(skiaSrc); err != nil {
			glog.Fatal(err)
		}
	} else {
		if err := setResourceLimits(); err != nil {
			glog.Fatal(err)
		}
		tempDir, err := ioutil.TempDir("", "fiddle_")
		if err != nil {
			glog.Fatal(err)
		}
		defer func() {
			err = os.RemoveAll(tempDir)
			if err != nil {
				glog.Fatalf("os.RemoveAll(tempDir) failed: %v", err)
			}
		}()
		if os.Args[2] == "-" {
			if err := fiddler(skiaSrc, os.Stdin, os.Stdout, tempDir); err != nil {
				glog.Fatal(err)
			}
		} else {
			inputFile, err := os.Open(os.Args[2])
			if err != nil {
				glog.Fatalf("unable to open \"%s\": %v", os.Args[2], err)
			}
			util.Close(inputFile)
			if err = fiddler(skiaSrc, inputFile, os.Stdout, tempDir); err != nil {
				glog.Fatal(err)
			}
		}
	}
}