package main

import (
	"encoding/json"
	"fmt"
	"image"
	_ "image/png"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/tc-hib/winres"
	"github.com/tc-hib/winres/version"
)

type winresConfig struct {
	GroupIcon map[string]map[string]json.RawMessage `json:"RT_GROUP_ICON"`
	Manifest  map[string]map[string]json.RawMessage `json:"RT_MANIFEST"`
	Version   map[string]map[string]json.RawMessage `json:"RT_VERSION"`
}

func main() {
	if len(os.Args) != 5 {
		log.Fatalf("usage: %s arch winres.json assets-dir output.syso", os.Args[0])
	}

	arch := winres.Arch(os.Args[1])
	switch arch {
	case winres.ArchAMD64, winres.ArchARM64, winres.ArchI386:
	default:
		log.Fatalf("unsupported arch: %s", arch)
	}

	cfgPath := os.Args[2]
	assetsDir := os.Args[3]
	outPath := os.Args[4]

	data, err := os.ReadFile(cfgPath)
	if err != nil {
		log.Fatalf("error reading config %q: %v", cfgPath, err)
	}

	var cfg winresConfig
	if err := json.Unmarshal(data, &cfg); err != nil {
		log.Fatalf("error decoding config %q: %v", cfgPath, err)
	}

	rs := winres.ResourceSet{}

	if err := addIcons(&rs, cfg.GroupIcon, assetsDir); err != nil {
		log.Fatalf("error loading icon resources: %v", err)
	}
	if err := addManifest(&rs, cfg.Manifest); err != nil {
		log.Fatalf("error loading manifest resources: %v", err)
	}
	if err := addVersion(&rs, cfg.Version); err != nil {
		log.Fatalf("error loading version resources: %v", err)
	}

	out, err := os.Create(outPath)
	if err != nil {
		log.Fatalf("error creating output file %q: %v", outPath, err)
	}
	defer out.Close()

	if err := rs.WriteObject(out, arch); err != nil {
		log.Fatalf("error writing object: %v", err)
	}
}

func addIcons(rs *winres.ResourceSet, icons map[string]map[string]json.RawMessage, assetsDir string) error {
	for resName, langs := range icons {
		resID := parseIdentifier(resName)
		for langCode, raw := range langs {
			langID, err := parseLangID(langCode)
			if err != nil {
				return fmt.Errorf("icon %q language %q: %w", resName, langCode, err)
			}

			var paths []string
			if err := json.Unmarshal(raw, &paths); err != nil {
				var single string
				if err := json.Unmarshal(raw, &single); err != nil {
					return fmt.Errorf("icon %q language %q: unsupported icon payload", resName, langCode)
				}
				paths = []string{single}
			}

			images := make([]winres.Name, 0, len(paths))
			for _, p := range paths {
				images = append(images, winres.Name(filepath.Join(assetsDir, filepath.Clean(p))))
			}

			icon, err := loadIconFromPNGs(images)
			if err != nil {
				return fmt.Errorf("icon %q language %q: %w", resName, langCode, err)
			}
			if err := rs.SetIconTranslation(resID, langID, icon); err != nil {
				return fmt.Errorf("icon %q language %q: %w", resName, langCode, err)
			}
		}
	}
	return nil
}

func addManifest(rs *winres.ResourceSet, manifests map[string]map[string]json.RawMessage) error {
	for resName, langs := range manifests {
		resID := parseIdentifier(resName)
		for langCode, raw := range langs {
			langID, err := parseLangID(langCode)
			if err != nil {
				return fmt.Errorf("manifest %q language %q: %w", resName, langCode, err)
			}

			var manifest winres.AppManifest
			if err := json.Unmarshal(raw, &manifest); err != nil {
				return fmt.Errorf("manifest %q language %q: %w", resName, langCode, err)
			}

			if err := rs.Set(winres.RT_MANIFEST, resID, langID, manifestBytes(manifest)); err != nil {
				return fmt.Errorf("manifest %q language %q: %w", resName, langCode, err)
			}
		}
	}
	return nil
}

func addVersion(rs *winres.ResourceSet, versions map[string]map[string]json.RawMessage) error {
	for resName, langs := range versions {
		resID := parseIdentifier(resName)
		for langCode, raw := range langs {
			langID, err := parseLangID(langCode)
			if err != nil {
				return fmt.Errorf("version %q language %q: %w", resName, langCode, err)
			}

			var info version.Info
			if err := json.Unmarshal(raw, &info); err != nil {
				return fmt.Errorf("version %q language %q: %w", resName, langCode, err)
			}

			if err := rs.Set(winres.RT_VERSION, resID, langID, info.Bytes()); err != nil {
				return fmt.Errorf("version %q language %q: %w", resName, langCode, err)
			}
		}
	}
	return nil
}

func manifestBytes(m winres.AppManifest) []byte {
	rs := winres.ResourceSet{}
	rs.SetManifest(m)
	return rs.Get(winres.RT_MANIFEST, winres.ID(1), winres.LCIDDefault)
}

func loadIconFromPNGs(paths []winres.Name) (*winres.Icon, error) {
	images := make([]image.Image, 0, len(paths))
	for _, p := range paths {
		f, err := os.Open(string(p))
		if err != nil {
			return nil, err
		}
		img, _, err := image.Decode(f)
		f.Close()
		if err != nil {
			return nil, err
		}
		images = append(images, img)
	}
	return winres.NewIconFromImages(images)
}

func parseIdentifier(value string) winres.Identifier {
	if strings.HasPrefix(value, "#") {
		var id uint16
		if _, err := fmt.Sscanf(value, "#%d", &id); err == nil && id != 0 {
			return winres.ID(id)
		}
	}
	return winres.Name(value)
}

func parseLangID(value string) (uint16, error) {
	var lang uint16
	if _, err := fmt.Sscanf(value, "%X", &lang); err != nil {
		return 0, fmt.Errorf("invalid lang id %q", value)
	}
	return lang, nil
}
