package main

import (
	"fmt"
	"github.com/ethereum/ethutil-go"
	"math/big"
	"strconv"
)

// Op codes
const (
	oSTOP           int = 0x00
	oADD            int = 0x01
	oMUL            int = 0x02
	oSUB            int = 0x03
	oDIV            int = 0x04
	oSDIV           int = 0x05
	oMOD            int = 0x06
	oSMOD           int = 0x07
	oEXP            int = 0x08
	oNEG            int = 0x09
	oLT             int = 0x0a
	oLE             int = 0x0b
	oGT             int = 0x0c
	oGE             int = 0x0d
	oEQ             int = 0x0e
	oNOT            int = 0x0f
	oMYADDRESS      int = 0x10
	oTXSENDER       int = 0x11
	oTXVALUE        int = 0x12
	oTXFEE          int = 0x13
	oTXDATAN        int = 0x14
	oTXDATA         int = 0x15
	oBLK_PREVHASH   int = 0x16
	oBLK_COINBASE   int = 0x17
	oBLK_TIMESTAMP  int = 0x18
	oBLK_NUMBER     int = 0x19
	oBLK_DIFFICULTY int = 0x1a
	oSHA256         int = 0x20
	oRIPEMD160      int = 0x21
	oECMUL          int = 0x22
	oECADD          int = 0x23
	oECSIGN         int = 0x24
	oECRECOVER      int = 0x25
	oECVALID        int = 0x26
	oPUSH           int = 0x30
	oPOP            int = 0x31
	oDUP            int = 0x32
	oDUPN           int = 0x33
	oSWAP           int = 0x34
	oSWAPN          int = 0x35
	oLOAD           int = 0x36
	oSTORE          int = 0x37
	oJMP            int = 0x40
	oJMPI           int = 0x41
	oIND            int = 0x42
	oEXTRO          int = 0x50
	oBALANCE        int = 0x51
	oMKTX           int = 0x60
	oSUICIDE        int = 0xff
)

type OpType int

const (
	tNorm = iota
	tData
	tExtro
	tCrypto
)

type TxCallback func(opType OpType) bool

// Simple push/pop stack mechanism
type Stack struct {
	data []string
}

func NewStack() *Stack {
	return &Stack{}
}
func (st *Stack) Pop() string {
	s := len(st.data)

	str := st.data[s-1]
	st.data = st.data[:s-1]

	return str
}

func (st *Stack) Popn() (*big.Int, *big.Int) {
	s := len(st.data)

	strs := st.data[s-2:]
	st.data = st.data[:s-2]

	return ethutil.Big(strs[0]), ethutil.Big(strs[1])
}

func (st *Stack) Push(d string) {
	st.data = append(st.data, d)
}
func (st *Stack) Print() {
	fmt.Println(st.data)
}

type Vm struct {
	// Stack
	stack *Stack
}

func NewVm() *Vm {
	return &Vm{
		stack: NewStack(),
	}
}

func (vm *Vm) ProcContract(tx *ethutil.Transaction,
	block *ethutil.Block, cb TxCallback) {
	// Instruction pointer
	pc := 0

	contract := block.GetContract(tx.Hash())
	if contract == nil {
		fmt.Println("Contract not found")
		return
	}

	Pow256 := ethutil.BigPow(2, 256)

	//fmt.Printf("#   op   arg\n")
out:
	for {
		// The base big int for all calculations. Use this for any results.
		base := new(big.Int)
		// XXX Should Instr return big int slice instead of string slice?
		// Get the next instruction from the contract
		//op, _, _ := Instr(contract.state.Get(string(Encode(uint32(pc)))))
		nb := ethutil.NumberToBytes(uint64(pc), 32)
		op, _, _ := ethutil.Instr(contract.State().Get(string(nb)))

		if !cb(0) {
			break
		}

		if Debug {
			//fmt.Printf("%-3d %-4d\n", pc, op)
		}

		switch op {
		case oADD:
			x, y := vm.stack.Popn()
			// (x + y) % 2 ** 256
			base.Add(x, y)
			base.Mod(base, Pow256)
			// Pop result back on the stack
			vm.stack.Push(base.String())
		case oSUB:
			x, y := vm.stack.Popn()
			// (x - y) % 2 ** 256
			base.Sub(x, y)
			base.Mod(base, Pow256)
			// Pop result back on the stack
			vm.stack.Push(base.String())
		case oMUL:
			x, y := vm.stack.Popn()
			// (x * y) % 2 ** 256
			base.Mul(x, y)
			base.Mod(base, Pow256)
			// Pop result back on the stack
			vm.stack.Push(base.String())
		case oDIV:
			x, y := vm.stack.Popn()
			// floor(x / y)
			base.Div(x, y)
			// Pop result back on the stack
			vm.stack.Push(base.String())
		case oSDIV:
			x, y := vm.stack.Popn()
			// n > 2**255
			if x.Cmp(Pow256) > 0 {
				x.Sub(Pow256, x)
			}
			if y.Cmp(Pow256) > 0 {
				y.Sub(Pow256, y)
			}
			z := new(big.Int)
			z.Div(x, y)
			if z.Cmp(Pow256) > 0 {
				z.Sub(Pow256, z)
			}
			// Push result on to the stack
			vm.stack.Push(z.String())
		case oMOD:
			x, y := vm.stack.Popn()
			base.Mod(x, y)
			vm.stack.Push(base.String())
		case oSMOD:
			x, y := vm.stack.Popn()
			// n > 2**255
			if x.Cmp(Pow256) > 0 {
				x.Sub(Pow256, x)
			}
			if y.Cmp(Pow256) > 0 {
				y.Sub(Pow256, y)
			}
			z := new(big.Int)
			z.Mod(x, y)
			if z.Cmp(Pow256) > 0 {
				z.Sub(Pow256, z)
			}
			// Push result on to the stack
			vm.stack.Push(z.String())
		case oEXP:
			x, y := vm.stack.Popn()
			base.Exp(x, y, Pow256)

			vm.stack.Push(base.String())
		case oNEG:
			base.Sub(Pow256, ethutil.Big(vm.stack.Pop()))
			vm.stack.Push(base.String())
		case oLT:
			x, y := vm.stack.Popn()
			// x < y
			if x.Cmp(y) < 0 {
				vm.stack.Push("1")
			} else {
				vm.stack.Push("0")
			}
		case oLE:
			x, y := vm.stack.Popn()
			// x <= y
			if x.Cmp(y) < 1 {
				vm.stack.Push("1")
			} else {
				vm.stack.Push("0")
			}
		case oGT:
			x, y := vm.stack.Popn()
			// x > y
			if x.Cmp(y) > 0 {
				vm.stack.Push("1")
			} else {
				vm.stack.Push("0")
			}
		case oGE:
			x, y := vm.stack.Popn()
			// x >= y
			if x.Cmp(y) > -1 {
				vm.stack.Push("1")
			} else {
				vm.stack.Push("0")
			}
		case oNOT:
			x, y := vm.stack.Popn()
			// x != y
			if x.Cmp(y) != 0 {
				vm.stack.Push("1")
			} else {
				vm.stack.Push("0")
			}
		case oMYADDRESS:
			vm.stack.Push(string(tx.Hash()))
		case oTXSENDER:
			vm.stack.Push(string(tx.Sender()))
		case oPUSH:
			// Get the next entry and pushes the value on the stack
			pc++
			vm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(pc), 32))))
		case oPOP:
			// Pop current value of the stack
			vm.stack.Pop()
		case oLOAD:
			// Load instruction X on the stack
			i, _ := strconv.Atoi(vm.stack.Pop())
			vm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(i), 32))))
		case oSTOP:
			break out
		}
		pc++
	}

	vm.stack.Print()
}