package goja

import (
	
	
	
	
	
)

type templatePropFactory func(*Runtime) Value

type objectTemplate struct {
	propNames []unistring.String
	props     map[unistring.String]templatePropFactory

	symProps     map[*Symbol]templatePropFactory
	symPropNames []*Symbol

	protoFactory func(*Runtime) *Object
}

type templatedObject struct {
	baseObject
	tmpl *objectTemplate

	protoMaterialised bool
}

type templatedFuncObject struct {
	templatedObject

	f         func(FunctionCall) Value
	construct func(args []Value, newTarget *Object) *Object
}

// This type exists because Array.prototype is supposed to be an array itself and I could not find
// a different way of implementing it without either introducing another layer of interfaces or hoisting
// the templates to baseObject both of which would have had a negative effect on the performance.
// The implementation is as simple as possible and is not optimised in any way, but I very much doubt anybody
// uses Array.prototype as an actual array.
type templatedArrayObject struct {
	templatedObject
}

func newObjectTemplate() *objectTemplate {
	return &objectTemplate{
		props: make(map[unistring.String]templatePropFactory),
	}
}

func ( *objectTemplate) ( unistring.String,  templatePropFactory) {
	.props[] = 
	.propNames = append(.propNames, )
}

func ( *objectTemplate) ( *Symbol,  templatePropFactory) {
	if .symProps == nil {
		.symProps = make(map[*Symbol]templatePropFactory)
	}
	.symProps[] = 
	.symPropNames = append(.symPropNames, )
}

func ( *Runtime) ( *objectTemplate,  *Object) *templatedObject {
	if  == nil {
		 = &Object{runtime: }
	}
	 := &templatedObject{
		baseObject: baseObject{
			class:      classObject,
			val:        ,
			extensible: true,
		},
		tmpl: ,
	}
	.self = 
	.init()
	return 
}

func ( *templatedObject) () {
	if !.protoMaterialised {
		if .tmpl.protoFactory != nil {
			.prototype = .tmpl.protoFactory(.val.runtime)
		}
		.protoMaterialised = true
	}
}

func ( *templatedObject) ( unistring.String,  Value) Value {
	 := .getOwnPropStr()
	if  == nil {
		.materialiseProto()
	}
	return .getStrWithOwnProp(, , )
}

func ( *templatedObject) ( *Symbol,  Value) Value {
	 := .getOwnPropSym()
	if  == nil {
		.materialiseProto()
	}
	return .getWithOwnProp(, , )
}

func ( *templatedObject) ( unistring.String) Value {
	if ,  := .values[];  {
		return 
	}
	if  := .tmpl.props[];  != nil {
		 := (.val.runtime)
		.values[] = 
		return 
	}
	return nil
}

func ( *templatedObject) () {
	if .symValues == nil {
		.symValues = newOrderedMap(nil)
		for ,  := range .tmpl.symPropNames {
			.symValues.set(, .tmpl.symProps[](.val.runtime))
		}
	}
}

func ( *templatedObject) ( *Symbol) Value {
	if .symValues == nil && .tmpl.symProps[] == nil {
		return nil
	}
	.materialiseSymbols()
	return .baseObject.getOwnPropSym()
}

func ( *templatedObject) () {
	if .propNames == nil {
		.propNames = append(([]unistring.String)(nil), .tmpl.propNames...)
	}
}

func ( *templatedObject) ( unistring.String,  Value,  bool) bool {
	 := .getOwnPropStr() // materialise property (in case it's an accessor)
	if  == nil {
		.materialiseProto()
		.materialisePropNames()
	}
	return .baseObject.setOwnStr(, , )
}

func ( *templatedObject) ( *Symbol,  Value,  bool) bool {
	.materialiseSymbols()
	.materialiseProto()
	return .baseObject.setOwnSym(, , )
}

func ( *templatedObject) ( unistring.String, ,  Value,  bool) (bool, bool) {
	 := .getOwnPropStr()
	if  == nil {
		.materialiseProto()
	}
	return ._setForeignStr(, , , , )
}

func ( *templatedObject) () *Object {
	.materialiseProto()
	return .prototype
}

func ( *templatedObject) ( *Object,  bool) bool {
	.protoMaterialised = true
	 := .baseObject.setProto(, )
	if  {
		.protoMaterialised = true
	}
	return 
}

func ( *templatedObject) ( valueInt, ,  Value,  bool) (bool, bool) {
	return .setForeignStr(.string(), , , )
}

func ( *templatedObject) ( *Symbol, ,  Value,  bool) (bool, bool) {
	.materialiseProto()
	.materialiseSymbols()
	return .baseObject.setForeignSym(, , , )
}

func ( *templatedObject) ( unistring.String) bool {
	if .val.self.hasOwnPropertyStr() {
		return true
	}
	.materialiseProto()
	if .prototype != nil {
		return .prototype.self.hasPropertyStr()
	}
	return false
}

func ( *templatedObject) ( *Symbol) bool {
	if .hasOwnPropertySym() {
		return true
	}
	.materialiseProto()
	if .prototype != nil {
		return .prototype.self.hasPropertySym()
	}
	return false
}

func ( *templatedObject) ( unistring.String) bool {
	if ,  := .values[];  {
		return  != nil
	}

	,  := .tmpl.props[]
	return 
}

func ( *templatedObject) ( *Symbol) bool {
	if .symValues != nil {
		return .symValues.has()
	}
	,  := .tmpl.symProps[]
	return 
}

func ( *templatedObject) ( unistring.String,  PropertyDescriptor,  bool) bool {
	 := .getOwnPropStr()
	if ,  := ._defineOwnProperty(, , , );  {
		.values[] = 
		if  == nil {
			.materialisePropNames()
			 := copyNamesIfNeeded(.propNames, 1)
			.propNames = append(, )
		}
		return true
	}
	return false
}

func ( *templatedObject) ( *Symbol,  PropertyDescriptor,  bool) bool {
	.materialiseSymbols()
	return .baseObject.defineOwnPropertySym(, , )
}

func ( *templatedObject) ( unistring.String,  bool) bool {
	if  := .getOwnPropStr();  != nil {
		if !.checkDelete(, , ) {
			return false
		}
		.materialisePropNames()
		._delete()
		if ,  := .tmpl.props[];  {
			.values[] = nil // white hole
		}
	}
	return true
}

func ( *templatedObject) ( *Symbol,  bool) bool {
	.materialiseSymbols()
	return .baseObject.deleteSym(, )
}

func ( *templatedObject) () {
	for ,  := range .tmpl.props {
		if ,  := .values[]; ! {
			.values[] = (.val.runtime)
		}
	}
	.materialisePropNames()
}

func ( *templatedObject) () iterNextFunc {
	.materialiseProps()
	return .baseObject.iterateStringKeys()
}

func ( *templatedObject) () iterNextFunc {
	.materialiseSymbols()
	return .baseObject.iterateSymbols()
}

func ( *templatedObject) ( bool,  []Value) []Value {
	if  {
		.materialisePropNames()
	} else {
		.materialiseProps()
	}
	return .baseObject.stringKeys(, )
}

func ( *templatedObject) ( bool,  []Value) []Value {
	.materialiseSymbols()
	return .baseObject.symbols(, )
}

func ( *templatedObject) ( bool,  []Value) []Value {
	return .symbols(, .stringKeys(, ))
}

func ( *Runtime) ( *objectTemplate,  *Object,  func(FunctionCall) Value,  func([]Value, *Object) *Object) *templatedFuncObject {
	if  == nil {
		 = &Object{runtime: }
	}
	 := &templatedFuncObject{
		templatedObject: templatedObject{
			baseObject: baseObject{
				class:      classFunction,
				val:        ,
				extensible: true,
			},
			tmpl: ,
		},
		f:         ,
		construct: ,
	}
	.self = 
	.init()
	return 
}

func ( *templatedFuncObject) () String {
	return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(.getStr("name", nil)).toString()))
}

func ( *templatedFuncObject) (*objectExportCtx) interface{} {
	return .f
}

func ( *templatedFuncObject) () (func(FunctionCall) Value, bool) {
	if .f != nil {
		return .f, true
	}
	return nil, false
}

func ( *templatedFuncObject) ( *vm,  int) {
	var  nativeFuncObject
	.f = .f
	.vmCall(, )
}

func ( *templatedFuncObject) () func( []Value,  *Object) *Object {
	return .construct
}

func ( *templatedFuncObject) () reflect.Type {
	return reflectTypeFunc
}

func ( *templatedFuncObject) () String {
	return stringFunction
}

func ( *templatedFuncObject) ( Value) bool {
	return hasInstance(.val, )
}

func ( *Runtime) ( *objectTemplate,  *Object) *templatedArrayObject {
	if  == nil {
		 = &Object{runtime: }
	}
	 := &templatedArrayObject{
		templatedObject: templatedObject{
			baseObject: baseObject{
				class:      classArray,
				val:        ,
				extensible: true,
			},
			tmpl: ,
		},
	}
	.self = 
	.init()
	return 
}

func ( *templatedArrayObject) () *valueProperty {
	,  := .getOwnPropStr("length").(*valueProperty)
	if  == nil {
		panic(.val.runtime.NewTypeError("missing length property"))
	}
	return 
}

func ( *templatedArrayObject) ( uint32) {
	 := .getLenProp()
	 := uint32(.value.ToInteger())
	if  >=  {
		.value = intToValue(int64() + 1)
	}
}

func ( *templatedArrayObject) ( uint32,  bool) bool {
	 := .getLenProp()
	 := uint32(.value.ToInteger())
	if  ==  {
		return true
	}
	if !.writable {
		.val.runtime.typeErrorResult(, "length is not writable")
		return false
	}
	 := true
	if  <  {
		.materialisePropNames()
		.fixPropOrder()
		 := sort.Search(.idxPropCount, func( int) bool {
			return strToArrayIdx(.propNames[]) >= 
		})
		for  := .idxPropCount - 1;  >= ; -- {
			if !.deleteStr(.propNames[], false) {
				 = strToArrayIdx(.propNames[]) + 1
				 = false
				break
			}
		}
	}
	.value = intToValue(int64())
	return 
}

func ( *templatedArrayObject) ( unistring.String,  Value,  bool) bool {
	if  == "length" {
		return .setLength(.val.runtime.toLengthUint32(), )
	}
	if !.templatedObject.setOwnStr(, , ) {
		return false
	}
	if  := strToArrayIdx();  != math.MaxUint32 {
		._setOwnIdx()
	}
	return true
}

func ( *templatedArrayObject) ( valueInt,  Value,  bool) bool {
	if !.templatedObject.setOwnStr(.string(), , ) {
		return false
	}
	if  := toIdx();  != math.MaxUint32 {
		._setOwnIdx()
	}
	return true
}

func ( *templatedArrayObject) ( unistring.String,  PropertyDescriptor,  bool) bool {
	if  == "length" {
		return .val.runtime.defineArrayLength(.getLenProp(), , .setLength, )
	}
	if !.templatedObject.defineOwnPropertyStr(, , ) {
		return false
	}
	if  := strToArrayIdx();  != math.MaxUint32 {
		._setOwnIdx()
	}
	return true
}

func ( *templatedArrayObject) ( valueInt,  PropertyDescriptor,  bool) bool {
	if !.templatedObject.defineOwnPropertyStr(.string(), , ) {
		return false
	}
	if  := toIdx();  != math.MaxUint32 {
		._setOwnIdx()
	}
	return true
}