// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
// Use of this file is governed by the BSD 3-clause license that
// can be found in the LICENSE.txt file in the project root.

package antlr

import (
	"fmt"
	"strconv"
	"strings"
)

// DFASerializer is a DFA walker that knows how to dump the DFA states to serialized
// strings.
type DFASerializer struct {
	dfa           *DFA
	literalNames  []string
	symbolicNames []string
}

func NewDFASerializer(dfa *DFA, literalNames, symbolicNames []string) *DFASerializer {
	if literalNames == nil {
		literalNames = make([]string, 0)
	}

	if symbolicNames == nil {
		symbolicNames = make([]string, 0)
	}

	return &DFASerializer{
		dfa:           dfa,
		literalNames:  literalNames,
		symbolicNames: symbolicNames,
	}
}

func (d *DFASerializer) String() string {
	if d.dfa.getS0() == nil {
		return ""
	}

	buf := ""
	states := d.dfa.sortedStates()

	for _, s := range states {
		if s.edges != nil {
			n := len(s.edges)

			for j := 0; j < n; j++ {
				t := s.edges[j]

				if t != nil && t.stateNumber != 0x7FFFFFFF {
					buf += d.GetStateString(s)
					buf += "-"
					buf += d.getEdgeLabel(j)
					buf += "->"
					buf += d.GetStateString(t)
					buf += "\n"
				}
			}
		}
	}

	if len(buf) == 0 {
		return ""
	}

	return buf
}

func (d *DFASerializer) getEdgeLabel(i int) string {
	if i == 0 {
		return "EOF"
	} else if d.literalNames != nil && i-1 < len(d.literalNames) {
		return d.literalNames[i-1]
	} else if d.symbolicNames != nil && i-1 < len(d.symbolicNames) {
		return d.symbolicNames[i-1]
	}

	return strconv.Itoa(i - 1)
}

func (d *DFASerializer) GetStateString(s *DFAState) string {
	var a, b string

	if s.isAcceptState {
		a = ":"
	}

	if s.requiresFullContext {
		b = "^"
	}

	baseStateStr := a + "s" + strconv.Itoa(s.stateNumber) + b

	if s.isAcceptState {
		if s.predicates != nil {
			return baseStateStr + "=>" + fmt.Sprint(s.predicates)
		}

		return baseStateStr + "=>" + fmt.Sprint(s.prediction)
	}

	return baseStateStr
}

type LexerDFASerializer struct {
	*DFASerializer
}

func NewLexerDFASerializer(dfa *DFA) *LexerDFASerializer {
	return &LexerDFASerializer{DFASerializer: NewDFASerializer(dfa, nil, nil)}
}

func (l *LexerDFASerializer) getEdgeLabel(i int) string {
	var sb strings.Builder
	sb.Grow(6)
	sb.WriteByte('\'')
	sb.WriteRune(rune(i))
	sb.WriteByte('\'')
	return sb.String()
}

func (l *LexerDFASerializer) String() string {
	if l.dfa.getS0() == nil {
		return ""
	}

	buf := ""
	states := l.dfa.sortedStates()

	for i := 0; i < len(states); i++ {
		s := states[i]

		if s.edges != nil {
			n := len(s.edges)

			for j := 0; j < n; j++ {
				t := s.edges[j]

				if t != nil && t.stateNumber != 0x7FFFFFFF {
					buf += l.GetStateString(s)
					buf += "-"
					buf += l.getEdgeLabel(j)
					buf += "->"
					buf += l.GetStateString(t)
					buf += "\n"
				}
			}
		}
	}

	if len(buf) == 0 {
		return ""
	}

	return buf
}
