package wasm

import (
	"fmt"
	"strings"
)

// CoreFeatures is a bit flag of WebAssembly Core specification features. See
// https://github.com/WebAssembly/proposals for proposals and their status.
//
// Constants define individual features, such as CoreFeatureMultiValue, or
// groups of "finished" features, assigned to a WebAssembly Core Specification
// version, ex. CoreFeaturesV1 or CoreFeaturesV2.
//
// Note: Numeric values are not intended to be interpreted except as bit flags.
type CoreFeatures uint64

// CoreFeaturesV1 are features included in the WebAssembly Core Specification
// 1.0. As of late 2022, this is the only version that is a Web Standard (W3C
// Recommendation).
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/
const CoreFeaturesV1 = CoreFeatureMutableGlobal

// CoreFeaturesV2 are features included in the WebAssembly Core Specification
// 2.0 (20220419). As of late 2022, version 2.0 is a W3C working draft, not yet
// a Web Standard (W3C Recommendation).
//
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#release-1-1
const CoreFeaturesV2 = CoreFeaturesV1 |
	CoreFeatureBulkMemoryOperations |
	CoreFeatureMultiValue |
	CoreFeatureNonTrappingFloatToIntConversion |
	CoreFeatureReferenceTypes |
	CoreFeatureSignExtensionOps |
	CoreFeatureSIMD

const (
	// CoreFeatureBulkMemoryOperations adds instructions modify ranges of
	// memory or table entries ("bulk-memory-operations"). This is included in
	// CoreFeaturesV2, but not CoreFeaturesV1.
	//
	// Here are the notable effects:
	//   - Adds `memory.fill`, `memory.init`, `memory.copy` and `data.drop`
	//     instructions.
	//   - Adds `table.init`, `table.copy` and `elem.drop` instructions.
	//   - Introduces a "passive" form of element and data segments.
	//   - Stops checking "active" element and data segment boundaries at
	//     compile-time, meaning they can error at runtime.
	//
	// Note: "bulk-memory-operations" is mixed with the "reference-types"
	// proposal due to the WebAssembly Working Group merging them
	// "mutually dependent". Therefore, enabling this feature requires enabling
	// CoreFeatureReferenceTypes, and vice-versa.
	//
	// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md
	// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md and
	// https://github.com/WebAssembly/spec/pull/1287
	CoreFeatureBulkMemoryOperations CoreFeatures = 1 << iota

	// CoreFeatureMultiValue enables multiple values ("multi-value"). This is
	// included in CoreFeaturesV2, but not CoreFeaturesV1.
	//
	// Here are the notable effects:
	//   - Function (`func`) types allow more than one result.
	//   - Block types (`block`, `loop` and `if`) can be arbitrary function
	//     types.
	//
	// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md
	CoreFeatureMultiValue

	// CoreFeatureMutableGlobal allows globals to be mutable. This is included
	// in both CoreFeaturesV1 and CoreFeaturesV2.
	//
	// When false, an api.Global can never be cast to an api.MutableGlobal, and
	// any wasm that includes global vars will fail to parse.
	CoreFeatureMutableGlobal

	// CoreFeatureNonTrappingFloatToIntConversion enables non-trapping
	// float-to-int conversions ("nontrapping-float-to-int-conversion"). This
	// is included in CoreFeaturesV2, but not CoreFeaturesV1.
	//
	// The only effect of enabling is allowing the following instructions,
	// which return 0 on NaN instead of panicking.
	//   - `i32.trunc_sat_f32_s`
	//   - `i32.trunc_sat_f32_u`
	//   - `i32.trunc_sat_f64_s`
	//   - `i32.trunc_sat_f64_u`
	//   - `i64.trunc_sat_f32_s`
	//   - `i64.trunc_sat_f32_u`
	//   - `i64.trunc_sat_f64_s`
	//   - `i64.trunc_sat_f64_u`
	//
	// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md
	CoreFeatureNonTrappingFloatToIntConversion

	// CoreFeatureReferenceTypes enables various instructions and features
	// related to table and new reference types. This is included in
	// CoreFeaturesV2, but not CoreFeaturesV1.
	//
	//   - Introduction of new value types: `funcref` and `externref`.
	//   - Support for the following new instructions:
	//     - `ref.null`
	//     - `ref.func`
	//     - `ref.is_null`
	//     - `table.fill`
	//     - `table.get`
	//     - `table.grow`
	//     - `table.set`
	//     - `table.size`
	//   - Support for multiple tables per module:
	//     - `call_indirect`, `table.init`, `table.copy` and `elem.drop`
	//   - Support for instructions can take non-zero table index.
	//     - Element segments can take non-zero table index.
	//
	// Note: "reference-types" is mixed with the "bulk-memory-operations"
	// proposal due to the WebAssembly Working Group merging them
	// "mutually dependent". Therefore, enabling this feature requires enabling
	// CoreFeatureBulkMemoryOperations, and vice-versa.
	//
	// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md
	// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md and
	// https://github.com/WebAssembly/spec/pull/1287
	CoreFeatureReferenceTypes

	// CoreFeatureSignExtensionOps enables sign extension instructions
	// ("sign-extension-ops"). This is included in CoreFeaturesV2, but not
	// CoreFeaturesV1.
	//
	// Adds instructions:
	//   - `i32.extend8_s`
	//   - `i32.extend16_s`
	//   - `i64.extend8_s`
	//   - `i64.extend16_s`
	//   - `i64.extend32_s`
	//
	// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md
	CoreFeatureSignExtensionOps

	// CoreFeatureSIMD enables the vector value type and vector instructions
	// (aka SIMD). This is included in CoreFeaturesV2, but not CoreFeaturesV1.
	//
	// Note: The instruction list is too long to enumerate in godoc.
	// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md
	CoreFeatureSIMD
)

// SetEnabled enables or disables the feature or group of features.
func (f CoreFeatures) SetEnabled(feature CoreFeatures, val bool) CoreFeatures {
	if val {
		return f | feature
	}
	return f &^ feature
}

// IsEnabled returns true if the feature (or group of features) is enabled.
func (f CoreFeatures) IsEnabled(feature CoreFeatures) bool {
	return f&feature != 0
}

// RequireEnabled returns an error if the feature (or group of features) is not
// enabled.
func (f CoreFeatures) RequireEnabled(feature CoreFeatures) error {
	if f&feature == 0 {
		return fmt.Errorf("feature %q is disabled", feature)
	}
	return nil
}

// String implements fmt.Stringer by returning each enabled feature.
func (f CoreFeatures) String() string {
	var builder strings.Builder
	for i := 0; i <= 63; i++ { // cycle through all bits to reduce code and maintenance
		target := CoreFeatures(1 << i)
		if f.IsEnabled(target) {
			if name := featureName(target); name != "" {
				if builder.Len() > 0 {
					builder.WriteByte('|')
				}
				builder.WriteString(name)
			}
		}
	}
	return builder.String()
}

func featureName(f CoreFeatures) string {
	switch f {
	case CoreFeatureMutableGlobal:
		// match https://github.com/WebAssembly/mutable-global
		return "mutable-global"
	case CoreFeatureSignExtensionOps:
		// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md
		return "sign-extension-ops"
	case CoreFeatureMultiValue:
		// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md
		return "multi-value"
	case CoreFeatureNonTrappingFloatToIntConversion:
		// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md
		return "nontrapping-float-to-int-conversion"
	case CoreFeatureBulkMemoryOperations:
		// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md
		return "bulk-memory-operations"
	case CoreFeatureReferenceTypes:
		// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md
		return "reference-types"
	case CoreFeatureSIMD:
		// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md
		return "simd"
	}
	return ""
}
