package builder

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/tinygo-org/tinygo/goenv"
)

var libMinGW = Library{
	name: "mingw-w64",
	makeHeaders: func(target, includeDir string) error {
		// copy _mingw.h
		srcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "mingw-w64")
		outf, err := os.Create(includeDir + "/_mingw.h")
		if err != nil {
			return err
		}
		defer outf.Close()
		inf, err := os.Open(srcDir + "/mingw-w64-headers/crt/_mingw.h.in")
		if err != nil {
			return err
		}
		_, err = io.Copy(outf, inf)
		return err
	},
	sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") },
	cflags: func(target, headerPath string) []string {
		mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64")
		return []string{
			"-nostdlibinc",
			"-isystem", mingwDir + "/mingw-w64-headers/crt",
			"-I", mingwDir + "/mingw-w64-headers/defaults/include",
			"-I" + headerPath,
		}
	},
	librarySources: func(target string) ([]string, error) {
		// These files are needed so that printf and the like are supported.
		sources := []string{
			"mingw-w64-crt/stdio/ucrt_fprintf.c",
			"mingw-w64-crt/stdio/ucrt_fwprintf.c",
			"mingw-w64-crt/stdio/ucrt_printf.c",
			"mingw-w64-crt/stdio/ucrt_snprintf.c",
			"mingw-w64-crt/stdio/ucrt_sprintf.c",
			"mingw-w64-crt/stdio/ucrt_vfprintf.c",
			"mingw-w64-crt/stdio/ucrt_vprintf.c",
			"mingw-w64-crt/stdio/ucrt_vsnprintf.c",
			"mingw-w64-crt/stdio/ucrt_vsprintf.c",
		}
		return sources, nil
	},
}

// makeMinGWExtraLibs returns a slice of jobs to import the correct .dll
// libraries. This is done by converting input .def files to .lib files which
// can then be linked as usual.
//
// TODO: cache the result. At the moment, it costs a few hundred milliseconds to
// compile these files.
func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
	var jobs []*compileJob
	root := goenv.Get("TINYGOROOT")
	// Normally all the api-ms-win-crt-*.def files are all compiled to a single
	// .lib file. But to simplify things, we're going to leave them as separate
	// files.
	for _, name := range []string{
		"kernel32.def.in",
		"api-ms-win-crt-conio-l1-1-0.def",
		"api-ms-win-crt-convert-l1-1-0.def.in",
		"api-ms-win-crt-environment-l1-1-0.def",
		"api-ms-win-crt-filesystem-l1-1-0.def",
		"api-ms-win-crt-heap-l1-1-0.def",
		"api-ms-win-crt-locale-l1-1-0.def",
		"api-ms-win-crt-math-l1-1-0.def.in",
		"api-ms-win-crt-multibyte-l1-1-0.def",
		"api-ms-win-crt-private-l1-1-0.def.in",
		"api-ms-win-crt-process-l1-1-0.def",
		"api-ms-win-crt-runtime-l1-1-0.def.in",
		"api-ms-win-crt-stdio-l1-1-0.def",
		"api-ms-win-crt-string-l1-1-0.def",
		"api-ms-win-crt-time-l1-1-0.def",
		"api-ms-win-crt-utility-l1-1-0.def",
	} {
		outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib")
		inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name)
		job := &compileJob{
			description: "create lib file " + inpath,
			result:      outpath,
			run: func(job *compileJob) error {
				defpath := inpath
				var archDef, emulation string
				switch goarch {
				case "amd64":
					archDef = "-DDEF_X64"
					emulation = "i386pep"
				case "arm64":
					archDef = "-DDEF_ARM64"
					emulation = "arm64pe"
				default:
					return fmt.Errorf("unsupported architecture for mingw-w64: %s", goarch)
				}
				if strings.HasSuffix(inpath, ".in") {
					// .in files need to be preprocessed by a preprocessor (-E)
					// first.
					defpath = outpath + ".def"
					err := runCCompiler("-E", "-x", "c", "-Wp,-w", "-P", archDef, "-DDATA", "-o", defpath, inpath, "-I"+goenv.Get("TINYGOROOT")+"/lib/mingw-w64/mingw-w64-crt/def-include/")
					if err != nil {
						return err
					}
				}
				return link("ld.lld", "-m", emulation, "-o", outpath, defpath)
			},
		}
		jobs = append(jobs, job)
	}
	return jobs
}
