package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
	"sort"
	"strconv"
	"strings"
)

func main() {
	// Check arguments
	if len(os.Args) != 3 {
		log.Fatal("Usage: magicgen.go path/to/magic.h path/to/magic.go")
	}

	magicHPath := os.Args[1]
	magicGoPath := os.Args[2]
	// Open magic.h
	magicHFile, err := os.Open(magicHPath)
	if err != nil {
		log.Fatal("Failed to open magic.h")
	}
	defer magicHFile.Close()
	// Open magic.json if it exists, else create it
	magicGoFile, err := os.Create(magicGoPath)
	if err != nil {
		log.Fatal("Failed to open magic.go")
	}
	defer magicGoFile.Close()

	magics := readMagics(magicHFile)
	writeMagics(magicGoFile, magics)
}

func readMagics(in io.Reader) map[string]uint64 {
	magics := make(map[string]uint64)

	r := bufio.NewScanner(in)
	// Read line by line
	for r.Scan() {
		tokens := strings.Fields(r.Text())
		if len(tokens) >= 3 {
			if tokens[0] == "#define" {
				magicName := tokens[1]
				magicHex := tokens[2]
				// Check if magic is defined as another magic
				if _, ok := magics[magicHex]; ok {
					continue
				}
				/* Base 0 has to be used to parse magicHex instead
				of base 16 as the string contains 0x at the
				beginning, which strconv.ParseUint cannot parse. */
				magicHexInt, err := strconv.ParseUint(magicHex, 0, 64)
				if err != nil {
					log.Printf("Failed to parse magic %s\n", magicName)
					continue
				}
				magics[magicName] = magicHexInt
			}
		}
	}

	return magics
}

func writeMagics(out io.Writer, magics map[string]uint64) {
	// Sort the magic names alphabetically and use
	// this in the generated file for consistency
	magicNames := make([]string, 0, len(magics))
	for name := range magics {
		magicNames = append(magicNames, name)
	}
	sort.Strings(magicNames)

	w := bufio.NewWriter(out)
	fmt.Fprintf(w,
		`// Code generated by magicgen. DO NOT EDIT.

package magic

type MagicMap struct {
	ByName  map[string]uint64
	ByValue map[uint64]string
}

func LoadMagic() MagicMap {
	magicMap := MagicMap{
		ByName:  make(map[string]uint64),
		ByValue: make(map[uint64]string),
	}`)

	for _, name := range magicNames {
		v := magics[name]
		// Ignore RAW_IMAGE_MAGIC and V1 magic
		if v == 0 || v == 1 {
			continue
		}
		if strings.Contains(name, "_MAGIC") == true {
			name = strings.Replace(name, "_MAGIC", "", -1)
		}
		fmt.Fprintf(w, "\n\tmagicMap.ByName[\"%s\"] = %d",
			name, v)
		fmt.Fprintf(w, "\n\tmagicMap.ByValue[%d] = \"%s\"",
			v, name)
	}
	fmt.Fprintf(w, "\n\treturn magicMap\n}\n")
	w.Flush()
}
