package tangle

import (
	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tipmanager"
	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
	"github.com/iotaledger/hive.go/events"
)

// ValueObjectFactory acts as a factory to create new value objects.
type ValueObjectFactory struct {
	tangle     *Tangle
	tipManager *tipmanager.TipManager
	Events     *ValueObjectFactoryEvents
}

// NewValueObjectFactory creates a new ValueObjectFactory.
func NewValueObjectFactory(tangle *Tangle, tipManager *tipmanager.TipManager) *ValueObjectFactory {
	return &ValueObjectFactory{
		tangle:     tangle,
		tipManager: tipManager,
		Events: &ValueObjectFactoryEvents{
			ValueObjectConstructed: events.NewEvent(valueObjectConstructedEvent),
		},
	}
}

// IssueTransaction creates a new value object including tip selection and returns it.
// It also triggers the ValueObjectConstructed event once it's done.
func (v *ValueObjectFactory) IssueTransaction(tx *transaction.Transaction) (valueObject *payload.Payload, err error) {
	parent1, parent2 := v.tipManager.Tips()

	// validate the transaction signature
	if !tx.SignaturesValid() {
		err = ErrInvalidTransactionSignature
		return
	}

	// check if the tx that is supposed to be issued is a double spend
	tx.Inputs().ForEach(func(outputId transaction.OutputID) bool {
		v.tangle.TransactionOutput(outputId).Consume(func(output *Output) {
			if output.ConsumerCount() >= 1 {
				err = ErrDoubleSpendForbidden
			}
		})

		return err == nil
	})
	if err != nil {
		return
	}

	valueObject = payload.New(parent1, parent2, tx)
	v.Events.ValueObjectConstructed.Trigger(valueObject)

	return
}

// ValueObjectFactoryEvents represent events happening on a ValueObjectFactory.
type ValueObjectFactoryEvents struct {
	// Fired when a value object is built including tips.
	ValueObjectConstructed *events.Event
}

func valueObjectConstructedEvent(handler interface{}, params ...interface{}) {
	handler.(func(*transaction.Transaction))(params[0].(*transaction.Transaction))
}