package vrp

import (
	"fmt"
	"go/token"
	"go/types"
	"math/big"

	"github.com/golangci/go-tools/ssa"
)

type Zs []Z

func (zs Zs) Len() int {
	return len(zs)
}

func (zs Zs) Less(i int, j int) bool {
	return zs[i].Cmp(zs[j]) == -1
}

func (zs Zs) Swap(i int, j int) {
	zs[i], zs[j] = zs[j], zs[i]
}

type Z struct {
	infinity int8
	integer  *big.Int
}

func NewZ(n int64) Z {
	return NewBigZ(big.NewInt(n))
}

func NewBigZ(n *big.Int) Z {
	return Z{integer: n}
}

func (z1 Z) Infinite() bool {
	return z1.infinity != 0
}

func (z1 Z) Add(z2 Z) Z {
	if z2.Sign() == -1 {
		return z1.Sub(z2.Negate())
	}
	if z1 == NInfinity {
		return NInfinity
	}
	if z1 == PInfinity {
		return PInfinity
	}
	if z2 == PInfinity {
		return PInfinity
	}

	if !z1.Infinite() && !z2.Infinite() {
		n := &big.Int{}
		n.Add(z1.integer, z2.integer)
		return NewBigZ(n)
	}

	panic(fmt.Sprintf("%s + %s is not defined", z1, z2))
}

func (z1 Z) Sub(z2 Z) Z {
	if z2.Sign() == -1 {
		return z1.Add(z2.Negate())
	}
	if !z1.Infinite() && !z2.Infinite() {
		n := &big.Int{}
		n.Sub(z1.integer, z2.integer)
		return NewBigZ(n)
	}

	if z1 != PInfinity && z2 == PInfinity {
		return NInfinity
	}
	if z1.Infinite() && !z2.Infinite() {
		return Z{infinity: z1.infinity}
	}
	if z1 == PInfinity && z2 == PInfinity {
		return PInfinity
	}
	panic(fmt.Sprintf("%s - %s is not defined", z1, z2))
}

func (z1 Z) Mul(z2 Z) Z {
	if (z1.integer != nil && z1.integer.Sign() == 0) ||
		(z2.integer != nil && z2.integer.Sign() == 0) {
		return NewBigZ(&big.Int{})
	}

	if z1.infinity != 0 || z2.infinity != 0 {
		return Z{infinity: int8(z1.Sign() * z2.Sign())}
	}

	n := &big.Int{}
	n.Mul(z1.integer, z2.integer)
	return NewBigZ(n)
}

func (z1 Z) Negate() Z {
	if z1.infinity == 1 {
		return NInfinity
	}
	if z1.infinity == -1 {
		return PInfinity
	}
	n := &big.Int{}
	n.Neg(z1.integer)
	return NewBigZ(n)
}

func (z1 Z) Sign() int {
	if z1.infinity != 0 {
		return int(z1.infinity)
	}
	return z1.integer.Sign()
}

func (z1 Z) String() string {
	if z1 == NInfinity {
		return "-∞"
	}
	if z1 == PInfinity {
		return "∞"
	}
	return fmt.Sprintf("%d", z1.integer)
}

func (z1 Z) Cmp(z2 Z) int {
	if z1.infinity == z2.infinity && z1.infinity != 0 {
		return 0
	}
	if z1 == PInfinity {
		return 1
	}
	if z1 == NInfinity {
		return -1
	}
	if z2 == NInfinity {
		return 1
	}
	if z2 == PInfinity {
		return -1
	}
	return z1.integer.Cmp(z2.integer)
}

func MaxZ(zs ...Z) Z {
	if len(zs) == 0 {
		panic("Max called with no arguments")
	}
	if len(zs) == 1 {
		return zs[0]
	}
	ret := zs[0]
	for _, z := range zs[1:] {
		if z.Cmp(ret) == 1 {
			ret = z
		}
	}
	return ret
}

func MinZ(zs ...Z) Z {
	if len(zs) == 0 {
		panic("Min called with no arguments")
	}
	if len(zs) == 1 {
		return zs[0]
	}
	ret := zs[0]
	for _, z := range zs[1:] {
		if z.Cmp(ret) == -1 {
			ret = z
		}
	}
	return ret
}

var NInfinity = Z{infinity: -1}
var PInfinity = Z{infinity: 1}
var EmptyIntInterval = IntInterval{true, PInfinity, NInfinity}

func InfinityFor(v ssa.Value) IntInterval {
	if b, ok := v.Type().Underlying().(*types.Basic); ok {
		if (b.Info() & types.IsUnsigned) != 0 {
			return NewIntInterval(NewZ(0), PInfinity)
		}
	}
	return NewIntInterval(NInfinity, PInfinity)
}

type IntInterval struct {
	known bool
	Lower Z
	Upper Z
}

func NewIntInterval(l, u Z) IntInterval {
	if u.Cmp(l) == -1 {
		return EmptyIntInterval
	}
	return IntInterval{known: true, Lower: l, Upper: u}
}

func (i IntInterval) IsKnown() bool {
	return i.known
}

func (i IntInterval) Empty() bool {
	return i.Lower == PInfinity && i.Upper == NInfinity
}

func (i IntInterval) IsMaxRange() bool {
	return i.Lower == NInfinity && i.Upper == PInfinity
}

func (i1 IntInterval) Intersection(i2 IntInterval) IntInterval {
	if !i1.IsKnown() {
		return i2
	}
	if !i2.IsKnown() {
		return i1
	}
	if i1.Empty() || i2.Empty() {
		return EmptyIntInterval
	}
	i3 := NewIntInterval(MaxZ(i1.Lower, i2.Lower), MinZ(i1.Upper, i2.Upper))
	if i3.Lower.Cmp(i3.Upper) == 1 {
		return EmptyIntInterval
	}
	return i3
}

func (i1 IntInterval) Union(other Range) Range {
	i2, ok := other.(IntInterval)
	if !ok {
		i2 = EmptyIntInterval
	}
	if i1.Empty() || !i1.IsKnown() {
		return i2
	}
	if i2.Empty() || !i2.IsKnown() {
		return i1
	}
	return NewIntInterval(MinZ(i1.Lower, i2.Lower), MaxZ(i1.Upper, i2.Upper))
}

func (i1 IntInterval) Add(i2 IntInterval) IntInterval {
	if i1.Empty() || i2.Empty() {
		return EmptyIntInterval
	}
	l1, u1, l2, u2 := i1.Lower, i1.Upper, i2.Lower, i2.Upper
	return NewIntInterval(l1.Add(l2), u1.Add(u2))
}

func (i1 IntInterval) Sub(i2 IntInterval) IntInterval {
	if i1.Empty() || i2.Empty() {
		return EmptyIntInterval
	}
	l1, u1, l2, u2 := i1.Lower, i1.Upper, i2.Lower, i2.Upper
	return NewIntInterval(l1.Sub(u2), u1.Sub(l2))
}

func (i1 IntInterval) Mul(i2 IntInterval) IntInterval {
	if i1.Empty() || i2.Empty() {
		return EmptyIntInterval
	}
	x1, x2 := i1.Lower, i1.Upper
	y1, y2 := i2.Lower, i2.Upper
	return NewIntInterval(
		MinZ(x1.Mul(y1), x1.Mul(y2), x2.Mul(y1), x2.Mul(y2)),
		MaxZ(x1.Mul(y1), x1.Mul(y2), x2.Mul(y1), x2.Mul(y2)),
	)
}

func (i1 IntInterval) String() string {
	if !i1.IsKnown() {
		return "[⊥, ⊥]"
	}
	if i1.Empty() {
		return "{}"
	}
	return fmt.Sprintf("[%s, %s]", i1.Lower, i1.Upper)
}

type IntArithmeticConstraint struct {
	aConstraint
	A  ssa.Value
	B  ssa.Value
	Op token.Token
	Fn func(IntInterval, IntInterval) IntInterval
}

type IntAddConstraint struct{ *IntArithmeticConstraint }
type IntSubConstraint struct{ *IntArithmeticConstraint }
type IntMulConstraint struct{ *IntArithmeticConstraint }

type IntConversionConstraint struct {
	aConstraint
	X ssa.Value
}

type IntIntersectionConstraint struct {
	aConstraint
	ranges   Ranges
	A        ssa.Value
	B        ssa.Value
	Op       token.Token
	I        IntInterval
	resolved bool
}

type IntIntervalConstraint struct {
	aConstraint
	I IntInterval
}

func NewIntArithmeticConstraint(a, b, y ssa.Value, op token.Token, fn func(IntInterval, IntInterval) IntInterval) *IntArithmeticConstraint {
	return &IntArithmeticConstraint{NewConstraint(y), a, b, op, fn}
}
func NewIntAddConstraint(a, b, y ssa.Value) Constraint {
	return &IntAddConstraint{NewIntArithmeticConstraint(a, b, y, token.ADD, IntInterval.Add)}
}
func NewIntSubConstraint(a, b, y ssa.Value) Constraint {
	return &IntSubConstraint{NewIntArithmeticConstraint(a, b, y, token.SUB, IntInterval.Sub)}
}
func NewIntMulConstraint(a, b, y ssa.Value) Constraint {
	return &IntMulConstraint{NewIntArithmeticConstraint(a, b, y, token.MUL, IntInterval.Mul)}
}
func NewIntConversionConstraint(x, y ssa.Value) Constraint {
	return &IntConversionConstraint{NewConstraint(y), x}
}
func NewIntIntersectionConstraint(a, b ssa.Value, op token.Token, ranges Ranges, y ssa.Value) Constraint {
	return &IntIntersectionConstraint{
		aConstraint: NewConstraint(y),
		ranges:      ranges,
		A:           a,
		B:           b,
		Op:          op,
	}
}
func NewIntIntervalConstraint(i IntInterval, y ssa.Value) Constraint {
	return &IntIntervalConstraint{NewConstraint(y), i}
}

func (c *IntArithmeticConstraint) Operands() []ssa.Value   { return []ssa.Value{c.A, c.B} }
func (c *IntConversionConstraint) Operands() []ssa.Value   { return []ssa.Value{c.X} }
func (c *IntIntersectionConstraint) Operands() []ssa.Value { return []ssa.Value{c.A} }
func (s *IntIntervalConstraint) Operands() []ssa.Value     { return nil }

func (c *IntArithmeticConstraint) String() string {
	return fmt.Sprintf("%s = %s %s %s", c.Y().Name(), c.A.Name(), c.Op, c.B.Name())
}
func (c *IntConversionConstraint) String() string {
	return fmt.Sprintf("%s = %s(%s)", c.Y().Name(), c.Y().Type(), c.X.Name())
}
func (c *IntIntersectionConstraint) String() string {
	return fmt.Sprintf("%s = %s %s %s (%t branch)", c.Y().Name(), c.A.Name(), c.Op, c.B.Name(), c.Y().(*ssa.Sigma).Branch)
}
func (c *IntIntervalConstraint) String() string { return fmt.Sprintf("%s = %s", c.Y().Name(), c.I) }

func (c *IntArithmeticConstraint) Eval(g *Graph) Range {
	i1, i2 := g.Range(c.A).(IntInterval), g.Range(c.B).(IntInterval)
	if !i1.IsKnown() || !i2.IsKnown() {
		return IntInterval{}
	}
	return c.Fn(i1, i2)
}
func (c *IntConversionConstraint) Eval(g *Graph) Range {
	s := &types.StdSizes{
		// XXX is it okay to assume the largest word size, or do we
		// need to be platform specific?
		WordSize: 8,
		MaxAlign: 1,
	}
	fromI := g.Range(c.X).(IntInterval)
	toI := g.Range(c.Y()).(IntInterval)
	fromT := c.X.Type().Underlying().(*types.Basic)
	toT := c.Y().Type().Underlying().(*types.Basic)
	fromB := s.Sizeof(c.X.Type())
	toB := s.Sizeof(c.Y().Type())

	if !fromI.IsKnown() {
		return toI
	}
	if !toI.IsKnown() {
		return fromI
	}

	// uint<N> -> sint/uint<M>, M > N: [max(0, l1), min(2**N-1, u2)]
	if (fromT.Info()&types.IsUnsigned != 0) &&
		toB > fromB {

		n := big.NewInt(1)
		n.Lsh(n, uint(fromB*8))
		n.Sub(n, big.NewInt(1))
		return NewIntInterval(
			MaxZ(NewZ(0), fromI.Lower),
			MinZ(NewBigZ(n), toI.Upper),
		)
	}

	// sint<N> -> sint<M>, M > N; [max(-∞, l1), min(2**N-1, u2)]
	if (fromT.Info()&types.IsUnsigned == 0) &&
		(toT.Info()&types.IsUnsigned == 0) &&
		toB > fromB {

		n := big.NewInt(1)
		n.Lsh(n, uint(fromB*8))
		n.Sub(n, big.NewInt(1))
		return NewIntInterval(
			MaxZ(NInfinity, fromI.Lower),
			MinZ(NewBigZ(n), toI.Upper),
		)
	}

	return fromI
}
func (c *IntIntersectionConstraint) Eval(g *Graph) Range {
	xi := g.Range(c.A).(IntInterval)
	if !xi.IsKnown() {
		return c.I
	}
	return xi.Intersection(c.I)
}
func (c *IntIntervalConstraint) Eval(*Graph) Range { return c.I }

func (c *IntIntersectionConstraint) Futures() []ssa.Value {
	return []ssa.Value{c.B}
}

func (c *IntIntersectionConstraint) Resolve() {
	r, ok := c.ranges[c.B].(IntInterval)
	if !ok {
		c.I = InfinityFor(c.Y())
		return
	}

	switch c.Op {
	case token.EQL:
		c.I = r
	case token.GTR:
		c.I = NewIntInterval(r.Lower.Add(NewZ(1)), PInfinity)
	case token.GEQ:
		c.I = NewIntInterval(r.Lower, PInfinity)
	case token.LSS:
		// TODO(dh): do we need 0 instead of NInfinity for uints?
		c.I = NewIntInterval(NInfinity, r.Upper.Sub(NewZ(1)))
	case token.LEQ:
		c.I = NewIntInterval(NInfinity, r.Upper)
	case token.NEQ:
		c.I = InfinityFor(c.Y())
	default:
		panic("unsupported op " + c.Op.String())
	}
}

func (c *IntIntersectionConstraint) IsKnown() bool {
	return c.I.IsKnown()
}

func (c *IntIntersectionConstraint) MarkUnresolved() {
	c.resolved = false
}

func (c *IntIntersectionConstraint) MarkResolved() {
	c.resolved = true
}

func (c *IntIntersectionConstraint) IsResolved() bool {
	return c.resolved
}
