//GohanModelFetch fetch gohan resource and running extensions
func GohanModelFetch(context map[string]interface{}, schemaID string, resourceID string,
	tenantIDs []string) (interface{}, error) {

	currentSchema, err := getSchema(schemaID)
	if err != nil {
		return nil, err
	}
	context["schema"] = currentSchema
	context["path"] = currentSchema.GetPluralURL()

	if err := resources.GetSingleResourceInTransaction(
		context, currentSchema, resourceID, tenantIDs); err != nil {
		return nil, err
	}
	response, ok := context["response"].(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("No response")
	}
	return response[currentSchema.Singular], nil
}
func init() {
	gohanChainingInit := func(env *Environment) {
		vm := env.VM
		builtins := map[string]interface{}{
			"gohan_model_list": func(call otto.FunctionCall) otto.Value {
				VerifyCallArguments(&call, "gohan_model_list", 3)
				rawContext, _ := call.Argument(0).Export()
				context, ok := rawContext.(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, noContextMessage)
				}
				schemaID := call.Argument(1).String()
				manager := schema.GetManager()
				currentSchema, ok := manager.Schema(schemaID)
				if !ok {
					ThrowOttoException(&call, unknownSchemaErrorMesssageFormat, schemaID)
				}
				context["schema"] = currentSchema
				context["path"] = currentSchema.GetPluralURL()
				filterObj, _ := call.Argument(2).Export()
				filter := map[string]interface{}{}
				if filterObj != nil {
					filterMap := filterObj.(map[string]interface{})
					for key, value := range filterMap {
						switch value := value.(type) {
						default:
							ThrowOttoException(&call, "Filter not a string nor array of strings")
						case string:
							filter[key] = value
						case []interface{}:
							for _, val := range value {
								v, ok := val.(string)
								if !ok {
									ThrowOttoException(&call, "Filter not a string nor array of strings")
								}
								filter[key] = v
							}
						}
					}
				}
				if err := resources.GetResourcesInTransaction(
					context, currentSchema, filter, nil); err != nil {
					handleChainError(env, &call, err)
				}
				response, ok := context["response"].(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, "No response")
				}
				resources, ok := response[currentSchema.Plural]
				if !ok {
					ThrowOttoException(&call, wrongResponseErrorMessageFormat, "GetMultipleResources.")
				}
				value, _ := vm.ToValue(resources)
				return value
			},
			"gohan_model_fetch": func(call otto.FunctionCall) otto.Value {
				VerifyCallArguments(&call, "gohan_model_fetch", 4)
				rawContext, _ := call.Argument(0).Export()
				context, ok := rawContext.(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, noContextMessage)
				}
				schemaID := call.Argument(1).String()
				manager := schema.GetManager()
				currentSchema, ok := manager.Schema(schemaID)
				if !ok {
					ThrowOttoException(&call, unknownSchemaErrorMesssageFormat, schemaID)
				}
				context["schema"] = currentSchema
				context["path"] = currentSchema.GetPluralURL()
				resourceID := call.Argument(2).String()
				rawTenantIDs, _ := call.Argument(3).Export()
				tenantIDs, ok := rawTenantIDs.([]string)
				if !ok {
					tenantIDs = nil
				}
				if err := resources.GetSingleResourceInTransaction(
					context, currentSchema, resourceID, tenantIDs); err != nil {
					handleChainError(env, &call, err)
				}
				response, ok := context["response"].(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, "No response")
				}
				resource := response[currentSchema.Singular]
				value, _ := vm.ToValue(resource)
				return value
			},
			"gohan_model_create": func(call otto.FunctionCall) otto.Value {
				VerifyCallArguments(&call, "gohan_model_create", 3)
				rawContext, _ := call.Argument(0).Export()
				context, ok := rawContext.(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, noContextMessage)
				}
				schemaID := call.Argument(1).String()
				manager := schema.GetManager()
				currentSchema, ok := manager.Schema(schemaID)
				if !ok {
					ThrowOttoException(&call, unknownSchemaErrorMesssageFormat, schemaID)
				}
				context["schema"] = currentSchema
				context["path"] = currentSchema.GetPluralURL()
				data, _ := call.Argument(2).Export()
				dataMap, ok := data.(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, notADictionaryErrorMessageFormat, dataMap)
				}
				resourceObj, err := manager.LoadResource(currentSchema.ID, dataMap)
				if err != nil {
					handleChainError(env, &call, err)
				}
				if err := resources.CreateResourceInTransaction(
					context, resourceObj); err != nil {
					handleChainError(env, &call, err)
				}
				response, ok := context["response"].(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, "No response")
				}
				resource := response[currentSchema.Singular]
				value, _ := vm.ToValue(resource)
				return value
			},
			"gohan_model_update": func(call otto.FunctionCall) otto.Value {
				VerifyCallArguments(&call, "gohan_model_update", 5)
				rawContext, _ := call.Argument(0).Export()
				context, ok := rawContext.(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, noContextMessage)
				}
				schemaID := call.Argument(1).String()
				manager := schema.GetManager()
				currentSchema, ok := manager.Schema(schemaID)
				if !ok {
					ThrowOttoException(&call, unknownSchemaErrorMesssageFormat, schemaID)
				}
				context["schema"] = currentSchema
				context["path"] = currentSchema.GetPluralURL()
				resourceID := call.Argument(2).String()
				data, _ := call.Argument(3).Export()

				rawTenantIDs, _ := call.Argument(4).Export()
				tenantIDs, ok := rawTenantIDs.([]string)
				if !ok {
					tenantIDs = nil
				}

				dataMap, ok := data.(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, notADictionaryErrorMessageFormat, dataMap)
				}
				err := resources.UpdateResourceInTransaction(context, currentSchema, resourceID, dataMap, tenantIDs)
				if err != nil {
					handleChainError(env, &call, err)
				}
				response, ok := context["response"].(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, "No response")
				}
				resource := response[currentSchema.Singular]
				value, _ := vm.ToValue(resource)
				return value
			},
			"gohan_model_delete": func(call otto.FunctionCall) otto.Value {
				VerifyCallArguments(&call, "gohan_model_delete", 3)
				rawContext, _ := call.Argument(0).Export()
				context, ok := rawContext.(map[string]interface{})
				if !ok {
					ThrowOttoException(&call, noContextMessage)
				}
				schemaID := call.Argument(1).String()
				manager := schema.GetManager()
				currentSchema, ok := manager.Schema(schemaID)
				if !ok {
					ThrowOttoException(&call, unknownSchemaErrorMesssageFormat, schemaID)
				}
				context["schema"] = currentSchema
				context["path"] = currentSchema.GetPluralURL()
				resourceID := call.Argument(2).String()
				err := resources.DeleteResourceInTransaction(context, currentSchema, resourceID)
				if err != nil {
					handleChainError(env, &call, err)
				}
				return otto.Value{}
			},
		}

		for name, object := range builtins {
			vm.Set(name, object)
		}
	}
	RegistInit(gohanChainingInit)
}