package vm

import (
	"testing"

	"github.com/chainmint/testutil"
)

func TestBitwiseOps(t *testing.T) {
	type testStruct struct {
		op      Op
		startVM *virtualMachine
		wantErr error
		wantVM  *virtualMachine
	}
	cases := []testStruct{{
		op: OP_INVERT,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{255}},
		},
		wantVM: &virtualMachine{
			runLimit:  49998,
			dataStack: [][]byte{{0}},
		},
	}, {
		op: OP_INVERT,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{255, 0}},
		},
		wantVM: &virtualMachine{
			runLimit:  49997,
			dataStack: [][]byte{{0, 255}},
		},
	}, {
		op: OP_AND,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0x80}},
		},
		wantVM: &virtualMachine{
			runLimit:     49998,
			deferredCost: -9,
			dataStack:    [][]byte{{0x80}},
		},
	}, {
		op: OP_AND,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0x80, 0xff}},
		},
		wantVM: &virtualMachine{
			runLimit:     49998,
			deferredCost: -10,
			dataStack:    [][]byte{{0x80}},
		},
	}, {
		op: OP_AND,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0x80, 0xff}, {0xff}},
		},
		wantVM: &virtualMachine{
			runLimit:     49998,
			deferredCost: -10,
			dataStack:    [][]byte{{0x80}},
		},
	}, {
		op: OP_OR,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0x80}},
		},
		wantVM: &virtualMachine{
			runLimit:     49998,
			deferredCost: -9,
			dataStack:    [][]byte{{0xff}},
		},
	}, {
		op: OP_OR,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0x80, 0x10}},
		},
		wantVM: &virtualMachine{
			runLimit:     49997,
			deferredCost: -9,
			dataStack:    [][]byte{{0xff, 0x10}},
		},
	}, {
		op: OP_OR,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff, 0x10}, {0x80}},
		},
		wantVM: &virtualMachine{
			runLimit:     49997,
			deferredCost: -9,
			dataStack:    [][]byte{{0xff, 0x10}},
		},
	}, {
		op: OP_XOR,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0x80}},
		},
		wantVM: &virtualMachine{
			runLimit:     49998,
			deferredCost: -9,
			dataStack:    [][]byte{{0x7f}},
		},
	}, {
		op: OP_XOR,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0x80, 0x10}},
		},
		wantVM: &virtualMachine{
			runLimit:     49997,
			deferredCost: -9,
			dataStack:    [][]byte{{0x7f, 0x10}},
		},
	}, {
		op: OP_XOR,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff, 0x10}, {0x80}},
		},
		wantVM: &virtualMachine{
			runLimit:     49997,
			deferredCost: -9,
			dataStack:    [][]byte{{0x7f, 0x10}},
		},
	}, {
		op: OP_EQUAL,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0xff}},
		},
		wantVM: &virtualMachine{
			runLimit:     49998,
			deferredCost: -9,
			dataStack:    [][]byte{{1}},
		},
	}, {
		op: OP_EQUAL,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff, 0x10}, {0xff, 0x10}},
		},
		wantVM: &virtualMachine{
			runLimit:     49997,
			deferredCost: -11,
			dataStack:    [][]byte{{1}},
		},
	}, {
		op: OP_EQUAL,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0x80}},
		},
		wantVM: &virtualMachine{
			runLimit:     49998,
			deferredCost: -10,
			dataStack:    [][]byte{{}},
		},
	}, {
		op: OP_EQUAL,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0xff, 0x80}},
		},
		wantVM: &virtualMachine{
			runLimit:     49998,
			deferredCost: -11,
			dataStack:    [][]byte{{}},
		},
	}, {
		op: OP_EQUAL,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff, 0x80}, {0xff}},
		},
		wantVM: &virtualMachine{
			runLimit:     49998,
			deferredCost: -11,
			dataStack:    [][]byte{{}},
		},
	}, {
		op: OP_EQUALVERIFY,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0xff}},
		},
		wantVM: &virtualMachine{
			runLimit:     49998,
			deferredCost: -18,
			dataStack:    [][]byte{},
		},
	}, {
		op: OP_EQUALVERIFY,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff, 0x10}, {0xff, 0x10}},
		},
		wantVM: &virtualMachine{
			runLimit:     49997,
			deferredCost: -20,
			dataStack:    [][]byte{},
		},
	}, {
		op: OP_EQUALVERIFY,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0x80}},
		},
		wantErr: ErrVerifyFailed,
	}, {
		op: OP_EQUALVERIFY,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff}, {0xff, 0x80}},
		},
		wantErr: ErrVerifyFailed,
	}, {
		op: OP_EQUALVERIFY,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0xff, 0x80}, {0xff}},
		},
		wantErr: ErrVerifyFailed,
	}}

	bitops := []Op{OP_INVERT, OP_AND, OP_OR, OP_XOR, OP_EQUAL, OP_EQUALVERIFY}
	for _, op := range bitops {
		cases = append(cases, testStruct{
			op: op,
			startVM: &virtualMachine{
				runLimit:  0,
				dataStack: [][]byte{{0xff}, {0xff}},
			},
			wantErr: ErrRunLimitExceeded,
		}, testStruct{
			op: op,
			startVM: &virtualMachine{
				runLimit:  1,
				dataStack: [][]byte{{0xff}, {0xff}},
			},
			wantErr: ErrRunLimitExceeded,
		})
	}

	for i, c := range cases {
		err := ops[c.op].fn(c.startVM)

		if err != c.wantErr {
			t.Errorf("case %d, op %s: got err = %v want %v", i, ops[c.op].name, err, c.wantErr)
			continue
		}
		if c.wantErr != nil {
			continue
		}

		if !testutil.DeepEqual(c.startVM, c.wantVM) {
			t.Errorf("case %d, op %s: unexpected vm result\n\tgot:  %+v\n\twant: %+v\n", i, ops[c.op].name, c.startVM, c.wantVM)
		}
	}
}
