// Copyright 2024 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build none
// +build none

// Tool for 1.28+ -> 1.29+. Pulls adjusted libsqlite3 code to this repo.

package main

import (
	"bytes"
	"fmt"
	"go/token"
	"os"
	"path/filepath"
	"strings"

	"modernc.org/gc/v3"
)

func fail(rc int, msg string, args ...any) {
	fmt.Fprintln(os.Stderr, strings.TrimSpace(fmt.Sprintf(msg, args...)))
	os.Exit(rc)
}

func main() {
	for _, v := range []struct{ goos, goarch string }{
		{"darwin", "amd64"},
		{"darwin", "arm64"},
		{"freebsd", "amd64"},
		{"freebsd", "arm64"},
		{"linux", "386"},
		{"linux", "amd64"},
		{"linux", "arm"},
		{"linux", "arm64"},
		{"linux", "loong64"},
		{"linux", "ppc64le"},
		{"linux", "riscv64"},
		{"linux", "s390x"},
		{"windows", "386"},
		{"windows", "amd64"},
	} {
		base := fmt.Sprintf("ccgo_%s_%s.go", v.goos, v.goarch)
		if v.goos == "windows" && v.goarch == "amd64" {
			base = "ccgo_windows.go"
		}
		ifn := filepath.Join("..", "libsqlite3", base)
		fmt.Printf("%s/%s\t%s\n", v.goos, v.goarch, ifn)
		in, err := os.ReadFile(ifn)
		if err != nil {
			fail(1, "%s\n", err)
		}

		ast, err := gc.ParseFile(ifn, in)
		if err != nil {
			fail(1, "%s\n", err)
		}

		b := bytes.NewBuffer(nil)
		s := ast.SourceFile.PackageClause.Source(true)
		s = strings.Replace(s, "package libsqlite3", "package sqlite3", 1)
		fmt.Fprintln(b, s)
		fmt.Fprint(b, ast.SourceFile.ImportDeclList.Source(true))
		taken := map[string]struct{}{}
		for n := ast.SourceFile.TopLevelDeclList; n != nil; n = n.List {
			switch x := n.TopLevelDecl.(type) {
			case *gc.TypeDeclNode:
				adn := x.TypeSpecList.TypeSpec.(*gc.AliasDeclNode)
				nm := adn.IDENT.Src()
				taken[nm] = struct{}{}
			}
		}
	loop:
		for n := ast.SourceFile.TopLevelDeclList; n != nil; n = n.List {
			switch x := n.TopLevelDecl.(type) {
			case *gc.ConstDeclNode:
				switch y := x.ConstSpec.(type) {
				case *gc.ConstSpecNode:
					if y.IDENT.Src() != "SQLITE_TRANSIENT" {
						fmt.Fprintln(b, x.Source(true))
					}
				default:
					panic(fmt.Sprintf("%v: %T %q", x.Position(), y, x.Source(false)))
				}

			case *gc.FunctionDeclNode:
				fmt.Fprintln(b, x.Source(true))
			case *gc.TypeDeclNode:
				fmt.Fprintln(b, x.Source(true))
				adn := x.TypeSpecList.TypeSpec.(*gc.AliasDeclNode)
				nm := adn.IDENT.Src()
				nm2 := nm[1:]
				if _, ok := taken[nm2]; ok {
					break
				}

				if token.IsExported(nm) {
					fmt.Fprintf(b, "\ntype %s = %s\n", nm2, nm)
				}
			case *gc.VarDeclNode:
				fmt.Fprintln(b, x.Source(true))
			default:
				fmt.Printf("%v: TODO %T\n", n.Position(), x)
				break loop
			}
		}

		b.WriteString(`
type Sqlite3_int64 = sqlite3_int64
type Sqlite3_mutex_methods = sqlite3_mutex_methods
type Sqlite3_value = sqlite3_value

type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

`)
		base = strings.Replace(base, "ccgo_", "sqlite_", 1)
		if err := os.WriteFile(filepath.Join("lib", base), b.Bytes(), 0660); err != nil {
			fail(1, "%s\n", err)
		}
	}
}
