package proto

import (
	"github.com/google/uuid"
	"github.com/sandertv/gophertunnel/minecraft/protocol"
)

const (
	RecipeUnlockContextNone = iota
	RecipeUnlockContextAlwaysUnlocked
	RecipeUnlockContextPlayerInWater
	RecipeUnlockContextPlayerHasManyItems
)

// RecipeUnlockRequirement represents a requirement that must be met in order to unlock a recipe. This is used
// for both shaped and shapeless recipes.
type RecipeUnlockRequirement struct {
	// Context is the context in which the recipe is unlocked. This is one of the constants above.
	Context byte
	// Ingredients are the ingredients required to unlock the recipe and only used if Context is set to none.
	Ingredients []protocol.ItemDescriptorCount
}

func (x *RecipeUnlockRequirement) FromLatest(requirement protocol.RecipeUnlockRequirement) RecipeUnlockRequirement {
	x.Context = requirement.Context
	x.Ingredients = requirement.Ingredients
	return *x
}

func (x *RecipeUnlockRequirement) ToLatest() protocol.RecipeUnlockRequirement {
	return protocol.RecipeUnlockRequirement{
		Context:     x.Context,
		Ingredients: x.Ingredients,
	}
}

// Marshal ...
func (x *RecipeUnlockRequirement) Marshal(r protocol.IO) {
	r.Uint8(&x.Context)
	if x.Context == RecipeUnlockContextNone {
		protocol.FuncSlice(r, &x.Ingredients, r.ItemDescriptorCount)
	}
}

const (
	RecipeShapeless int32 = iota
	RecipeShaped
	RecipeFurnace
	RecipeFurnaceData
	RecipeMulti
	RecipeShulkerBox
	RecipeShapelessChemistry
	RecipeShapedChemistry
	RecipeSmithingTransform
	RecipeSmithingTrim
)

// Recipe represents a recipe that may be sent in a CraftingData packet to let the client know what recipes
// are available server-side.
type Recipe interface {
	// Marshal encodes the recipe data to its binary representation into buf.
	Marshal(w *Writer)
	// Unmarshal decodes a serialised recipe from Reader r into the recipe instance.
	Unmarshal(r *Reader)
}

func takePtr[T any](x T) *T {
	return &x
}

func RecipeToLatest(x Recipe) protocol.Recipe {
	switch x := x.(type) {
	case *ShapelessRecipe:
		return takePtr(x.ToLatest())
	case *ShulkerBoxRecipe:
		return takePtr(x.ToLatest())
	case *ShapelessChemistryRecipe:
		return takePtr(x.ToLatest())
	case *ShapedRecipe:
		return takePtr(x.ToLatest())
	case *ShapedChemistryRecipe:
		return takePtr(x.ToLatest())
	case *FurnaceRecipe:
		return takePtr(x.ToLatest())
	case *FurnaceDataRecipe:
		return takePtr(x.ToLatest())
	case *MultiRecipe:
		return takePtr(x.ToLatest())
	case *SmithingTransformRecipe:
		return takePtr(x.ToLatest())
	case *SmithingTrimRecipe:
		return takePtr(x.ToLatest())
	}
	panic("RecipeToLatest: unknown recipe type")
}

func RecipeFromLatest(x protocol.Recipe) Recipe {
	switch x := x.(type) {
	case *protocol.ShapelessRecipe:
		return (&ShapelessRecipe{}).FromLatest(*x)
	case *protocol.ShulkerBoxRecipe:
		return (&ShulkerBoxRecipe{}).FromLatest(*x)
	case *protocol.ShapelessChemistryRecipe:
		return (&ShapelessChemistryRecipe{}).FromLatest(*x)
	case *protocol.ShapedRecipe:
		return (&ShapedRecipe{}).FromLatest(*x)
	case *protocol.ShapedChemistryRecipe:
		return (&ShapedChemistryRecipe{}).FromLatest(*x)
	case *protocol.FurnaceRecipe:
		return (&FurnaceRecipe{}).FromLatest(*x)
	case *protocol.FurnaceDataRecipe:
		return (&FurnaceDataRecipe{}).FromLatest(*x)
	case *protocol.MultiRecipe:
		return (&MultiRecipe{}).FromLatest(*x)
	case *protocol.SmithingTransformRecipe:
		return (&SmithingTransformRecipe{}).FromLatest(*x)
	case *protocol.SmithingTrimRecipe:
		return (&SmithingTrimRecipe{}).FromLatest(*x)
	}
	panic("RecipeFromLatest: unknown recipe type")
}

// lookupRecipe looks up the Recipe for a recipe type. False is returned if not
// found.
func lookupRecipe(recipeType int32, x *Recipe) bool {
	switch recipeType {
	case RecipeShapeless:
		*x = &ShapelessRecipe{}
	case RecipeShaped:
		*x = &ShapedRecipe{}
	case RecipeFurnace:
		*x = &FurnaceRecipe{}
	case RecipeFurnaceData:
		*x = &FurnaceDataRecipe{}
	case RecipeMulti:
		*x = &MultiRecipe{}
	case RecipeShulkerBox:
		*x = &ShulkerBoxRecipe{}
	case RecipeShapelessChemistry:
		*x = &ShapelessChemistryRecipe{}
	case RecipeShapedChemistry:
		*x = &ShapedChemistryRecipe{}
	case RecipeSmithingTransform:
		*x = &SmithingTransformRecipe{}
	case RecipeSmithingTrim:
		*x = &SmithingTrimRecipe{}
	default:
		return false
	}
	return true
}

// lookupRecipeType looks up the recipe type for a Recipe. False is returned if
// none was found.
func lookupRecipeType(x Recipe, recipeType *int32) bool {
	switch x.(type) {
	case *ShapelessRecipe:
		*recipeType = RecipeShapeless
	case *ShapedRecipe:
		*recipeType = RecipeShaped
	case *FurnaceRecipe:
		*recipeType = RecipeFurnace
	case *FurnaceDataRecipe:
		*recipeType = RecipeFurnaceData
	case *MultiRecipe:
		*recipeType = RecipeMulti
	case *ShulkerBoxRecipe:
		*recipeType = RecipeShulkerBox
	case *ShapelessChemistryRecipe:
		*recipeType = RecipeShapelessChemistry
	case *ShapedChemistryRecipe:
		*recipeType = RecipeShapedChemistry
	case *SmithingTransformRecipe:
		*recipeType = RecipeSmithingTransform
	case *SmithingTrimRecipe:
		*recipeType = RecipeSmithingTrim
	default:
		return false
	}
	return true
}

// ShapelessRecipe is a recipe that has no particular shape. Its functionality is shared with the
// RecipeShulkerBox and RecipeShapelessChemistry types.
type ShapelessRecipe struct {
	// RecipeID is a unique ID of the recipe. This ID must be unique amongst all other types of recipes too,
	// but its functionality is not exactly known.
	RecipeID string
	// Input is a list of items that serve as the input of the shapeless recipe. These items are the items
	// required to craft the output.
	Input []protocol.ItemDescriptorCount
	// Output is a list of items that are created as a result of crafting the recipe.
	Output []protocol.ItemStack
	// UUID is a UUID identifying the recipe. Since the CraftingEvent packet no longer exists, this can always be empty.
	UUID uuid.UUID
	// Block is the block name that is required to craft the output of the recipe. The block is not prefixed
	// with 'minecraft:', so it will look like 'crafting_table' as an example.
	// The available blocks are:
	// - crafting_table
	// - cartography_table
	// - stonecutter
	// - furnace
	// - blast_furnace
	// - smoker
	// - campfire
	Block string
	// Priority ...
	Priority int32
	// UnlockRequirement is a requirement that must be met in order to unlock the recipe.
	UnlockRequirement RecipeUnlockRequirement
	// RecipeNetworkID is a unique ID used to identify the recipe over network. Each recipe must have a unique
	// network ID. Recommended is to just increment a variable for each unique recipe registered.
	// This field must never be 0.
	RecipeNetworkID uint32
}

// ShulkerBoxRecipe is a shapeless recipe made specifically for shulker box crafting, so that they don't lose
// their user data when dyeing a shulker box.
type ShulkerBoxRecipe struct {
	ShapelessRecipe
}

// ShapelessChemistryRecipe is a recipe specifically made for chemistry related features, which exist only in
// the Education Edition. They function the same as shapeless recipes do.
type ShapelessChemistryRecipe struct {
	ShapelessRecipe
}

// ShapedRecipe is a recipe that has a specific shape that must be used to craft the output of the recipe.
// Trying to craft the item in any other shape will not work. The ShapedRecipe is of the same structure as the
// ShapedChemistryRecipe.
type ShapedRecipe struct {
	// RecipeID is a unique ID of the recipe. This ID must be unique amongst all other types of recipes too,
	// but its functionality is not exactly known.
	RecipeID string
	// Width is the width of the recipe's shape.
	Width int32
	// Height is the height of the recipe's shape.
	Height int32
	// Input is a list of items that serve as the input of the shapeless recipe. These items are the items
	// required to craft the output. The amount of input items must be exactly equal to Width * Height.
	Input []protocol.ItemDescriptorCount
	// Output is a list of items that are created as a result of crafting the recipe.
	Output []protocol.ItemStack
	// UUID is a UUID identifying the recipe. Since the CraftingEvent packet no longer exists, this can always be empty.
	UUID uuid.UUID
	// Block is the block name that is required to craft the output of the recipe. The block is not prefixed
	// with 'minecraft:', so it will look like 'crafting_table' as an example.
	Block string
	// Priority ...
	Priority int32
	// AssumeSymmetry specifies if the recipe is symmetrical. If this is set to true, the recipe will be
	// mirrored along the diagonal axis. This means that the recipe will be the same if rotated 180 degrees.
	AssumeSymmetry bool
	// UnlockRequirement is a requirement that must be met in order to unlock the recipe.
	UnlockRequirement RecipeUnlockRequirement
	// RecipeNetworkID is a unique ID used to identify the recipe over network. Each recipe must have a unique
	// network ID. Recommended is to just increment a variable for each unique recipe registered.
	// This field must never be 0.
	RecipeNetworkID uint32
}

// ShapedChemistryRecipe is a recipe specifically made for chemistry related features, which exist only in the
// Education Edition. It functions the same as a normal ShapedRecipe.
type ShapedChemistryRecipe struct {
	ShapedRecipe
}

// FurnaceRecipe is a recipe that is specifically used for all kinds of furnaces. These recipes don't just
// apply to furnaces, but also blast furnaces and smokers.
type FurnaceRecipe struct {
	// InputType is the item type of the input item. The metadata value of the item is not used in the
	// FurnaceRecipe. Use FurnaceDataRecipe to allow an item with only one metadata value.
	InputType protocol.ItemType
	// Output is the item that is created as a result of smelting/cooking an item in the furnace.
	Output protocol.ItemStack
	// Block is the block name that is required to create the output of the recipe. The block is not prefixed
	// with 'minecraft:', so it will look like 'furnace' as an example.
	Block string
}

// FurnaceDataRecipe is a recipe specifically used for furnace-type crafting stations. It is equal to
// FurnaceRecipe, except it has an input item with a specific metadata value, instead of any metadata value.
type FurnaceDataRecipe struct {
	FurnaceRecipe
}

// MultiRecipe serves as an 'enable' switch for multi-shape recipes.
type MultiRecipe struct {
	// UUID is a UUID identifying the recipe. Since the CraftingEvent packet no longer exists, this can always be empty.
	UUID uuid.UUID
	// RecipeNetworkID is a unique ID used to identify the recipe over network. Each recipe must have a unique
	// network ID. Recommended is to just increment a variable for each unique recipe registered.
	// This field must never be 0.
	RecipeNetworkID uint32
}

// SmithingTransformRecipe is a recipe specifically used for smithing tables. It has three input items and adds them
// together, resulting in a new item.
type SmithingTransformRecipe struct {
	// RecipeNetworkID is a unique ID used to identify the recipe over network. Each recipe must have a unique
	// network ID. Recommended is to just increment a variable for each unique recipe registered.
	// This field must never be 0.
	RecipeNetworkID uint32
	// RecipeID is a unique ID of the recipe. This ID must be unique amongst all other types of recipes too,
	// but its functionality is not exactly known.
	RecipeID string
	// Template is the item that is used to shape the Base item based on the Addition being applied.
	Template protocol.ItemDescriptorCount
	// Base is the item that the Addition is being applied to in the smithing table.
	Base protocol.ItemDescriptorCount
	// Addition is the item that is being added to the Base item to result in a modified item.
	Addition protocol.ItemDescriptorCount
	// Result is the resulting item from the two items being added together.
	Result protocol.ItemStack
	// Block is the block name that is required to create the output of the recipe. The block is not prefixed with
	// 'minecraft:', so it will look like 'smithing_table' as an example.
	Block string
}

// SmithingTrimRecipe is a recipe specifically used for applying armour trims to an armour piece inside a smithing table.
type SmithingTrimRecipe struct {
	// RecipeNetworkID is a unique ID used to identify the recipe over network. Each recipe must have a unique
	// network ID. Recommended is to just increment a variable for each unique recipe registered.
	// This field must never be 0.
	RecipeNetworkID uint32
	// RecipeID is a unique ID of the recipe. This ID must be unique amongst all other types of recipes too,
	// but its functionality is not exactly known.
	RecipeID string
	// Template is the item that is used to shape the Base item based on the Addition being applied.
	Template protocol.ItemDescriptorCount
	// Base is the item that the Addition is being applied to in the smithing table.
	Base protocol.ItemDescriptorCount
	// Addition is the item that is being added to the Base item to result in a modified item.
	Addition protocol.ItemDescriptorCount
	// Block is the block name that is required to create the output of the recipe. The block is not prefixed with
	// 'minecraft:', so it will look like 'smithing_table' as an example.
	Block string
}

// FromLatest ...
func (recipe *ShapelessRecipe) FromLatest(v protocol.ShapelessRecipe) *ShapelessRecipe {
	recipe.RecipeID = v.RecipeID
	recipe.Input = v.Input
	recipe.Output = v.Output
	recipe.UUID = v.UUID
	recipe.Block = v.Block
	recipe.Priority = v.Priority
	recipe.UnlockRequirement = (&RecipeUnlockRequirement{}).FromLatest(v.UnlockRequirement)
	recipe.RecipeNetworkID = v.RecipeNetworkID
	return recipe
}

// ToLatest ...
func (recipe *ShapelessRecipe) ToLatest() protocol.ShapelessRecipe {
	return protocol.ShapelessRecipe{
		RecipeID:          recipe.RecipeID,
		Input:             recipe.Input,
		Output:            recipe.Output,
		UUID:              recipe.UUID,
		Block:             recipe.Block,
		Priority:          recipe.Priority,
		UnlockRequirement: recipe.UnlockRequirement.ToLatest(),
		RecipeNetworkID:   recipe.RecipeNetworkID,
	}
}

// Marshal ...
func (recipe *ShapelessRecipe) Marshal(w *Writer) {
	marshalShapeless(w, recipe)
}

// Unmarshal ...
func (recipe *ShapelessRecipe) Unmarshal(r *Reader) {
	marshalShapeless(r, recipe)
}

// FromLatest ...
func (recipe *ShulkerBoxRecipe) FromLatest(v protocol.ShulkerBoxRecipe) *ShulkerBoxRecipe {
	recipe.ShapelessRecipe = *(&ShapelessRecipe{}).FromLatest(v.ShapelessRecipe)
	return recipe
}

// ToLatest ...
func (recipe *ShulkerBoxRecipe) ToLatest() protocol.ShulkerBoxRecipe {
	return protocol.ShulkerBoxRecipe{
		ShapelessRecipe: recipe.ShapelessRecipe.ToLatest(),
	}
}

// Marshal ...
func (recipe *ShulkerBoxRecipe) Marshal(w *Writer) {
	marshalShapeless(w, &recipe.ShapelessRecipe)
}

// Unmarshal ...
func (recipe *ShulkerBoxRecipe) Unmarshal(r *Reader) {
	marshalShapeless(r, &recipe.ShapelessRecipe)
}

// FromLatest ...
func (recipe *ShapelessChemistryRecipe) FromLatest(v protocol.ShapelessChemistryRecipe) *ShapelessChemistryRecipe {
	recipe.ShapelessRecipe = *(&ShapelessRecipe{}).FromLatest(v.ShapelessRecipe)
	return recipe
}

// ToLatest ...
func (recipe *ShapelessChemistryRecipe) ToLatest() protocol.ShapelessChemistryRecipe {
	return protocol.ShapelessChemistryRecipe{
		ShapelessRecipe: recipe.ShapelessRecipe.ToLatest(),
	}
}

// Marshal ...
func (recipe *ShapelessChemistryRecipe) Marshal(w *Writer) {
	marshalShapeless(w, &recipe.ShapelessRecipe)
}

// Unmarshal ...
func (recipe *ShapelessChemistryRecipe) Unmarshal(r *Reader) {
	marshalShapeless(r, &recipe.ShapelessRecipe)
}

// FromLatest ...
func (recipe *ShapedRecipe) FromLatest(v protocol.ShapedRecipe) *ShapedRecipe {
	recipe.RecipeID = v.RecipeID
	recipe.Width = v.Width
	recipe.Height = v.Height
	recipe.Input = v.Input
	recipe.Output = v.Output
	recipe.UUID = v.UUID
	recipe.Block = v.Block
	recipe.AssumeSymmetry = v.AssumeSymmetry
	recipe.UnlockRequirement = (&RecipeUnlockRequirement{}).FromLatest(v.UnlockRequirement)
	recipe.Priority = v.Priority
	recipe.RecipeNetworkID = v.RecipeNetworkID
	return recipe
}

// ToLatest ...
func (recipe *ShapedRecipe) ToLatest() protocol.ShapedRecipe {
	return protocol.ShapedRecipe{
		RecipeID:          recipe.RecipeID,
		Width:             recipe.Width,
		Height:            recipe.Height,
		Input:             recipe.Input,
		Output:            recipe.Output,
		UUID:              recipe.UUID,
		Block:             recipe.Block,
		AssumeSymmetry:    recipe.AssumeSymmetry,
		UnlockRequirement: recipe.UnlockRequirement.ToLatest(),
		Priority:          recipe.Priority,
		RecipeNetworkID:   recipe.RecipeNetworkID,
	}
}

// Marshal ...
func (recipe *ShapedRecipe) Marshal(w *Writer) {
	marshalShaped(w, recipe)
}

// Unmarshal ...
func (recipe *ShapedRecipe) Unmarshal(r *Reader) {
	marshalShaped(r, recipe)
}

// FromLatest ...
func (recipe *ShapedChemistryRecipe) FromLatest(v protocol.ShapedChemistryRecipe) *ShapedChemistryRecipe {
	recipe.ShapedRecipe = *(&ShapedRecipe{}).FromLatest(v.ShapedRecipe)
	return recipe
}

// ToLatest ...
func (recipe *ShapedChemistryRecipe) ToLatest() protocol.ShapedChemistryRecipe {
	return protocol.ShapedChemistryRecipe{
		ShapedRecipe: recipe.ShapedRecipe.ToLatest(),
	}
}

// Marshal ...
func (recipe *ShapedChemistryRecipe) Marshal(w *Writer) {
	marshalShaped(w, &recipe.ShapedRecipe)
}

// Unmarshal ...
func (recipe *ShapedChemistryRecipe) Unmarshal(r *Reader) {
	marshalShaped(r, &recipe.ShapedRecipe)
}

// FromLatest ...
func (recipe *FurnaceRecipe) FromLatest(v protocol.FurnaceRecipe) *FurnaceRecipe {
	recipe.InputType = v.InputType
	recipe.Output = v.Output
	recipe.Block = v.Block
	return recipe
}

// ToLatest ...
func (recipe *FurnaceRecipe) ToLatest() protocol.FurnaceRecipe {
	return protocol.FurnaceRecipe{
		InputType: recipe.InputType,
		Output:    recipe.Output,
		Block:     recipe.Block,
	}
}

// Marshal ...
func (recipe *FurnaceRecipe) Marshal(w *Writer) {
	w.Varint32(&recipe.InputType.NetworkID)
	w.Item(&recipe.Output)
	w.String(&recipe.Block)
}

// Unmarshal ...
func (recipe *FurnaceRecipe) Unmarshal(r *Reader) {
	r.Varint32(&recipe.InputType.NetworkID)
	r.Item(&recipe.Output)
	r.String(&recipe.Block)
}

// FromLatest ...
func (recipe *FurnaceDataRecipe) FromLatest(v protocol.FurnaceDataRecipe) *FurnaceDataRecipe {
	recipe.FurnaceRecipe = *(&FurnaceRecipe{}).FromLatest(v.FurnaceRecipe)
	return recipe
}

// ToLatest ...
func (recipe *FurnaceDataRecipe) ToLatest() protocol.FurnaceDataRecipe {
	return protocol.FurnaceDataRecipe{
		FurnaceRecipe: recipe.FurnaceRecipe.ToLatest(),
	}
}

// Marshal ...
func (recipe *FurnaceDataRecipe) Marshal(w *Writer) {
	w.Varint32(&recipe.InputType.NetworkID)
	aux := int32(recipe.InputType.MetadataValue)
	w.Varint32(&aux)
	w.Item(&recipe.Output)
	w.String(&recipe.Block)
}

// Unmarshal ...
func (recipe *FurnaceDataRecipe) Unmarshal(r *Reader) {
	var dataValue int32
	r.Varint32(&recipe.InputType.NetworkID)
	r.Varint32(&dataValue)
	recipe.InputType.MetadataValue = uint32(dataValue)
	r.Item(&recipe.Output)
	r.String(&recipe.Block)
}

// FromLatest ...
func (recipe *MultiRecipe) FromLatest(v protocol.MultiRecipe) *MultiRecipe {
	recipe.UUID = v.UUID
	recipe.RecipeNetworkID = v.RecipeNetworkID
	return recipe
}

// ToLatest ...
func (recipe *MultiRecipe) ToLatest() protocol.MultiRecipe {
	return protocol.MultiRecipe{
		UUID:            recipe.UUID,
		RecipeNetworkID: recipe.RecipeNetworkID,
	}
}

// Marshal ...
func (recipe *MultiRecipe) Marshal(w *Writer) {
	w.UUID(&recipe.UUID)
	w.Varuint32(&recipe.RecipeNetworkID)
}

// Unmarshal ...
func (recipe *MultiRecipe) Unmarshal(r *Reader) {
	r.UUID(&recipe.UUID)
	r.Varuint32(&recipe.RecipeNetworkID)
}

// FromLatest ...
func (recipe *SmithingTransformRecipe) FromLatest(v protocol.SmithingTransformRecipe) *SmithingTransformRecipe {
	recipe.RecipeNetworkID = v.RecipeNetworkID
	recipe.RecipeID = v.RecipeID
	recipe.Template = v.Template
	recipe.Base = v.Base
	recipe.Addition = v.Addition
	recipe.Result = v.Result
	recipe.Block = v.Block
	return recipe
}

// ToLatest ...
func (recipe *SmithingTransformRecipe) ToLatest() protocol.SmithingTransformRecipe {
	return protocol.SmithingTransformRecipe{
		RecipeNetworkID: recipe.RecipeNetworkID,
		RecipeID:        recipe.RecipeID,
		Template:        recipe.Template,
		Base:            recipe.Base,
		Addition:        recipe.Addition,
		Result:          recipe.Result,
		Block:           recipe.Block,
	}
}

// Marshal ...
func (recipe *SmithingTransformRecipe) Marshal(w *Writer) {
	w.String(&recipe.RecipeID)
	w.ItemDescriptorCount(&recipe.Template)
	w.ItemDescriptorCount(&recipe.Base)
	w.ItemDescriptorCount(&recipe.Addition)
	w.Item(&recipe.Result)
	w.String(&recipe.Block)
	w.Varuint32(&recipe.RecipeNetworkID)
}

// Unmarshal ...
func (recipe *SmithingTransformRecipe) Unmarshal(r *Reader) {
	r.String(&recipe.RecipeID)
	r.ItemDescriptorCount(&recipe.Template)
	r.ItemDescriptorCount(&recipe.Base)
	r.ItemDescriptorCount(&recipe.Addition)
	r.Item(&recipe.Result)
	r.String(&recipe.Block)
	r.Varuint32(&recipe.RecipeNetworkID)
}

// FromLatest ...
func (recipe *SmithingTrimRecipe) FromLatest(v protocol.SmithingTrimRecipe) *SmithingTrimRecipe {
	recipe.RecipeNetworkID = v.RecipeNetworkID
	recipe.RecipeID = v.RecipeID
	recipe.Template = v.Template
	recipe.Base = v.Base
	recipe.Addition = v.Addition
	recipe.Block = v.Block
	return recipe
}

// ToLatest ...
func (recipe *SmithingTrimRecipe) ToLatest() protocol.SmithingTrimRecipe {
	return protocol.SmithingTrimRecipe{
		RecipeNetworkID: recipe.RecipeNetworkID,
		RecipeID:        recipe.RecipeID,
		Template:        recipe.Template,
		Base:            recipe.Base,
		Addition:        recipe.Addition,
		Block:           recipe.Block,
	}
}

// Marshal ...
func (recipe *SmithingTrimRecipe) Marshal(w *Writer) {
	w.String(&recipe.RecipeID)
	w.ItemDescriptorCount(&recipe.Template)
	w.ItemDescriptorCount(&recipe.Base)
	w.ItemDescriptorCount(&recipe.Addition)
	w.String(&recipe.Block)
	w.Varuint32(&recipe.RecipeNetworkID)
}

// Unmarshal ...
func (recipe *SmithingTrimRecipe) Unmarshal(r *Reader) {
	r.String(&recipe.RecipeID)
	r.ItemDescriptorCount(&recipe.Template)
	r.ItemDescriptorCount(&recipe.Base)
	r.ItemDescriptorCount(&recipe.Addition)
	r.String(&recipe.Block)
	r.Varuint32(&recipe.RecipeNetworkID)
}

// marshalShaped ...
func marshalShaped(r protocol.IO, recipe *ShapedRecipe) {
	r.String(&recipe.RecipeID)
	r.Varint32(&recipe.Width)
	r.Varint32(&recipe.Height)
	protocol.FuncSliceOfLen(r, uint32(recipe.Width*recipe.Height), &recipe.Input, r.ItemDescriptorCount)
	protocol.FuncSlice(r, &recipe.Output, r.Item)
	r.UUID(&recipe.UUID)
	r.String(&recipe.Block)
	r.Varint32(&recipe.Priority)
	if IsProtoGTE(r, ID671) {
		r.Bool(&recipe.AssumeSymmetry)
	}
	if IsProtoGTE(r, ID685) {
		protocol.Single(r, &recipe.UnlockRequirement)
	}
	r.Varuint32(&recipe.RecipeNetworkID)
}

// marshalShapeless ...
func marshalShapeless(r protocol.IO, recipe *ShapelessRecipe) {
	r.String(&recipe.RecipeID)
	protocol.FuncSlice(r, &recipe.Input, r.ItemDescriptorCount)
	protocol.FuncSlice(r, &recipe.Output, r.Item)
	r.UUID(&recipe.UUID)
	r.String(&recipe.Block)
	r.Varint32(&recipe.Priority)
	if IsProtoGTE(r, ID685) {
		protocol.Single(r, &recipe.UnlockRequirement)
	}
	r.Varuint32(&recipe.RecipeNetworkID)
}
