// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package btcchain

import (
	"fmt"
	"github.com/conformal/btcscript"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
	"math"
)

// txValidate is used to track results of validating scripts for each
// transaction input index.
type txValidate struct {
	txIndex int
	err     error
}

// validateTxIn validates a the script pair for the passed spending transaction
// (along with the specific input index) and origin transaction (with the
// specific output index).
func validateTxIn(txInIdx int, txin *btcwire.TxIn, tx *btcutil.Tx, originTx *btcutil.Tx, flags btcscript.ScriptFlags) error {
	// If the input transaction has no previous input, there is nothing
	// to check.
	originTxIdx := txin.PreviousOutpoint.Index
	if originTxIdx == math.MaxUint32 {
		return nil
	}

	if originTxIdx >= uint32(len(originTx.MsgTx().TxOut)) {
		originTxSha := &txin.PreviousOutpoint.Hash
		log.Warnf("unable to locate source tx %v spending tx %v",
			originTxSha, tx.Sha())
		return fmt.Errorf("invalid index %x", originTxIdx)
	}

	sigScript := txin.SignatureScript
	pkScript := originTx.MsgTx().TxOut[originTxIdx].PkScript
	engine, err := btcscript.NewScript(sigScript, pkScript, txInIdx,
		tx.MsgTx(), flags)
	if err != nil {
		return err
	}

	err = engine.Execute()
	if err != nil {
		log.Warnf("validate of input %v failed: %v", txInIdx, err)
		return err
	}

	return nil
}

// ValidateTransactionScripts validates the scripts for the passed transaction
// using multiple goroutines.
func ValidateTransactionScripts(tx *btcutil.Tx, txStore TxStore, flags btcscript.ScriptFlags) (err error) {
	c := make(chan txValidate)
	job := tx.MsgTx().TxIn
	resultErrors := make([]error, len(job))

	var currentItem int
	var completedItems int

	processFunc := func(txInIdx int) {
		log.Tracef("validating tx %v input %v len %v",
			tx.Sha(), currentItem, len(job))
		txin := job[txInIdx]
		originTxSha := &txin.PreviousOutpoint.Hash
		origintxidx := txin.PreviousOutpoint.Index

		var originTx *btcutil.Tx
		if origintxidx != math.MaxUint32 {
			txInfo, ok := txStore[*originTxSha]
			if !ok {
				//wtf?
				fmt.Printf("obj not found in txStore %v",
					originTxSha)
			}
			originTx = txInfo.Tx
		}
		err := validateTxIn(txInIdx, txin, tx, originTx, flags)
		r := txValidate{txInIdx, err}
		c <- r
	}
	for currentItem = 0; currentItem < len(job) && currentItem < 16; currentItem++ {
		go processFunc(currentItem)
	}
	for completedItems < len(job) {
		select {
		case result := <-c:
			completedItems++
			resultErrors[result.txIndex] = result.err
			// would be nice to determine if we could stop
			// on early errors here instead of running more.
			if err == nil {
				err = result.err
			}

			if currentItem < len(job) {
				go processFunc(currentItem)
				currentItem++
			}
		}
	}
	for i := 0; i < len(job); i++ {
		if resultErrors[i] != nil {
			log.Warnf("tx %v failed input %v, err %v", tx.Sha(), i,
				resultErrors[i])
		}
	}
	return
}

// checkBlockScripts executes and validates the scripts for all transactions in
// the passed block.
func checkBlockScripts(block *btcutil.Block, txStore TxStore) error {
	// Setup the script validation flags.  Blocks created after the BIP0016
	// activation time need to have the pay-to-script-hash checks enabled.
	var flags btcscript.ScriptFlags
	if block.MsgBlock().Header.Timestamp.After(btcscript.Bip16Activation) {
		flags |= btcscript.ScriptBip16
	}

	txList := block.Transactions()
	c := make(chan txValidate)
	resultErrors := make([]error, len(txList))

	var currentItem int
	var completedItems int
	processFunc := func(txIdx int) {
		err := ValidateTransactionScripts(txList[txIdx], txStore, flags)
		r := txValidate{txIdx, err}
		c <- r
	}
	for currentItem = 0; currentItem < len(txList) && currentItem < 8; currentItem++ {
		go processFunc(currentItem)
	}
	for completedItems < len(txList) {
		select {
		case result := <-c:
			completedItems++
			resultErrors[result.txIndex] = result.err
			// would be nice to determine if we could stop
			// on early errors here instead of running more.

			if currentItem < len(txList) {
				go processFunc(currentItem)
				currentItem++
			}
		}
	}
	for i := 0; i < len(txList); i++ {
		if resultErrors[i] != nil {
			return resultErrors[i]
		}
	}

	return nil
}