//Copyright 2013 Vastech SA (PTY) LTD
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package golang

import (
	"bytes"
	"fmt"
	"path"
	"text/template"

	"github.com/goccmack/gocc/internal/ast"
	"github.com/goccmack/gocc/internal/io"
	"github.com/goccmack/gocc/internal/parser/lr1/items"
	"github.com/goccmack/gocc/internal/parser/symbols"
	"github.com/goccmack/gocc/internal/token"
)

func GenProductionsTable(pkg, outDir, header string, prods ast.SyntaxProdList, symbols *symbols.Symbols,
	itemsets *items.ItemSets, tokMap *token.TokenMap) {
	fname := path.Join(outDir, "parser", "productionstable.go")
	tmpl, err := template.New("parser productions table").Parse(prodsTabSrc[1:])
	if err != nil {
		panic(err)
	}
	wr := new(bytes.Buffer)
	if err := tmpl.Execute(wr, getProdsTab(header, prods, symbols, itemsets, tokMap)); err != nil {
		panic(err)
	}
	io.WriteFile(fname, wr.Bytes())
}

func getProdsTab(header string, prods ast.SyntaxProdList, symbols *symbols.Symbols,
	itemsets *items.ItemSets, tokMap *token.TokenMap) *prodsTabData {
	data := &prodsTabData{
		Header:  header,
		ProdTab: make([]prodTabEntry, len(prods)),
	}
	for i, prod := range prods {
		data.ProdTab[i].String = fmt.Sprintf("`%s`", prod.String())
		data.ProdTab[i].Id = prod.Id
		data.ProdTab[i].NTType = symbols.NTType(prod.Id)
		isEmpty := prod.Body.Symbols[0].SymbolString() == "empty"
		if isEmpty {
			data.ProdTab[i].NumSymbols = 0
		} else {
			data.ProdTab[i].NumSymbols = len(prod.Body.Symbols)
		}
		switch {
		case len(prod.Body.SDT) > 0:
			data.ProdTab[i].ReduceFunc = fmt.Sprintf("return %s", prod.Body.SDT)
		case isEmpty:
			// Empty production with no semantic action.
			data.ProdTab[i].ReduceFunc = "return nil, nil"
		default:
			data.ProdTab[i].ReduceFunc = "return X[0], nil"
		}
	}

	return data
}

type prodsTabData struct {
	Header  string
	ProdTab []prodTabEntry
}

type prodTabEntry struct {
	String     string
	Id         string
	NTType     int
	NumSymbols int
	ReduceFunc string
}

// TODO(types): change type and variable names to be consistent with other tables
const prodsTabSrc = `
// Code generated by gocc; DO NOT EDIT.

package parser
{{if .Header}}
{{.Header}}
{{end}}
type (
	ProdTab      [numProductions]ProdTabEntry
	ProdTabEntry struct {
		String     string
		Id         string
		NTType     int
		Index      int
		NumSymbols int
		ReduceFunc func([]Attrib, interface{}) (Attrib, error)
	}
	Attrib interface {
	}
)

var productionsTable = ProdTab{
	{{- range $i, $entry := .ProdTab }}
	ProdTabEntry{
		String: {{$entry.String}},
		Id:         "{{$entry.Id}}",
		NTType:     {{$entry.NTType}},
		Index:      {{$i}},
		NumSymbols: {{$entry.NumSymbols}},
		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
			{{$entry.ReduceFunc}}
		},
	},
	{{- end }}
}
`
