package vm

import (
	"testing"

	"github.com/chainmint/testutil"
)

func TestControlOps(t *testing.T) {
	type testStruct struct {
		op      Op
		startVM *virtualMachine
		wantErr error
		wantVM  *virtualMachine
	}
	cases := []testStruct{{
		op: OP_JUMP,
		startVM: &virtualMachine{
			runLimit: 50000,
			pc:       0,
			nextPC:   1,
			data:     []byte{0x05, 0x00, 0x00, 0x00},
		},
		wantErr: nil,
		wantVM: &virtualMachine{
			runLimit: 49999,
			pc:       0,
			nextPC:   5,
			data:     []byte{0x05, 0x00, 0x00, 0x00},
		},
	}, {
		op: OP_JUMP,
		startVM: &virtualMachine{
			runLimit: 50000,
			pc:       0,
			nextPC:   1,
			data:     []byte{0xff, 0xff, 0xff, 0xff},
		},
		wantErr: nil,
		wantVM: &virtualMachine{
			runLimit: 49999,
			pc:       0,
			nextPC:   4294967295,
			data:     []byte{0xff, 0xff, 0xff, 0xff},
		},
	}, {
		op: OP_JUMPIF,
		startVM: &virtualMachine{
			runLimit:     50000,
			pc:           0,
			nextPC:       1,
			deferredCost: 0,
			dataStack:    [][]byte{{1}},
			data:         []byte{0x05, 0x00, 0x00, 0x00},
		},
		wantErr: nil,
		wantVM: &virtualMachine{
			runLimit:     49999,
			pc:           0,
			nextPC:       5,
			deferredCost: -9,
			dataStack:    [][]byte{},
			data:         []byte{0x05, 0x00, 0x00, 0x00},
		},
	}, {
		op: OP_JUMPIF,
		startVM: &virtualMachine{
			runLimit:     50000,
			pc:           0,
			nextPC:       1,
			deferredCost: 0,
			dataStack:    [][]byte{{}},
			data:         []byte{0x05, 0x00, 0x00, 0x00},
		},
		wantErr: nil,
		wantVM: &virtualMachine{
			runLimit:     49999,
			pc:           0,
			nextPC:       1,
			deferredCost: -8,
			dataStack:    [][]byte{},
			data:         []byte{0x05, 0x00, 0x00, 0x00},
		},
	}, {
		op: OP_VERIFY,
		startVM: &virtualMachine{
			pc:           0,
			runLimit:     50000,
			deferredCost: 0,
			dataStack:    [][]byte{{1}},
		},
		wantErr: nil,
		wantVM: &virtualMachine{
			runLimit:     49999,
			deferredCost: -9,
			dataStack:    [][]byte{},
		},
	}, {
		op: OP_VERIFY,
		startVM: &virtualMachine{
			runLimit:     50000,
			deferredCost: 0,
			dataStack:    [][]byte{{1, 1}},
		},
		wantErr: nil,
		wantVM: &virtualMachine{
			runLimit:     49999,
			deferredCost: -10,
			dataStack:    [][]byte{},
		},
	}, {
		op: OP_VERIFY,
		startVM: &virtualMachine{
			runLimit:     50000,
			deferredCost: 0,
			dataStack:    [][]byte{{}},
		},
		wantErr: ErrVerifyFailed,
	}, {
		startVM: &virtualMachine{runLimit: 50000},
		op:      OP_FAIL,
		wantErr: ErrReturn,
	}, {
		op: OP_CHECKPREDICATE,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{}, {byte(OP_TRUE)}, {}},
		},
		wantVM: &virtualMachine{
			runLimit:     0,
			deferredCost: -49951,
			dataStack:    [][]byte{{1}},
		},
	}, {
		op: OP_CHECKPREDICATE,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{}, {}, {}},
		},
		wantVM: &virtualMachine{
			runLimit:     0,
			deferredCost: -49952,
			dataStack:    [][]byte{{}},
		},
	}, {
		op: OP_CHECKPREDICATE,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{}, {byte(OP_FAIL)}, {}},
		},
		wantVM: &virtualMachine{
			runLimit:     0,
			deferredCost: -49952,
			dataStack:    [][]byte{{}},
		},
	}, {
		op: OP_CHECKPREDICATE,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{}, {}, Int64Bytes(-1)},
		},
		wantErr: ErrBadValue,
	}, {
		op: OP_CHECKPREDICATE,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{}, {}, Int64Bytes(50000)},
		},
		wantErr: ErrRunLimitExceeded,
	}, {
		op: OP_CHECKPREDICATE,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0x05}, {0x07}, {0x02}, {byte(OP_ADD), byte(OP_12), byte(OP_NUMEQUAL)}, {}},
		},
		wantVM: &virtualMachine{
			deferredCost: -49968,
			dataStack:    [][]byte{{0x01}},
		},
	}, {
		// stack underflow in child vm should produce false result in parent vm
		op: OP_CHECKPREDICATE,
		startVM: &virtualMachine{
			runLimit:  50000,
			dataStack: [][]byte{{0x05}, {0x07}, {0x01}, {byte(OP_ADD), byte(OP_DATA_12), byte(OP_NUMEQUAL)}, {}},
		},
		wantVM: &virtualMachine{
			deferredCost: -49954,
			dataStack:    [][]byte{{0x05}, {}},
		},
	}}

	limitChecks := []Op{
		OP_CHECKPREDICATE, OP_VERIFY, OP_FAIL,
	}

	for _, op := range limitChecks {
		cases = append(cases, testStruct{
			op:      op,
			startVM: &virtualMachine{runLimit: 0},
			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, c.op.String(), 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, c.op.String(), c.startVM, c.wantVM)
		}
	}
}
