package bincludegen

import (
	"bytes"
	"fmt"
	"go/format"
	"io/ioutil"
	"path/filepath"
	"sort"

	"github.com/lu4p/binclude"
)

func generateCode(pkgName string, fs *binclude.FileSystem, buildTag string) *bytes.Buffer {
	b := bytes.NewBuffer(nil)

	header := `// Code generated by https://github.com/lu4p/binclude; DO NOT EDIT.

package ` + pkgName + "\n"

	b.WriteString(header)

	b.WriteString("import (\n")
	b.WriteString("\"github.com/lu4p/binclude\"\n")
	if len(fs.Files) > 0 {
		b.WriteString("\"time\"\n")
	}
	b.WriteString(")\n")

	fsName := "BinFS"
	if buildTag != "default" {
		fsName = "_binfs" + buildTag
	}

	fsCode(fs, b, fsName)

	return b
}

func generateFiles(dir, pkgName string, fileSystems map[string]*binclude.FileSystem) error {
	for buildTag, fs := range fileSystems {
		code := generateFile(pkgName, fs, buildTag)

		path := filepath.Join(dir, "binclude"+buildTag+".go")
		if buildTag == "default" {
			path = filepath.Join(dir, "binclude.go")
		}

		if err := writeCodeToFile(path, code); err != nil {
			return err
		}
	}
	return nil
}

func generateFile(pkgName string, fs *binclude.FileSystem, buildTag string) []byte {
	b := generateCode(pkgName, fs, buildTag)

	if buildTag == "default" {
		return b.Bytes()
	}

	initFunc := `
func init() {
	BinFS.Lock()
	for path, file := range _binfs` + buildTag + `.Files {
		BinFS.Files[path] = file
	}
	BinFS.Unlock()
}`

	b.WriteString(initFunc)
	return b.Bytes()
}

func writeCodeToFile(filename string, code []byte) error {
	fmtCode, err := format.Source([]byte(code))
	if err != nil {
		return err
	}

	return ioutil.WriteFile(filename, fmtCode, 0o666)
}

func fsCode(fs *binclude.FileSystem, b *bytes.Buffer, fsName string) {
	fmt.Fprintf(b, "var %s = &binclude.FileSystem{Files: binclude.Files{\n", fsName)

	var paths []string
	for path := range fs.Files {
		paths = append(paths, path)
	}

	sort.Strings(paths)

	for _, path := range paths {
		file := fs.Files[path]
		fileCode(file, b, path)
	}

	b.WriteString("}}\n")
}

func fileCode(f *binclude.File, b *bytes.Buffer, path string) {
	fmt.Fprintf(b, "%q:{\n", path)

	fmt.Fprintf(b, `Filename: %q, Mode: %O, ModTime: time.Unix(%d,%d), Compression: %d,`,
		f.Filename, f.Mode, f.ModTime.Unix(), f.ModTime.UnixNano(), f.Compression)

	if f.Content != nil {
		fmt.Fprintf(b, "\nContent: []byte(%q),", f.Content)
	}

	b.WriteString("\n},\n")
}
