package bundler

import (
	"sort"
	"strconv"
	"sync"
	"sync/atomic"

	"github.com/evanw/esbuild/internal/ast"
	"github.com/evanw/esbuild/internal/lexer"
)

func computeReservedNames(moduleScopes []*ast.Scope, symbols ast.SymbolMap) map[string]bool {
	names := make(map[string]bool)

	// All keywords are reserved names
	for k := range lexer.Keywords() {
		names[k] = true
	}

	// All unbound symbols must be reserved names
	for _, scope := range moduleScopes {
		for _, ref := range scope.Members {
			symbol := symbols.Get(ref)
			if symbol.Kind == ast.SymbolUnbound {
				names[symbol.Name] = true
			}
		}
		for _, ref := range scope.Generated {
			symbol := symbols.Get(ref)
			if symbol.Kind == ast.SymbolUnbound {
				names[symbol.Name] = true
			}
		}
	}

	return names
}

func sortedSymbolsInScope(scope *ast.Scope) uint64Array {
	// Sort for determinism
	sorted := uint64Array(make([]uint64, 0, len(scope.Members)+len(scope.Generated)))
	for _, ref := range scope.Members {
		sorted = append(sorted, (uint64(ref.OuterIndex)<<32)|uint64(ref.InnerIndex))
	}
	for _, ref := range scope.Generated {
		sorted = append(sorted, (uint64(ref.OuterIndex)<<32)|uint64(ref.InnerIndex))
	}
	sort.Sort(sorted)
	return sorted
}

////////////////////////////////////////////////////////////////////////////////
// renameAllSymbols() implementation

type renamer struct {
	parent *renamer

	// This is used as a set of used names in this scope. This also maps the name
	// to the number of times the name has experienced a collision. When a name
	// collides with an already-used name, we need to rename it. This is done by
	// incrementing a number at the end until the name is unused. We save the
	// count here so that subsequent collisions can start counting from where the
	// previous collision ended instead of having to start counting from 1.
	nameCounts map[string]uint32
}

type nameUse uint8

const (
	nameUnused nameUse = iota
	nameUsed
	nameUsedInSameScope
)

func (r *renamer) findNameUse(name string) nameUse {
	original := r
	for {
		if _, ok := r.nameCounts[name]; ok {
			if r == original {
				return nameUsedInSameScope
			}
			return nameUsed
		}
		r = r.parent
		if r == nil {
			return nameUnused
		}
	}
}

func (r *renamer) findUnusedName(name string) string {
	if use := r.findNameUse(name); use != nameUnused {
		// If the name is already in use, generate a new name by appending a number
		tries := uint32(1)
		if use == nameUsedInSameScope {
			// To avoid O(n^2) behavior, the number must start off being the number
			// that we used last time there was a collision with this name. Otherwise
			// if there are many collisions with the same name, each name collision
			// would have to increment the counter past all previous name collisions
			// which is a O(n^2) time algorithm. Only do this if this symbol comes
			// from the same scope as the previous one since sibling scopes can reuse
			// the same name without problems.
			tries = r.nameCounts[name]
		}
		prefix := name

		// Keep incrementing the number until the name is unused
		for {
			tries++
			name = prefix + strconv.Itoa(int(tries))

			// Make sure this new name is unused
			if r.findNameUse(name) == nameUnused {
				// Store the count so we can start here next time instead of starting
				// from 1. This means we avoid O(n^2) behavior.
				if use == nameUsedInSameScope {
					r.nameCounts[prefix] = tries
				}
				break
			}
		}
	}

	// Each name starts off with a count of 1 so that the first collision with
	// "name" is called "name2"
	r.nameCounts[name] = 1
	return name
}

func renameAllSymbols(reservedNames map[string]bool, moduleScopes []*ast.Scope, symbols ast.SymbolMap) {
	reservedNameCounts := make(map[string]uint32)
	for name := range reservedNames {
		// Each name starts off with a count of 1 so that the first collision with
		// "name" is called "name2"
		reservedNameCounts[name] = 1
	}
	r := &renamer{nil, reservedNameCounts}

	// This is essentially a "map[ast.Ref]bool" but we use an array instead of a
	// map so we can mutate it concurrently from multiple threads.
	alreadyRenamed := make([][]bool, len(symbols.Outer))
	for sourceIndex, inner := range symbols.Outer {
		alreadyRenamed[sourceIndex] = make([]bool, len(inner))
	}

	// Rename top-level symbols across all files all at once since after
	// bundling, they will all be in the same scope
	for _, scope := range moduleScopes {
		r.renameSymbolsInScope(scope, symbols, alreadyRenamed)
	}

	// Symbols in child scopes may also have to be renamed to avoid conflicts.
	// Since child scopes in different files are isolated from each other, we
	// can process each file independently in parallel.
	waitGroup := sync.WaitGroup{}
	waitGroup.Add(len(moduleScopes))
	for _, scope := range moduleScopes {
		go func(scope *ast.Scope) {
			for _, child := range scope.Children {
				r.renameAllSymbolsRecursive(child, symbols, alreadyRenamed)
			}
			waitGroup.Done()
		}(scope)
	}
	waitGroup.Wait()
}

func (r *renamer) renameSymbolsInScope(scope *ast.Scope, symbols ast.SymbolMap, alreadyRenamed [][]bool) {
	sorted := sortedSymbolsInScope(scope)

	// Rename all symbols in this scope
	for _, i := range sorted {
		ref := ast.Ref{OuterIndex: uint32(i >> 32), InnerIndex: uint32(i)}
		ref = ast.FollowSymbols(symbols, ref)

		// Don't rename the same symbol more than once
		if alreadyRenamed[ref.OuterIndex][ref.InnerIndex] {
			continue
		}
		alreadyRenamed[ref.OuterIndex][ref.InnerIndex] = true

		symbol := symbols.Get(ref)

		// Don't rename unbound symbols and symbols marked as reserved names
		if symbol.Kind == ast.SymbolUnbound || symbol.MustNotBeRenamed {
			continue
		}

		symbol.Name = r.findUnusedName(symbol.Name)
	}
}

func (parent *renamer) renameAllSymbolsRecursive(scope *ast.Scope, symbols ast.SymbolMap, alreadyRenamed [][]bool) {
	r := &renamer{parent, make(map[string]uint32)}
	r.renameSymbolsInScope(scope, symbols, alreadyRenamed)

	// Symbols in child scopes may also have to be renamed to avoid conflicts
	for _, child := range scope.Children {
		r.renameAllSymbolsRecursive(child, symbols, alreadyRenamed)
	}
}

////////////////////////////////////////////////////////////////////////////////
// minifyAllSymbols() implementation

func minifyAllSymbols(reservedNames map[string]bool, moduleScopes []*ast.Scope, symbols ast.SymbolMap) {
	g := minifyGroup{make([][]minifyInfo, len(symbols.Outer))}
	for sourceIndex, inner := range symbols.Outer {
		g.symbolToMinifyInfo[sourceIndex] = make([]minifyInfo, len(inner))
	}
	next := uint32(0)
	nextPrivate := uint32(0)

	// Allocate a slot for every symbol in each top-level scope. These slots must
	// not overlap between files because the bundler may smoosh everything
	// together into a single scope.
	for _, scope := range moduleScopes {
		next, nextPrivate = g.countSymbolsInScope(scope, symbols, next, nextPrivate)
	}

	// Allocate a slot for every symbol in each nested scope. Since it's
	// impossible for symbols from nested scopes to conflict, symbols from
	// different nested scopes can reuse the same slots (and therefore get the
	// same minified names).
	//
	// One good heuristic is to merge slots from different nested scopes using
	// sequential assignment. Then top-level function statements will always have
	// the same argument names, which is better for gzip compression.
	//
	// This code uses atomics to avoid counting the same symbol twice, so it can
	// be parallelized across multiple threads.
	waitGroup := sync.WaitGroup{}
	waitGroup.Add(len(moduleScopes))
	for _, scope := range moduleScopes {
		go func(scope *ast.Scope) {
			for _, child := range scope.Children {
				// Deliberately don't update "next" and "nextPrivate" here. Sibling
				// scopes can't collide and so can reuse slots.
				g.countSymbolsRecursive(child, symbols, next, nextPrivate, 0)
			}
			waitGroup.Done()
		}(scope)
	}
	waitGroup.Wait()

	// Find the largest slot value
	maxSlot := uint32(0)
	maxPrivateSlot := uint32(0)
	for outer, array := range g.symbolToMinifyInfo {
		for inner, data := range array {
			if data.used != 0 {
				if symbols.Outer[outer][inner].Kind.IsPrivate() {
					if data.slot > maxPrivateSlot {
						maxPrivateSlot = data.slot
					}
				} else {
					if data.slot > maxSlot {
						maxSlot = data.slot
					}
				}
			}
		}
	}

	// Allocate one count for each slot
	slotToCount := make([]uint32, maxSlot+1)
	privateSlotToCount := make([]uint32, maxPrivateSlot+1)
	for outer, array := range g.symbolToMinifyInfo {
		for inner, data := range array {
			if data.used != 0 {
				if symbols.Outer[outer][inner].Kind.IsPrivate() {
					privateSlotToCount[data.slot] += data.count
				} else {
					slotToCount[data.slot] += data.count
				}
			}
		}
	}

	// Sort slot indices descending by the count for that slot
	sorted := slotAndCountArray(make([]slotAndCount, len(slotToCount)))
	privateSorted := slotAndCountArray(make([]slotAndCount, len(privateSlotToCount)))
	for slot, count := range slotToCount {
		sorted[slot] = slotAndCount{uint32(slot), count}
	}
	for slot, count := range privateSlotToCount {
		privateSorted[slot] = slotAndCount{uint32(slot), count}
	}
	sort.Sort(sorted)
	sort.Sort(privateSorted)

	// Assign names sequentially in order so the most frequent symbols get the
	// shortest names
	nextName := 0
	names := make([]string, len(sorted))
	privateNames := make([]string, len(privateSorted))
	for _, data := range sorted {
		name := lexer.NumberToMinifiedName(nextName)
		nextName++

		// Make sure we never generate a reserved name
		for reservedNames[name] {
			name = lexer.NumberToMinifiedName(nextName)
			nextName++
		}

		names[data.slot] = name
	}
	for i, data := range privateSorted {
		// Don't need to worry about collisions with reserved names here
		privateNames[data.slot] = "#" + lexer.NumberToMinifiedName(i)
	}

	// Copy the names to the appropriate symbols
	for outer, array := range g.symbolToMinifyInfo {
		for inner, data := range array {
			if data.used != 0 {
				symbol := &symbols.Outer[outer][inner]
				if symbol.Kind.IsPrivate() {
					symbol.Name = privateNames[data.slot]
				} else {
					symbol.Name = names[data.slot]
				}
			}
		}
	}
}

type minifyGroup struct {
	symbolToMinifyInfo [][]minifyInfo
}

type minifyInfo struct {
	used  uint32
	slot  uint32
	count uint32
}

func (g *minifyGroup) countSymbol(slot uint32, ref ast.Ref, count uint32) bool {
	// Don't double-count symbols that have already been counted
	minifyInfo := &g.symbolToMinifyInfo[ref.OuterIndex][ref.InnerIndex]
	if !atomic.CompareAndSwapUint32(&minifyInfo.used, 0, 1) {
		return false
	}

	// Count this symbol in this slot
	minifyInfo.slot = slot
	minifyInfo.count = count
	return true
}

func (g *minifyGroup) countSymbolsInScope(scope *ast.Scope, symbols ast.SymbolMap, next uint32, nextPrivate uint32) (uint32, uint32) {
	sorted := sortedSymbolsInScope(scope)

	for _, i := range sorted {
		ref := ast.Ref{OuterIndex: uint32(i >> 32), InnerIndex: uint32(i)}
		ref = ast.FollowSymbols(symbols, ref)
		symbol := symbols.Get(ref)

		// Don't rename unbound symbols and symbols marked as reserved names
		if symbol.Kind == ast.SymbolUnbound || symbol.MustNotBeRenamed {
			continue
		}

		// Private symbols are in a different namespace
		if symbol.Kind.IsPrivate() {
			if g.countSymbol(nextPrivate, ref, symbol.UseCountEstimate) {
				nextPrivate++
			}
		} else {
			if g.countSymbol(next, ref, symbol.UseCountEstimate) {
				next++
			}
		}
	}

	return next, nextPrivate
}

func (g *minifyGroup) countSymbolsRecursive(
	scope *ast.Scope, symbols ast.SymbolMap, next uint32, nextPrivate uint32, labelCount uint32,
) (uint32, uint32) {
	next, nextPrivate = g.countSymbolsInScope(scope, symbols, next, nextPrivate)

	// Labels are in a separate namespace from symbols
	if scope.Kind == ast.ScopeLabel {
		symbol := symbols.Get(scope.LabelRef)
		g.countSymbol(labelCount, scope.LabelRef, symbol.UseCountEstimate+1) // +1 for the label itself
		labelCount += 1
	}

	for _, child := range scope.Children {
		// Deliberately don't update "next" and "nextPrivate" here. Sibling scopes
		// can't collide and so can reuse slots.
		g.countSymbolsRecursive(child, symbols, next, nextPrivate, labelCount)
	}
	return next, nextPrivate
}

type slotAndCount struct {
	slot  uint32
	count uint32
}

// These types are just so we can use Go's native sort function
type uint64Array []uint64
type slotAndCountArray []slotAndCount

func (a uint64Array) Len() int               { return len(a) }
func (a uint64Array) Swap(i int, j int)      { a[i], a[j] = a[j], a[i] }
func (a uint64Array) Less(i int, j int) bool { return a[i] < a[j] }

func (a slotAndCountArray) Len() int          { return len(a) }
func (a slotAndCountArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
func (a slotAndCountArray) Less(i int, j int) bool {
	ai, aj := a[i], a[j]
	return ai.count > aj.count || (ai.count == aj.count && ai.slot < aj.slot)
}
