package avm

import "testing"

func q16(x int32) Val { return Val{Ty: Q16_16, Q: avmClamp(int64(x))} }

func runTest(prog []Instr, fuel int) (*State, error) {
    return Run(NewState(0), prog, fuel)
}

func TestBasicAdd(t *testing.T) {
    prog := []Instr{
        {Op: PushQ16, Arg: 5 * Q16Scale},
        {Op: PushQ16, Arg: 3 * Q16Scale},
        {Op: Primitive, Arg: int32(AddSatQ16)},
        {Op: Halt},
    }
    s, err := runTest(prog, 100)
    if err != nil { t.Fatal(err) }
    if s.Stack[0].Q != 8*Q16Scale { t.Fatalf("got %d", s.Stack[0].Q) }
}

func TestFloorDiv(t *testing.T) {
    // (3 * Q16) / (5 * Q16) = floor(0.6) = 0.6 in Q16
    prog := []Instr{
        {Op: PushQ16, Arg: 3 * Q16Scale},
        {Op: PushQ16, Arg: 5 * Q16Scale},
        {Op: Primitive, Arg: int32(DivSatQ16)},
        {Op: Halt},
    }
    s, err := runTest(prog, 100)
    if err != nil { t.Fatal(err) }
    expected := int32((3 * Q16Scale) / 5)
    if s.Stack[0].Q != expected { t.Fatalf("got %d, expected %d", s.Stack[0].Q, expected) }
}

func TestSaturation(t *testing.T) {
    prog := []Instr{
        {Op: PushQ16, Arg: AVMClampMax - 1},
        {Op: PushQ16, Arg: 2},
        {Op: Primitive, Arg: int32(AddSatQ16)},
        {Op: Halt},
    }
    s, err := runTest(prog, 100)
    if err != nil { t.Fatal(err) }
    if s.Stack[0].Q != AVMClampMax { t.Fatalf("got %d", s.Stack[0].Q) }
}

func TestV6LtQ16(t *testing.T) {
    cases := []struct{ a, b int32; exp bool }{
        {-5 * Q16Scale, -3 * Q16Scale, true},
        {-3 * Q16Scale, -5 * Q16Scale, false},
        {5 * Q16Scale, 3 * Q16Scale, false},
        {3 * Q16Scale, 5 * Q16Scale, true},
        {-1 * Q16Scale, 2 * Q16Scale, true},
    }
    for _, c := range cases {
        prog := []Instr{
            {Op: PushQ16, Arg: c.a},
            {Op: PushQ16, Arg: c.b},
            {Op: Primitive, Arg: int32(LtQ16)},
            {Op: Halt},
        }
        s, err := runTest(prog, 100)
        if err != nil { t.Fatal(err) }
        if s.Stack[0].Bval != c.exp { t.Fatalf("ltQ16(%d,%d): got %v", c.a, c.b, s.Stack[0]) }
    }
}

func TestDivByZero(t *testing.T) {
    prog := []Instr{
        {Op: PushQ16, Arg: Q16Scale},
        {Op: PushQ16, Arg: 0},
        {Op: Primitive, Arg: int32(DivSatQ16)},
    }
    _, err := runTest(prog, 100)
    if err == nil { t.Fatal("expected error") }
}

func TestStackOverflow(t *testing.T) {
    prog := make([]Instr, AVMMaxStack+1)
    for i := range prog { prog[i] = Instr{Op: PushQ16, Arg: 0} }
    _, err := runTest(prog, 10000)
    if err == nil { t.Fatal("expected overflow") }
}

func TestControlFlow(t *testing.T) {
    prog := []Instr{
        {Op: PushBool, Arg2: true},
        {Op: JumpIf, Arg: 4},
        {Op: PushQ16, Arg: 0},
        {Op: Halt},
        {Op: PushQ16, Arg: Q16Scale},
        {Op: Halt},
    }
    s, err := runTest(prog, 100)
    if err != nil { t.Fatal(err) }
    if s.Stack[0].Q != Q16Scale { t.Fatalf("got %d", s.Stack[0].Q) }
}

func TestLocals(t *testing.T) {
    s := NewState(1)
    prog := []Instr{
        {Op: PushQ16, Arg: 42 * Q16Scale},
        {Op: Store, Arg: 0},
        {Op: Load, Arg: 0},
        {Op: Halt},
    }
    result, err := Run(s, prog, 100)
    if err != nil { t.Fatal(err) }
    if result.Stack[0].Q != 42*Q16Scale { t.Fatalf("got %d", result.Stack[0].Q) }
}
