package tangle

import (
	"github.com/iotaledger/hive.go/marshalutil"
	"github.com/iotaledger/hive.go/objectstorage"
	"github.com/iotaledger/hive.go/stringify"

	"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
)

// region Approver /////////////////////////////////////////////////////////////////////////////////////////////////////

type Approver struct {
	objectstorage.StorableObjectFlags

	referencedMessageId message.Id
	approvingMessageId  message.Id
}

func NewApprover(referencedTransaction message.Id, approvingTransaction message.Id) *Approver {
	approver := &Approver{
		referencedMessageId: referencedTransaction,
		approvingMessageId:  approvingTransaction,
	}

	return approver
}

func ApproverFromBytes(bytes []byte, optionalTargetObject ...*Approver) (result *Approver, err error, consumedBytes int) {
	marshalUtil := marshalutil.New(bytes)
	result, err = ParseApprover(marshalUtil, optionalTargetObject...)
	consumedBytes = marshalUtil.ReadOffset()

	return
}

func ParseApprover(marshalUtil *marshalutil.MarshalUtil, optionalTargetObject ...*Approver) (result *Approver, err error) {
	if parsedObject, parseErr := marshalUtil.Parse(func(data []byte) (interface{}, error, int) {
		return ApproverFromStorageKey(data, optionalTargetObject...)
	}); parseErr != nil {
		err = parseErr

		return
	} else {
		result = parsedObject.(*Approver)
	}

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

		return
	}); err != nil {
		return
	}

	return
}

func ApproverFromStorageKey(key []byte, optionalTargetObject ...*Approver) (result objectstorage.StorableObject, err error, consumedBytes int) {
	// determine the target object that will hold the unmarshaled information
	switch len(optionalTargetObject) {
	case 0:
		result = &Approver{}
	case 1:
		result = optionalTargetObject[0]
	default:
		panic("too many arguments in call to ApproverFromStorageKey")
	}

	// parse the properties that are stored in the key
	marshalUtil := marshalutil.New(key)
	result.(*Approver).referencedMessageId, err = message.ParseId(marshalUtil)
	if err != nil {
		return
	}
	result.(*Approver).approvingMessageId, err = message.ParseId(marshalUtil)
	if err != nil {
		return
	}
	consumedBytes = marshalUtil.ReadOffset()

	return
}

func (approver *Approver) ReferencedMessageId() message.Id {
	return approver.approvingMessageId
}

func (approver *Approver) ApprovingMessageId() message.Id {
	return approver.approvingMessageId
}

func (approver *Approver) Bytes() []byte {
	return approver.ObjectStorageKey()
}

func (approver *Approver) String() string {
	return stringify.Struct("Approver",
		stringify.StructField("referencedMessageId", approver.ReferencedMessageId()),
		stringify.StructField("approvingMessageId", approver.ApprovingMessageId()),
	)
}

func (approver *Approver) ObjectStorageKey() []byte {
	return marshalutil.New().
		WriteBytes(approver.referencedMessageId.Bytes()).
		WriteBytes(approver.approvingMessageId.Bytes()).
		Bytes()
}

func (approver *Approver) ObjectStorageValue() (result []byte) {
	return
}

func (approver *Approver) UnmarshalObjectStorageValue(data []byte) (err error, consumedBytes int) {
	return
}

func (approver *Approver) Update(other objectstorage.StorableObject) {
	panic("approvers should never be overwritten and only stored once to optimize IO")
}

// interface contract (allow the compiler to check if the implementation has all of the required methods).
var _ objectstorage.StorableObject = &Approver{}

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

// region CachedApprover ///////////////////////////////////////////////////////////////////////////////////////////////

type CachedApprover struct {
	objectstorage.CachedObject
}

func (cachedApprover *CachedApprover) Unwrap() *Approver {
	if untypedObject := cachedApprover.Get(); untypedObject == nil {
		return nil
	} else {
		if typedObject := untypedObject.(*Approver); typedObject == nil || typedObject.IsDeleted() {
			return nil
		} else {
			return typedObject
		}
	}
}

func (cachedApprover *CachedApprover) Consume(consumer func(approver *Approver)) (consumed bool) {
	return cachedApprover.CachedObject.Consume(func(object objectstorage.StorableObject) {
		consumer(object.(*Approver))
	})
}

type CachedApprovers []*CachedApprover

func (cachedApprovers CachedApprovers) Consume(consumer func(approver *Approver)) (consumed bool) {
	for _, cachedApprover := range cachedApprovers {
		consumed = cachedApprover.Consume(func(approver *Approver) {
			consumer(approver)
		}) || consumed
	}

	return
}

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