// Copyright (c) 2012-2017 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 (
	"sort"
	"sync"
)

type DFA struct {
	// atnStartState is the ATN state in which this was created
	atnStartState DecisionState

	decision int

	// states is all the DFA states. Use Map to get the old state back; Set can only
	// indicate whether it is there.
	states map[int]*DFAState
	statesMu sync.RWMutex

	s0 *DFAState
	s0Mu sync.RWMutex

	// precedenceDfa is the backing field for isPrecedenceDfa and setPrecedenceDfa.
	// True if the DFA is for a precedence decision and false otherwise.
	precedenceDfa bool
}

func NewDFA(atnStartState DecisionState, decision int) *DFA {
	return &DFA{
		atnStartState: atnStartState,
		decision:      decision,
		states:        make(map[int]*DFAState),
	}
}

// getPrecedenceStartState gets the start state for the current precedence and
// returns the start state corresponding to the specified precedence if a start
// state exists for the specified precedence and nil otherwise. d must be a
// precedence DFA. See also isPrecedenceDfa.
func (d *DFA) getPrecedenceStartState(precedence int) *DFAState {
	if !d.precedenceDfa {
		panic("only precedence DFAs may contain a precedence start state")
	}

	d.s0Mu.RLock()
	defer d.s0Mu.RUnlock()

	// s0.edges is never nil for a precedence DFA
	if precedence < 0 || precedence >= len(d.s0.edges) {
		return nil
	}

	return d.s0.edges[precedence]
}

// setPrecedenceStartState sets the start state for the current precedence. d
// must be a precedence DFA. See also isPrecedenceDfa.
func (d *DFA) setPrecedenceStartState(precedence int, startState *DFAState) {
	if !d.precedenceDfa {
		panic("only precedence DFAs may contain a precedence start state")
	}

	if precedence < 0 {
		return
	}

	d.s0Mu.Lock()
	defer d.s0Mu.Unlock()

	// Synchronization on s0 here is ok. When the DFA is turned into a
	// precedence DFA, s0 will be initialized once and not updated again. s0.edges
	// is never nil for a precedence DFA.
	if precedence >= len(d.s0.edges) {
		d.s0.edges = append(d.s0.edges, make([]*DFAState, precedence+1-len(d.s0.edges))...)
	}

	d.s0.edges[precedence] = startState
}

// setPrecedenceDfa sets whether d is a precedence DFA. If precedenceDfa differs
// from the current DFA configuration, then d.states is cleared, the initial
// state s0 is set to a new DFAState with an empty outgoing DFAState.edges to
// store the start states for individual precedence values if precedenceDfa is
// true or nil otherwise, and d.precedenceDfa is updated.
func (d *DFA) setPrecedenceDfa(precedenceDfa bool) {
	if d.precedenceDfa != precedenceDfa {
		d.states = make(map[int]*DFAState)

		if precedenceDfa {
			precedenceState := NewDFAState(-1, NewBaseATNConfigSet(false))

			precedenceState.edges = make([]*DFAState, 0)
			precedenceState.isAcceptState = false
			precedenceState.requiresFullContext = false
			d.s0 = precedenceState
		} else {
			d.s0 = nil
		}

		d.precedenceDfa = precedenceDfa
	}
}

func (d *DFA) getS0() *DFAState {
	d.s0Mu.RLock()
	defer d.s0Mu.RUnlock()
	return d.s0
}

func (d *DFA) setS0(s *DFAState) {
	d.s0Mu.Lock()
	defer d.s0Mu.Unlock()
	d.s0 = s
}

func (d *DFA) getState(hash int) (*DFAState, bool) {
	d.statesMu.RLock()
	defer d.statesMu.RUnlock()
	s, ok := d.states[hash]
	return s, ok
}

func (d *DFA) setState(hash int, state *DFAState) {
	d.statesMu.Lock()
	defer d.statesMu.Unlock()
	d.states[hash] = state
}

func (d *DFA) numStates() int {
	d.statesMu.RLock()
	defer d.statesMu.RUnlock()
	return len(d.states)
}

type dfaStateList []*DFAState

func (d dfaStateList) Len() int           { return len(d) }
func (d dfaStateList) Less(i, j int) bool { return d[i].stateNumber < d[j].stateNumber }
func (d dfaStateList) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }

// sortedStates returns the states in d sorted by their state number.
func (d *DFA) sortedStates() []*DFAState {
	vs := make([]*DFAState, 0, len(d.states))

	for _, v := range d.states {
		vs = append(vs, v)
	}

	sort.Sort(dfaStateList(vs))

	return vs
}

func (d *DFA) String(literalNames []string, symbolicNames []string) string {
	if d.s0 == nil {
		return ""
	}

	return NewDFASerializer(d, literalNames, symbolicNames).String()
}

func (d *DFA) ToLexerString() string {
	if d.s0 == nil {
		return ""
	}

	return NewLexerDFASerializer(d).String()
}
