package tangle

import (
	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
	"github.com/iotaledger/hive.go/marshalutil"
	"github.com/iotaledger/hive.go/objectstorage"
	"github.com/iotaledger/hive.go/stringify"
)

// ConsumerPartitionKeys defines the "layout" of the key. This enables prefix iterations in the objectstorage.
var ConsumerPartitionKeys = objectstorage.PartitionKey([]int{address.Length, transaction.IDLength, transaction.IDLength}...)

// Consumer stores the information which transaction output was consumed by which transaction. We need this to be able
// to perform reverse lookups from transaction outputs to their corresponding consuming transactions.
type Consumer struct {
	objectstorage.StorableObjectFlags

	consumedInput transaction.OutputID
	transactionID transaction.ID

	storageKey []byte
}

// NewConsumer creates a Consumer object with the given information.
func NewConsumer(consumedInput transaction.OutputID, transactionID transaction.ID) *Consumer {
	return &Consumer{
		consumedInput: consumedInput,
		transactionID: transactionID,

		storageKey: marshalutil.New(ConsumerLength).
			WriteBytes(consumedInput.Bytes()).
			WriteBytes(transactionID.Bytes()).
			Bytes(),
	}
}

// ConsumerFromBytes unmarshals a Consumer from a sequence of bytes - it either creates a new object or fills the
// optionally provided one with the parsed information.
func ConsumerFromBytes(bytes []byte, optionalTargetObject ...*Consumer) (result *Consumer, consumedBytes int, err error) {
	marshalUtil := marshalutil.New(bytes)
	result, err = ParseConsumer(marshalUtil, optionalTargetObject...)
	consumedBytes = marshalUtil.ReadOffset()

	return
}

// ParseConsumer unmarshals a Consumer using the given marshalUtil (for easier marshaling/unmarshaling).
func ParseConsumer(marshalUtil *marshalutil.MarshalUtil, optionalTargetObject ...*Consumer) (result *Consumer, err error) {
	parsedObject, parseErr := marshalUtil.Parse(func(data []byte) (interface{}, int, error) {
		return ConsumerFromStorageKey(data, optionalTargetObject...)
	})
	if parseErr != nil {
		err = parseErr

		return
	}

	result = parsedObject.(*Consumer)
	_, err = marshalUtil.Parse(func(data []byte) (parseResult interface{}, parsedBytes int, parseErr error) {
		parsedBytes, parseErr = result.UnmarshalObjectStorageValue(data)

		return
	})

	return
}

// ConsumerFromStorageKey is a factory method that creates a new Consumer instance from a storage key of the
// objectstorage. It is used by the objectstorage, to create new instances of this entity.
func ConsumerFromStorageKey(key []byte, optionalTargetObject ...*Consumer) (result *Consumer, consumedBytes int, err error) {
	// determine the target object that will hold the unmarshaled information
	switch len(optionalTargetObject) {
	case 0:
		result = &Consumer{}
	case 1:
		result = optionalTargetObject[0]
	default:
		panic("too many arguments in call to ConsumerFromStorageKey")
	}

	// parse the properties that are stored in the key
	marshalUtil := marshalutil.New(key)
	if result.consumedInput, err = transaction.ParseOutputID(marshalUtil); err != nil {
		return
	}
	if result.transactionID, err = transaction.ParseID(marshalUtil); err != nil {
		return
	}
	consumedBytes = marshalUtil.ReadOffset()
	result.storageKey = marshalutil.New(key[:consumedBytes]).Bytes(true)

	return
}

// ConsumedInput returns the OutputID of the Consumer.
func (consumer *Consumer) ConsumedInput() transaction.OutputID {
	return consumer.consumedInput
}

// TransactionID returns the transaction ID of this Consumer.
func (consumer *Consumer) TransactionID() transaction.ID {
	return consumer.transactionID
}

// Bytes marshals the Consumer into a sequence of bytes.
func (consumer *Consumer) Bytes() []byte {
	return consumer.ObjectStorageKey()
}

// String returns a human readable version of the Consumer.
func (consumer *Consumer) String() string {
	return stringify.Struct("Consumer",
		stringify.StructField("consumedInput", consumer.ConsumedInput()),
		stringify.StructField("transactionId", consumer.TransactionID()),
	)
}

// ObjectStorageKey returns the key that is used to store the object in the database.
func (consumer *Consumer) ObjectStorageKey() []byte {
	return consumer.storageKey
}

// ObjectStorageValue marshals the "content part" of an Consumer to a sequence of bytes. Since all of the information for
// this object are stored in its key, this method does nothing and is only required to conform with the interface.
func (consumer *Consumer) ObjectStorageValue() (data []byte) {
	return
}

// UnmarshalObjectStorageValue unmarshals the "content part" of a Consumer from a sequence of bytes. Since all of the information
// for this object are stored in its key, this method does nothing and is only required to conform with the interface.
func (consumer *Consumer) UnmarshalObjectStorageValue(data []byte) (consumedBytes int, err error) {
	return
}

// Update is disabled - updates are supposed to happen through the setters (if existing).
func (consumer *Consumer) Update(other objectstorage.StorableObject) {
	panic("update forbidden")
}

// Interface contract: make compiler warn if the interface is not implemented correctly.
var _ objectstorage.StorableObject = &Consumer{}

// ConsumerLength holds the length of a marshaled Consumer in bytes.
const ConsumerLength = transaction.OutputIDLength + transaction.IDLength

// region CachedConsumer /////////////////////////////////////////////////////////////////////////////////////////////////

// CachedConsumer is a wrapper for the generic CachedObject returned by the objectstorage, that overrides the accessor
// methods, with a type-casted one.
type CachedConsumer struct {
	objectstorage.CachedObject
}

// Unwrap is the type-casted equivalent of Get. It returns nil if the object does not exist.
func (cachedConsumer *CachedConsumer) Unwrap() *Consumer {
	untypedObject := cachedConsumer.Get()
	if untypedObject == nil {
		return nil
	}

	typedObject := untypedObject.(*Consumer)
	if typedObject == nil || typedObject.IsDeleted() {
		return nil
	}

	return typedObject
}

// Consume unwraps the CachedObject and passes a type-casted version to the consumer (if the object is not empty - it
// exists). It automatically releases the object when the consumer finishes.
func (cachedConsumer *CachedConsumer) Consume(consumer func(consumer *Consumer)) (consumed bool) {
	return cachedConsumer.CachedObject.Consume(func(object objectstorage.StorableObject) {
		consumer(object.(*Consumer))
	})
}

// CachedConsumers represents a collection of CachedConsumers.
type CachedConsumers []*CachedConsumer

// Consume iterates over the CachedObjects, unwraps them and passes a type-casted version to the consumer (if the object
// is not empty - it exists). It automatically releases the object when the consumer finishes. It returns true, if at
// least one object was consumed.
func (cachedConsumers CachedConsumers) Consume(consumer func(consumer *Consumer)) (consumed bool) {
	for _, cachedConsumer := range cachedConsumers {
		consumed = cachedConsumer.Consume(func(output *Consumer) {
			consumer(output)
		}) || consumed
	}

	return
}

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////