// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gc

import "cmd/internal/obj"

func overlap_cplx(f *Node, t *Node) bool {
	// check whether f and t could be overlapping stack references.
	// not exact, because it's hard to check for the stack register
	// in portable code.  close enough: worst case we will allocate
	// an extra temporary and the registerizer will clean it up.
	return f.Op == OINDREG && t.Op == OINDREG && f.Xoffset+f.Type.Width >= t.Xoffset && t.Xoffset+t.Type.Width >= f.Xoffset
}

func complexbool(op Op, nl, nr, res *Node, wantTrue bool, likely int, to *obj.Prog) {
	// make both sides addable in ullman order
	if nr != nil {
		if nl.Ullman > nr.Ullman && !nl.Addable {
			nl = CgenTemp(nl)
		}

		if !nr.Addable {
			nr = CgenTemp(nr)
		}
	}
	if !nl.Addable {
		nl = CgenTemp(nl)
	}

	// Break nl and nr into real and imaginary components.
	var lreal, limag, rreal, rimag Node
	subnode(&lreal, &limag, nl)
	subnode(&rreal, &rimag, nr)

	// build tree
	// if branching:
	// 	real(l) == real(r) && imag(l) == imag(r)
	// if generating a value, use a branch-free version:
	// 	real(l) == real(r) & imag(l) == imag(r)
	realeq := Node{
		Op:    OEQ,
		Left:  &lreal,
		Right: &rreal,
		Type:  Types[TBOOL],
	}
	imageq := Node{
		Op:    OEQ,
		Left:  &limag,
		Right: &rimag,
		Type:  Types[TBOOL],
	}
	and := Node{
		Op:    OANDAND,
		Left:  &realeq,
		Right: &imageq,
		Type:  Types[TBOOL],
	}

	if res != nil {
		// generating a value
		and.Op = OAND
		if op == ONE {
			and.Op = OOR
			realeq.Op = ONE
			imageq.Op = ONE
		}
		Bvgen(&and, res, true)
		return
	}

	// generating a branch
	if op == ONE {
		wantTrue = !wantTrue
	}

	Bgen(&and, wantTrue, likely, to)
}

// break addable nc-complex into nr-real and ni-imaginary
func subnode(nr *Node, ni *Node, nc *Node) {
	if !nc.Addable {
		Fatalf("subnode not addable")
	}

	tc := Simsimtype(nc.Type)
	tc = cplxsubtype(tc)
	t := Types[tc]

	if nc.Op == OLITERAL {
		nodfconst(nr, t, &nc.Val().U.(*Mpcplx).Real)
		nodfconst(ni, t, &nc.Val().U.(*Mpcplx).Imag)
		return
	}

	*nr = *nc
	nr.Type = t

	*ni = *nc
	ni.Type = t
	ni.Xoffset += t.Width
}

// generate code res = -nl
func minus(nl *Node, res *Node) {
	var ra Node
	ra.Op = OMINUS
	ra.Left = nl
	ra.Type = nl.Type
	Cgen(&ra, res)
}

// build and execute tree
//	real(res) = -real(nl)
//	imag(res) = -imag(nl)
func complexminus(nl *Node, res *Node) {
	var n1 Node
	var n2 Node
	var n5 Node
	var n6 Node

	subnode(&n1, &n2, nl)
	subnode(&n5, &n6, res)

	minus(&n1, &n5)
	minus(&n2, &n6)
}

// build and execute tree
//	real(res) = real(nl) op real(nr)
//	imag(res) = imag(nl) op imag(nr)
func complexadd(op Op, nl *Node, nr *Node, res *Node) {
	var n1 Node
	var n2 Node
	var n3 Node
	var n4 Node
	var n5 Node
	var n6 Node

	subnode(&n1, &n2, nl)
	subnode(&n3, &n4, nr)
	subnode(&n5, &n6, res)

	var ra Node
	ra.Op = op
	ra.Left = &n1
	ra.Right = &n3
	ra.Type = n1.Type
	Cgen(&ra, &n5)

	ra = Node{}
	ra.Op = op
	ra.Left = &n2
	ra.Right = &n4
	ra.Type = n2.Type
	Cgen(&ra, &n6)
}

// build and execute tree
//	tmp       = real(nl)*real(nr) - imag(nl)*imag(nr)
//	imag(res) = real(nl)*imag(nr) + imag(nl)*real(nr)
//	real(res) = tmp
func complexmul(nl *Node, nr *Node, res *Node) {
	var n1 Node
	var n2 Node
	var n3 Node
	var n4 Node
	var n5 Node
	var n6 Node
	var tmp Node

	subnode(&n1, &n2, nl)
	subnode(&n3, &n4, nr)
	subnode(&n5, &n6, res)
	Tempname(&tmp, n5.Type)

	// real part -> tmp
	var rm1 Node

	rm1.Op = OMUL
	rm1.Left = &n1
	rm1.Right = &n3
	rm1.Type = n1.Type

	var rm2 Node
	rm2.Op = OMUL
	rm2.Left = &n2
	rm2.Right = &n4
	rm2.Type = n2.Type

	var ra Node
	ra.Op = OSUB
	ra.Left = &rm1
	ra.Right = &rm2
	ra.Type = rm1.Type
	Cgen(&ra, &tmp)

	// imag part
	rm1 = Node{}

	rm1.Op = OMUL
	rm1.Left = &n1
	rm1.Right = &n4
	rm1.Type = n1.Type

	rm2 = Node{}
	rm2.Op = OMUL
	rm2.Left = &n2
	rm2.Right = &n3
	rm2.Type = n2.Type

	ra = Node{}
	ra.Op = OADD
	ra.Left = &rm1
	ra.Right = &rm2
	ra.Type = rm1.Type
	Cgen(&ra, &n6)

	// tmp ->real part
	Cgen(&tmp, &n5)
}

func nodfconst(n *Node, t *Type, fval *Mpflt) {
	*n = Node{}
	n.Op = OLITERAL
	n.Addable = true
	ullmancalc(n)
	n.SetVal(Val{fval})
	n.Type = t

	if !Isfloat[t.Etype] {
		Fatalf("nodfconst: bad type %v", t)
	}
}

func Complexop(n *Node, res *Node) bool {
	if n != nil && n.Type != nil {
		if Iscomplex[n.Type.Etype] {
			goto maybe
		}
	}

	if res != nil && res.Type != nil {
		if Iscomplex[res.Type.Etype] {
			goto maybe
		}
	}

	if n.Op == OREAL || n.Op == OIMAG {
		//dump("\ncomplex-yes", n);
		return true
	}

	//dump("\ncomplex-no", n);
	return false

maybe:
	switch n.Op {
	case OCONV, // implemented ops
		OADD,
		OSUB,
		OMUL,
		OMINUS,
		OCOMPLEX,
		OREAL,
		OIMAG:
		//dump("\ncomplex-yes", n);
		return true

	case ODOT,
		ODOTPTR,
		OINDEX,
		OIND,
		ONAME:
		//dump("\ncomplex-yes", n);
		return true
	}

	//dump("\ncomplex-no", n);
	return false
}

func Complexmove(f *Node, t *Node) {
	if Debug['g'] != 0 {
		Dump("\ncomplexmove-f", f)
		Dump("complexmove-t", t)
	}

	if !t.Addable {
		Fatalf("complexmove: to not addable")
	}

	ft := Simsimtype(f.Type)
	tt := Simsimtype(t.Type)
	// complex to complex move/convert.
	// make f addable.
	// also use temporary if possible stack overlap.
	if (ft == TCOMPLEX64 || ft == TCOMPLEX128) && (tt == TCOMPLEX64 || tt == TCOMPLEX128) {
		if !f.Addable || overlap_cplx(f, t) {
			var tmp Node
			Tempname(&tmp, f.Type)
			Complexmove(f, &tmp)
			f = &tmp
		}

		var n1 Node
		var n2 Node
		subnode(&n1, &n2, f)
		var n4 Node
		var n3 Node
		subnode(&n3, &n4, t)

		Cgen(&n1, &n3)
		Cgen(&n2, &n4)
	} else {
		Fatalf("complexmove: unknown conversion: %v -> %v\n", f.Type, t.Type)
	}
}

func Complexgen(n *Node, res *Node) {
	if Debug['g'] != 0 {
		Dump("\ncomplexgen-n", n)
		Dump("complexgen-res", res)
	}

	for n.Op == OCONVNOP {
		n = n.Left
	}

	// pick off float/complex opcodes
	switch n.Op {
	case OCOMPLEX:
		if res.Addable {
			var n1 Node
			var n2 Node
			subnode(&n1, &n2, res)
			var tmp Node
			Tempname(&tmp, n1.Type)
			Cgen(n.Left, &tmp)
			Cgen(n.Right, &n2)
			Cgen(&tmp, &n1)
			return
		}

	case OREAL, OIMAG:
		nl := n.Left
		if !nl.Addable {
			var tmp Node
			Tempname(&tmp, nl.Type)
			Complexgen(nl, &tmp)
			nl = &tmp
		}

		var n1 Node
		var n2 Node
		subnode(&n1, &n2, nl)
		if n.Op == OREAL {
			Cgen(&n1, res)
			return
		}

		Cgen(&n2, res)
		return
	}

	// perform conversion from n to res
	tl := Simsimtype(res.Type)

	tl = cplxsubtype(tl)
	tr := Simsimtype(n.Type)
	tr = cplxsubtype(tr)
	if tl != tr {
		if !n.Addable {
			var n1 Node
			Tempname(&n1, n.Type)
			Complexmove(n, &n1)
			n = &n1
		}

		Complexmove(n, res)
		return
	}

	if !res.Addable {
		var n1 Node
		Igen(res, &n1, nil)
		Cgen(n, &n1)
		Regfree(&n1)
		return
	}

	if n.Addable {
		Complexmove(n, res)
		return
	}

	switch n.Op {
	default:
		Dump("complexgen: unknown op", n)
		Fatalf("complexgen: unknown op %v", Oconv(n.Op, 0))

	case ODOT,
		ODOTPTR,
		OINDEX,
		OIND,
		ONAME, // PHEAP or PPARAMREF var
		OCALLFUNC,
		OCALLMETH,
		OCALLINTER:
		var n1 Node
		Igen(n, &n1, res)

		Complexmove(&n1, res)
		Regfree(&n1)
		return

	case OCONV,
		OADD,
		OSUB,
		OMUL,
		OMINUS,
		OCOMPLEX,
		OREAL,
		OIMAG:
		break
	}

	nl := n.Left
	if nl == nil {
		return
	}
	nr := n.Right

	// make both sides addable in ullman order
	var tnl Node
	if nr != nil {
		if nl.Ullman > nr.Ullman && !nl.Addable {
			Tempname(&tnl, nl.Type)
			Cgen(nl, &tnl)
			nl = &tnl
		}

		if !nr.Addable {
			var tnr Node
			Tempname(&tnr, nr.Type)
			Cgen(nr, &tnr)
			nr = &tnr
		}
	}

	if !nl.Addable {
		Tempname(&tnl, nl.Type)
		Cgen(nl, &tnl)
		nl = &tnl
	}

	switch n.Op {
	default:
		Fatalf("complexgen: unknown op %v", Oconv(n.Op, 0))

	case OCONV:
		Complexmove(nl, res)

	case OMINUS:
		complexminus(nl, res)

	case OADD, OSUB:
		complexadd(n.Op, nl, nr, res)

	case OMUL:
		complexmul(nl, nr, res)
	}
}
