package checkers

import (
	"go/ast"
	"go/token"

	"github.com/go-critic/go-critic/checkers/internal/astwalk"
	"github.com/go-critic/go-critic/framework/linter"
	"github.com/go-toolsmith/astcast"
	"github.com/go-toolsmith/astequal"
)

func init() {
	var info linter.CheckerInfo
	info.Name = "valSwap"
	info.Tags = []string{"style"}
	info.Summary = "Detects value swapping code that are not using parallel assignment"
	info.Before = `
tmp := *x
*x = *y
*y = tmp`
	info.After = `*x, *y = *y, *x`

	collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) {
		return astwalk.WalkerForStmtList(&valSwapChecker{ctx: ctx}), nil
	})
}

type valSwapChecker struct {
	astwalk.WalkHandler
	ctx *linter.CheckerContext
}

func (c *valSwapChecker) VisitStmtList(list []ast.Stmt) {
	for len(list) >= 3 {
		tmpAssign := astcast.ToAssignStmt(list[0])
		assignX := astcast.ToAssignStmt(list[1])
		assignY := astcast.ToAssignStmt(list[2])

		cond := c.isSimpleAssign(tmpAssign) &&
			c.isSimpleAssign(assignX) &&
			c.isSimpleAssign(assignY) &&
			assignX.Tok == token.ASSIGN &&
			assignY.Tok == token.ASSIGN &&
			astequal.Expr(assignX.Lhs[0], tmpAssign.Rhs[0]) &&
			astequal.Expr(assignX.Rhs[0], assignY.Lhs[0]) &&
			astequal.Expr(assignY.Rhs[0], tmpAssign.Lhs[0])
		if cond {
			c.warn(tmpAssign, assignX.Lhs[0], assignY.Lhs[0])
			list = list[3:]
		} else {
			list = list[1:]
		}
	}
}

func (c *valSwapChecker) isSimpleAssign(x *ast.AssignStmt) bool {
	return len(x.Lhs) == 1 && len(x.Rhs) == 1
}

func (c *valSwapChecker) warn(cause, x, y ast.Node) {
	c.ctx.Warn(cause, "can re-write as `%s, %s = %s, %s`",
		x, y, y, x)
}
