// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package syntax

import 

// Simplify modifies a node to remove redundant pieces of syntax, and returns
// whether any changes were made.
//
// The changes currently applied are:
//
//	Remove clearly useless parentheses       $(( (expr) ))
//	Remove dollars from vars in exprs        (($var))
//	Remove duplicate subshells               $( (stmts) )
//	Remove redundant quotes                  [[ "$var" == str ]]
//	Merge negations with unary operators     [[ ! -n $var ]]
//	Use single quotes to shorten literals    "\$foo"
func ( Node) bool {
	 := simplifier{}
	Walk(, .visit)
	return .modified
}

type simplifier struct {
	modified bool
}

func ( *simplifier) ( Node) bool {
	switch x := .(type) {
	case *Assign:
		.Index = .removeParensArithm(.Index)
		// Don't inline params, as x[i] and x[$i] mean
		// different things when x is an associative
		// array; the first means "i", the second "$i".
	case *ParamExp:
		.Index = .removeParensArithm(.Index)
		// don't inline params - same as above.

		if .Slice == nil {
			break
		}
		.Slice.Offset = .removeParensArithm(.Slice.Offset)
		.Slice.Offset = .inlineSimpleParams(.Slice.Offset)
		.Slice.Length = .removeParensArithm(.Slice.Length)
		.Slice.Length = .inlineSimpleParams(.Slice.Length)
	case *ArithmExp:
		.X = .removeParensArithm(.X)
		.X = .inlineSimpleParams(.X)
	case *ArithmCmd:
		.X = .removeParensArithm(.X)
		.X = .inlineSimpleParams(.X)
	case *ParenArithm:
		.X = .removeParensArithm(.X)
		.X = .inlineSimpleParams(.X)
	case *BinaryArithm:
		.X = .inlineSimpleParams(.X)
		.Y = .inlineSimpleParams(.Y)
	case *CmdSubst:
		.Stmts = .inlineSubshell(.Stmts)
	case *Subshell:
		.Stmts = .inlineSubshell(.Stmts)
	case *Word:
		.Parts = .simplifyWord(.Parts)
	case *TestClause:
		.X = .removeParensTest(.X)
		.X = .removeNegateTest(.X)
	case *ParenTest:
		.X = .removeParensTest(.X)
		.X = .removeNegateTest(.X)
	case *BinaryTest:
		.X = .unquoteParams(.X)
		.X = .removeNegateTest(.X)
		if .Op == TsMatchShort {
			.modified = true
			.Op = TsMatch
		}
		switch .Op {
		case TsMatch, TsNoMatch:
			// unquoting enables globbing
		default:
			.Y = .unquoteParams(.Y)
		}
		.Y = .removeNegateTest(.Y)
	case *UnaryTest:
		.X = .unquoteParams(.X)
	}
	return true
}

func ( *simplifier) ( []WordPart) []WordPart {
:
	for ,  := range  {
		,  := .(*DblQuoted)
		if  == nil || len(.Parts) != 1 {
			break
		}
		,  := .Parts[0].(*Lit)
		if  == nil {
			break
		}
		var  bytes.Buffer
		 := false
		for ,  := range .Value {
			switch  {
			case '\\':
				 = !
				if  {
					continue
				}
			case '\'':
				continue 
			case '$', '"', '`':
				 = false
			default:
				if  {
					continue 
				}
				 = false
			}
			.WriteRune()
		}
		 := .String()
		if  == .Value {
			break
		}
		.modified = true
		[] = &SglQuoted{
			Left:   .Pos(),
			Right:  .End(),
			Dollar: .Dollar,
			Value:  ,
		}
	}
	return 
}

func ( *simplifier) ( ArithmExpr) ArithmExpr {
	for {
		,  := .(*ParenArithm)
		if  == nil {
			return 
		}
		.modified = true
		 = .X
	}
}

func ( *simplifier) ( ArithmExpr) ArithmExpr {
	,  := .(*Word)
	if  == nil || len(.Parts) != 1 {
		return 
	}
	,  := .Parts[0].(*ParamExp)
	if  == nil || !ValidName(.Param.Value) {
		// Not a parameter expansion, or not a valid name, like $3.
		return 
	}
	if .Excl || .Length || .Width || .Slice != nil ||
		.Repl != nil || .Exp != nil || .Index != nil {
		// A complex parameter expansion can't be simplified.
		//
		// Note that index expressions can't generally be simplified
		// either. It's fine to turn ${a[0]} into a[0], but others like
		// a[*] are invalid in many shells including Bash.
		return 
	}
	.modified = true
	return &Word{Parts: []WordPart{.Param}}
}

func ( *simplifier) ( []*Stmt) []*Stmt {
	for len() == 1 {
		 := [0]
		if .Negated || .Background || .Coprocess ||
			len(.Redirs) > 0 {
			break
		}
		,  := .Cmd.(*Subshell)
		if  == nil {
			break
		}
		.modified = true
		 = .Stmts
	}
	return 
}

func ( *simplifier) ( TestExpr) TestExpr {
	,  := .(*Word)
	if  == nil || len(.Parts) != 1 {
		return 
	}
	,  := .Parts[0].(*DblQuoted)
	if  == nil || len(.Parts) != 1 {
		return 
	}
	if ,  := .Parts[0].(*ParamExp); ! {
		return 
	}
	.modified = true
	.Parts = .Parts
	return 
}

func ( *simplifier) ( TestExpr) TestExpr {
	for {
		,  := .(*ParenTest)
		if  == nil {
			return 
		}
		.modified = true
		 = .X
	}
}

func ( *simplifier) ( TestExpr) TestExpr {
	,  := .(*UnaryTest)
	if  == nil || .Op != TsNot {
		return 
	}
	switch y := .X.(type) {
	case *UnaryTest:
		switch .Op {
		case TsEmpStr:
			.Op = TsNempStr
			.modified = true
			return 
		case TsNempStr:
			.Op = TsEmpStr
			.modified = true
			return 
		case TsNot:
			.modified = true
			return .X
		}
	case *BinaryTest:
		switch .Op {
		case TsMatch:
			.Op = TsNoMatch
			.modified = true
			return 
		case TsNoMatch:
			.Op = TsMatch
			.modified = true
			return 
		}
	}
	return 
}