package transaction

import (
	"encoding/json"
	"errors"
	"math/big"

	"git.marketally.com/tutus-one/tutus-chain/pkg/io"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

//go:generate stringer -type=WitnessAction -linecomment

// WitnessAction represents an action to perform in WitnessRule if
// WitnessCondition matches.
type WitnessAction byte

const (
	// WitnessDeny rejects current witness if condition is met.
	WitnessDeny WitnessAction = 0 // Deny
	// WitnessAllow approves current witness if condition is met.
	WitnessAllow WitnessAction = 1 // Allow
)

// WitnessRule represents a single rule for Rules witness scope.
type WitnessRule struct {
	Action    WitnessAction    `json:"action"`
	Condition WitnessCondition `json:"condition"`
}

type witnessRuleAux struct {
	Action    string          `json:"action"`
	Condition json.RawMessage `json:"condition"`
}

// EncodeBinary implements the Serializable interface.
func (w *WitnessRule) EncodeBinary(bw *io.BinWriter) {
	bw.WriteB(byte(w.Action))
	w.Condition.EncodeBinary(bw)
}

// DecodeBinary implements the Serializable interface.
func (w *WitnessRule) DecodeBinary(br *io.BinReader) {
	w.Action = WitnessAction(br.ReadB())
	if br.Err == nil && w.Action != WitnessDeny && w.Action != WitnessAllow {
		br.Err = errors.New("unknown witness rule action")
		return
	}
	w.Condition = DecodeBinaryCondition(br)
}

// MarshalJSON implements the json.Marshaler interface.
func (w *WitnessRule) MarshalJSON() ([]byte, error) {
	cond, err := w.Condition.MarshalJSON()
	if err != nil {
		return nil, err
	}
	aux := &witnessRuleAux{
		Action:    w.Action.String(),
		Condition: cond,
	}
	return json.Marshal(aux)
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (w *WitnessRule) UnmarshalJSON(data []byte) error {
	aux := &witnessRuleAux{}
	err := json.Unmarshal(data, aux)
	if err != nil {
		return err
	}
	var action WitnessAction
	switch aux.Action {
	case WitnessDeny.String():
		action = WitnessDeny
	case WitnessAllow.String():
		action = WitnessAllow
	default:
		return errors.New("unknown witness rule action")
	}
	cond, err := UnmarshalConditionJSON(aux.Condition)
	if err != nil {
		return err
	}
	w.Action = action
	w.Condition = cond
	return nil
}

// ToStackItem implements Convertible interface.
func (w *WitnessRule) ToStackItem() stackitem.Item {
	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(w.Action))),
		w.Condition.ToStackItem(),
	})
}

// Copy creates a deep copy of the WitnessRule.
func (w *WitnessRule) Copy() *WitnessRule {
	return &WitnessRule{
		Action:    w.Action,
		Condition: w.Condition.Copy(),
	}
}
