package config

import (
	"fmt"
	"io/ioutil"

	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/hcl"
	"github.com/hashicorp/hcl/hcl/ast"
	"github.com/mitchellh/mapstructure"
)

// hclConfigurable is an implementation of configurable that knows
// how to turn HCL configuration into a *Config object.
type hclConfigurable struct {
	File string
	Root *ast.File
}

var ReservedDataSourceFields = []string{
	"connection",
	"count",
	"depends_on",
	"lifecycle",
	"provider",
	"provisioner",
}

var ReservedResourceFields = []string{
	"connection",
	"count",
	"depends_on",
	"id",
	"lifecycle",
	"provider",
	"provisioner",
}

var ReservedProviderFields = []string{
	"alias",
	"version",
}

func (t *hclConfigurable) Config() (*Config, error) {
	validKeys := map[string]struct{}{
		"atlas":     struct{}{},
		"data":      struct{}{},
		"locals":    struct{}{},
		"module":    struct{}{},
		"output":    struct{}{},
		"provider":  struct{}{},
		"resource":  struct{}{},
		"terraform": struct{}{},
		"variable":  struct{}{},
	}

	// Top-level item should be the object list
	list, ok := t.Root.Node.(*ast.ObjectList)
	if !ok {
		return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
	}

	// Start building up the actual configuration.
	config := new(Config)

	// Terraform config
	if o := list.Filter("terraform"); len(o.Items) > 0 {
		var err error
		config.Terraform, err = loadTerraformHcl(o)
		if err != nil {
			return nil, err
		}
	}

	// Build the variables
	if vars := list.Filter("variable"); len(vars.Items) > 0 {
		var err error
		config.Variables, err = loadVariablesHcl(vars)
		if err != nil {
			return nil, err
		}
	}

	// Build local values
	if locals := list.Filter("locals"); len(locals.Items) > 0 {
		var err error
		config.Locals, err = loadLocalsHcl(locals)
		if err != nil {
			return nil, err
		}
	}

	// Get Atlas configuration
	if atlas := list.Filter("atlas"); len(atlas.Items) > 0 {
		var err error
		config.Atlas, err = loadAtlasHcl(atlas)
		if err != nil {
			return nil, err
		}
	}

	// Build the modules
	if modules := list.Filter("module"); len(modules.Items) > 0 {
		var err error
		config.Modules, err = loadModulesHcl(modules)
		if err != nil {
			return nil, err
		}
	}

	// Build the provider configs
	if providers := list.Filter("provider"); len(providers.Items) > 0 {
		var err error
		config.ProviderConfigs, err = loadProvidersHcl(providers)
		if err != nil {
			return nil, err
		}
	}

	// Build the resources
	{
		var err error
		managedResourceConfigs := list.Filter("resource")
		dataResourceConfigs := list.Filter("data")

		config.Resources = make(
			[]*Resource, 0,
			len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items),
		)

		managedResources, err := loadManagedResourcesHcl(managedResourceConfigs)
		if err != nil {
			return nil, err
		}
		dataResources, err := loadDataResourcesHcl(dataResourceConfigs)
		if err != nil {
			return nil, err
		}

		config.Resources = append(config.Resources, dataResources...)
		config.Resources = append(config.Resources, managedResources...)
	}

	// Build the outputs
	if outputs := list.Filter("output"); len(outputs.Items) > 0 {
		var err error
		config.Outputs, err = loadOutputsHcl(outputs)
		if err != nil {
			return nil, err
		}
	}

	// Check for invalid keys
	for _, item := range list.Items {
		if len(item.Keys) == 0 {
			// Not sure how this would happen, but let's avoid a panic
			continue
		}

		k := item.Keys[0].Token.Value().(string)
		if _, ok := validKeys[k]; ok {
			continue
		}

		config.unknownKeys = append(config.unknownKeys, k)
	}

	return config, nil
}

// loadFileHcl is a fileLoaderFunc that knows how to read HCL
// files and turn them into hclConfigurables.
func loadFileHcl(root string) (configurable, []string, error) {
	// Read the HCL file and prepare for parsing
	d, err := ioutil.ReadFile(root)
	if err != nil {
		return nil, nil, fmt.Errorf(
			"Error reading %s: %s", root, err)
	}

	// Parse it
	hclRoot, err := hcl.Parse(string(d))
	if err != nil {
		return nil, nil, fmt.Errorf(
			"Error parsing %s: %s", root, err)
	}

	// Start building the result
	result := &hclConfigurable{
		File: root,
		Root: hclRoot,
	}

	// Dive in, find the imports. This is disabled for now since
	// imports were removed prior to Terraform 0.1. The code is
	// remaining here commented for historical purposes.
	/*
		imports := obj.Get("import")
		if imports == nil {
			result.Object.Ref()
			return result, nil, nil
		}

		if imports.Type() != libucl.ObjectTypeString {
			imports.Close()

			return nil, nil, fmt.Errorf(
				"Error in %s: all 'import' declarations should be in the format\n"+
					"`import \"foo\"` (Got type %s)",
				root,
				imports.Type())
		}

		// Gather all the import paths
		importPaths := make([]string, 0, imports.Len())
		iter := imports.Iterate(false)
		for imp := iter.Next(); imp != nil; imp = iter.Next() {
			path := imp.ToString()
			if !filepath.IsAbs(path) {
				// Relative paths are relative to the Terraform file itself
				dir := filepath.Dir(root)
				path = filepath.Join(dir, path)
			}

			importPaths = append(importPaths, path)
			imp.Close()
		}
		iter.Close()
		imports.Close()

		result.Object.Ref()
	*/

	return result, nil, nil
}

// Given a handle to a HCL object, this transforms it into the Terraform config
func loadTerraformHcl(list *ast.ObjectList) (*Terraform, error) {
	if len(list.Items) > 1 {
		return nil, fmt.Errorf("only one 'terraform' block allowed per module")
	}

	// Get our one item
	item := list.Items[0]

	// This block should have an empty top level ObjectItem.  If there are keys
	// here, it's likely because we have a flattened JSON object, and we can
	// lift this into a nested ObjectList to decode properly.
	if len(item.Keys) > 0 {
		item = &ast.ObjectItem{
			Val: &ast.ObjectType{
				List: &ast.ObjectList{
					Items: []*ast.ObjectItem{item},
				},
			},
		}
	}

	// We need the item value as an ObjectList
	var listVal *ast.ObjectList
	if ot, ok := item.Val.(*ast.ObjectType); ok {
		listVal = ot.List
	} else {
		return nil, fmt.Errorf("terraform block: should be an object")
	}

	// NOTE: We purposely don't validate unknown HCL keys here so that
	// we can potentially read _future_ Terraform version config (to
	// still be able to validate the required version).
	//
	// We should still keep track of unknown keys to validate later, but
	// HCL doesn't currently support that.

	var config Terraform
	if err := hcl.DecodeObject(&config, item.Val); err != nil {
		return nil, fmt.Errorf(
			"Error reading terraform config: %s",
			err)
	}

	// If we have provisioners, then parse those out
	if os := listVal.Filter("backend"); len(os.Items) > 0 {
		var err error
		config.Backend, err = loadTerraformBackendHcl(os)
		if err != nil {
			return nil, fmt.Errorf(
				"Error reading backend config for terraform block: %s",
				err)
		}
	}

	return &config, nil
}

// Loads the Backend configuration from an object list.
func loadTerraformBackendHcl(list *ast.ObjectList) (*Backend, error) {
	if len(list.Items) > 1 {
		return nil, fmt.Errorf("only one 'backend' block allowed")
	}

	// Get our one item
	item := list.Items[0]

	// Verify the keys
	if len(item.Keys) != 1 {
		return nil, fmt.Errorf(
			"position %s: 'backend' must be followed by exactly one string: a type",
			item.Pos())
	}

	typ := item.Keys[0].Token.Value().(string)

	// Decode the raw config
	var config map[string]interface{}
	if err := hcl.DecodeObject(&config, item.Val); err != nil {
		return nil, fmt.Errorf(
			"Error reading backend config: %s",
			err)
	}

	rawConfig, err := NewRawConfig(config)
	if err != nil {
		return nil, fmt.Errorf(
			"Error reading backend config: %s",
			err)
	}

	b := &Backend{
		Type:      typ,
		RawConfig: rawConfig,
	}
	b.Hash = b.Rehash()

	return b, nil
}

// Given a handle to a HCL object, this transforms it into the Atlas
// configuration.
func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) {
	if len(list.Items) > 1 {
		return nil, fmt.Errorf("only one 'atlas' block allowed")
	}

	// Get our one item
	item := list.Items[0]

	var config AtlasConfig
	if err := hcl.DecodeObject(&config, item.Val); err != nil {
		return nil, fmt.Errorf(
			"Error reading atlas config: %s",
			err)
	}

	return &config, nil
}

// Given a handle to a HCL object, this recurses into the structure
// and pulls out a list of modules.
//
// The resulting modules may not be unique, but each module
// represents exactly one module definition in the HCL configuration.
// We leave it up to another pass to merge them together.
func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) {
	if err := assertAllBlocksHaveNames("module", list); err != nil {
		return nil, err
	}

	list = list.Children()
	if len(list.Items) == 0 {
		return nil, nil
	}

	// Where all the results will go
	var result []*Module

	// Now go over all the types and their children in order to get
	// all of the actual resources.
	for _, item := range list.Items {
		k := item.Keys[0].Token.Value().(string)

		var listVal *ast.ObjectList
		if ot, ok := item.Val.(*ast.ObjectType); ok {
			listVal = ot.List
		} else {
			return nil, fmt.Errorf("module '%s': should be an object", k)
		}

		var config map[string]interface{}
		if err := hcl.DecodeObject(&config, item.Val); err != nil {
			return nil, fmt.Errorf(
				"Error reading config for %s: %s",
				k,
				err)
		}

		rawConfig, err := NewRawConfig(config)
		if err != nil {
			return nil, fmt.Errorf(
				"Error reading config for %s: %s",
				k,
				err)
		}

		// Remove the fields we handle specially
		delete(config, "source")
		delete(config, "version")
		delete(config, "providers")

		var source string
		if o := listVal.Filter("source"); len(o.Items) > 0 {
			err = hcl.DecodeObject(&source, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error parsing source for %s: %s",
					k,
					err)
			}
		}

		var version string
		if o := listVal.Filter("version"); len(o.Items) > 0 {
			err = hcl.DecodeObject(&version, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error parsing version for %s: %s",
					k,
					err)
			}
		}

		var providers map[string]string
		if o := listVal.Filter("providers"); len(o.Items) > 0 {
			err = hcl.DecodeObject(&providers, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error parsing providers for %s: %s",
					k,
					err)
			}
		}

		result = append(result, &Module{
			Name:      k,
			Source:    source,
			Version:   version,
			Providers: providers,
			RawConfig: rawConfig,
		})
	}

	return result, nil
}

// loadLocalsHcl recurses into the given HCL object turns it into
// a list of locals.
func loadLocalsHcl(list *ast.ObjectList) ([]*Local, error) {

	result := make([]*Local, 0, len(list.Items))

	for _, block := range list.Items {
		if len(block.Keys) > 0 {
			return nil, fmt.Errorf(
				"locals block at %s should not have label %q",
				block.Pos(), block.Keys[0].Token.Value(),
			)
		}

		blockObj, ok := block.Val.(*ast.ObjectType)
		if !ok {
			return nil, fmt.Errorf("locals value at %s should be a block", block.Val.Pos())
		}

		// blockObj now contains directly our local decls
		for _, item := range blockObj.List.Items {
			if len(item.Keys) != 1 {
				return nil, fmt.Errorf("local declaration at %s may not be a block", item.Val.Pos())
			}

			// By the time we get here there can only be one item left, but
			// we'll decode into a map anyway because it's a convenient way
			// to extract both the key and the value robustly.
			kv := map[string]interface{}{}
			hcl.DecodeObject(&kv, item)
			for k, v := range kv {
				rawConfig, err := NewRawConfig(map[string]interface{}{
					"value": v,
				})

				if err != nil {
					return nil, fmt.Errorf(
						"error parsing local value %q at %s: %s",
						k, item.Val.Pos(), err,
					)
				}

				result = append(result, &Local{
					Name:      k,
					RawConfig: rawConfig,
				})
			}
		}
	}

	return result, nil
}

// LoadOutputsHcl recurses into the given HCL object and turns
// it into a mapping of outputs.
func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) {
	if err := assertAllBlocksHaveNames("output", list); err != nil {
		return nil, err
	}

	list = list.Children()

	// Go through each object and turn it into an actual result.
	result := make([]*Output, 0, len(list.Items))
	for _, item := range list.Items {
		n := item.Keys[0].Token.Value().(string)

		var listVal *ast.ObjectList
		if ot, ok := item.Val.(*ast.ObjectType); ok {
			listVal = ot.List
		} else {
			return nil, fmt.Errorf("output '%s': should be an object", n)
		}

		var config map[string]interface{}
		if err := hcl.DecodeObject(&config, item.Val); err != nil {
			return nil, err
		}

		// Delete special keys
		delete(config, "depends_on")
		delete(config, "description")

		rawConfig, err := NewRawConfig(config)
		if err != nil {
			return nil, fmt.Errorf(
				"Error reading config for output %s: %s",
				n,
				err)
		}

		// If we have depends fields, then add those in
		var dependsOn []string
		if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
			err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error reading depends_on for output %q: %s",
					n,
					err)
			}
		}

		// If we have a description field, then filter that
		var description string
		if o := listVal.Filter("description"); len(o.Items) > 0 {
			err := hcl.DecodeObject(&description, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error reading description for output %q: %s",
					n,
					err)
			}
		}

		result = append(result, &Output{
			Name:        n,
			RawConfig:   rawConfig,
			DependsOn:   dependsOn,
			Description: description,
		})
	}

	return result, nil
}

// LoadVariablesHcl recurses into the given HCL object and turns
// it into a list of variables.
func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) {
	if err := assertAllBlocksHaveNames("variable", list); err != nil {
		return nil, err
	}

	list = list.Children()

	// hclVariable is the structure each variable is decoded into
	type hclVariable struct {
		DeclaredType string `hcl:"type"`
		Default      interface{}
		Description  string
		Fields       []string `hcl:",decodedFields"`
	}

	// Go through each object and turn it into an actual result.
	result := make([]*Variable, 0, len(list.Items))
	for _, item := range list.Items {
		// Clean up items from JSON
		unwrapHCLObjectKeysFromJSON(item, 1)

		// Verify the keys
		if len(item.Keys) != 1 {
			return nil, fmt.Errorf(
				"position %s: 'variable' must be followed by exactly one strings: a name",
				item.Pos())
		}

		n := item.Keys[0].Token.Value().(string)
		if !NameRegexp.MatchString(n) {
			return nil, fmt.Errorf(
				"position %s: 'variable' name must match regular expression: %s",
				item.Pos(), NameRegexp)
		}

		// Check for invalid keys
		valid := []string{"type", "default", "description"}
		if err := checkHCLKeys(item.Val, valid); err != nil {
			return nil, multierror.Prefix(err, fmt.Sprintf(
				"variable[%s]:", n))
		}

		// Decode into hclVariable to get typed values
		var hclVar hclVariable
		if err := hcl.DecodeObject(&hclVar, item.Val); err != nil {
			return nil, err
		}

		// Defaults turn into a slice of map[string]interface{} and
		// we need to make sure to convert that down into the
		// proper type for Config.
		if ms, ok := hclVar.Default.([]map[string]interface{}); ok {
			def := make(map[string]interface{})
			for _, m := range ms {
				for k, v := range m {
					def[k] = v
				}
			}

			hclVar.Default = def
		}

		// Build the new variable and do some basic validation
		newVar := &Variable{
			Name:         n,
			DeclaredType: hclVar.DeclaredType,
			Default:      hclVar.Default,
			Description:  hclVar.Description,
		}
		if err := newVar.ValidateTypeAndDefault(); err != nil {
			return nil, err
		}

		result = append(result, newVar)
	}

	return result, nil
}

// LoadProvidersHcl recurses into the given HCL object and turns
// it into a mapping of provider configs.
func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) {
	if err := assertAllBlocksHaveNames("provider", list); err != nil {
		return nil, err
	}

	list = list.Children()
	if len(list.Items) == 0 {
		return nil, nil
	}

	// Go through each object and turn it into an actual result.
	result := make([]*ProviderConfig, 0, len(list.Items))
	for _, item := range list.Items {
		n := item.Keys[0].Token.Value().(string)

		var listVal *ast.ObjectList
		if ot, ok := item.Val.(*ast.ObjectType); ok {
			listVal = ot.List
		} else {
			return nil, fmt.Errorf("module '%s': should be an object", n)
		}

		var config map[string]interface{}
		if err := hcl.DecodeObject(&config, item.Val); err != nil {
			return nil, err
		}

		delete(config, "alias")
		delete(config, "version")

		rawConfig, err := NewRawConfig(config)
		if err != nil {
			return nil, fmt.Errorf(
				"Error reading config for provider config %s: %s",
				n,
				err)
		}

		// If we have an alias field, then add those in
		var alias string
		if a := listVal.Filter("alias"); len(a.Items) > 0 {
			err := hcl.DecodeObject(&alias, a.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error reading alias for provider[%s]: %s",
					n,
					err)
			}
		}

		// If we have a version field then extract it
		var version string
		if a := listVal.Filter("version"); len(a.Items) > 0 {
			err := hcl.DecodeObject(&version, a.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error reading version for provider[%s]: %s",
					n,
					err)
			}
		}

		result = append(result, &ProviderConfig{
			Name:      n,
			Alias:     alias,
			Version:   version,
			RawConfig: rawConfig,
		})
	}

	return result, nil
}

// Given a handle to a HCL object, this recurses into the structure
// and pulls out a list of data sources.
//
// The resulting data sources may not be unique, but each one
// represents exactly one data definition in the HCL configuration.
// We leave it up to another pass to merge them together.
func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
	if err := assertAllBlocksHaveNames("data", list); err != nil {
		return nil, err
	}

	list = list.Children()
	if len(list.Items) == 0 {
		return nil, nil
	}

	// Where all the results will go
	var result []*Resource

	// Now go over all the types and their children in order to get
	// all of the actual resources.
	for _, item := range list.Items {
		if len(item.Keys) != 2 {
			return nil, fmt.Errorf(
				"position %s: 'data' must be followed by exactly two strings: a type and a name",
				item.Pos())
		}

		t := item.Keys[0].Token.Value().(string)
		k := item.Keys[1].Token.Value().(string)

		var listVal *ast.ObjectList
		if ot, ok := item.Val.(*ast.ObjectType); ok {
			listVal = ot.List
		} else {
			return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k)
		}

		var config map[string]interface{}
		if err := hcl.DecodeObject(&config, item.Val); err != nil {
			return nil, fmt.Errorf(
				"Error reading config for %s[%s]: %s",
				t,
				k,
				err)
		}

		// Remove the fields we handle specially
		delete(config, "depends_on")
		delete(config, "provider")
		delete(config, "count")

		rawConfig, err := NewRawConfig(config)
		if err != nil {
			return nil, fmt.Errorf(
				"Error reading config for %s[%s]: %s",
				t,
				k,
				err)
		}

		// If we have a count, then figure it out
		var count string = "1"
		if o := listVal.Filter("count"); len(o.Items) > 0 {
			err = hcl.DecodeObject(&count, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error parsing count for %s[%s]: %s",
					t,
					k,
					err)
			}
		}
		countConfig, err := NewRawConfig(map[string]interface{}{
			"count": count,
		})
		if err != nil {
			return nil, err
		}
		countConfig.Key = "count"

		// If we have depends fields, then add those in
		var dependsOn []string
		if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
			err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error reading depends_on for %s[%s]: %s",
					t,
					k,
					err)
			}
		}

		// If we have a provider, then parse it out
		var provider string
		if o := listVal.Filter("provider"); len(o.Items) > 0 {
			err := hcl.DecodeObject(&provider, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error reading provider for %s[%s]: %s",
					t,
					k,
					err)
			}
		}

		result = append(result, &Resource{
			Mode:         DataResourceMode,
			Name:         k,
			Type:         t,
			RawCount:     countConfig,
			RawConfig:    rawConfig,
			Provider:     provider,
			Provisioners: []*Provisioner{},
			DependsOn:    dependsOn,
			Lifecycle:    ResourceLifecycle{},
		})
	}

	return result, nil
}

// Given a handle to a HCL object, this recurses into the structure
// and pulls out a list of managed resources.
//
// The resulting resources may not be unique, but each resource
// represents exactly one "resource" block in the HCL configuration.
// We leave it up to another pass to merge them together.
func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
	list = list.Children()
	if len(list.Items) == 0 {
		return nil, nil
	}

	// Where all the results will go
	var result []*Resource

	// Now go over all the types and their children in order to get
	// all of the actual resources.
	for _, item := range list.Items {
		// GH-4385: We detect a pure provisioner resource and give the user
		// an error about how to do it cleanly.
		if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" {
			return nil, fmt.Errorf(
				"position %s: provisioners in a resource should be wrapped in a list\n\n"+
					"Example: \"provisioner\": [ { \"local-exec\": ... } ]",
				item.Pos())
		}

		// Fix up JSON input
		unwrapHCLObjectKeysFromJSON(item, 2)

		if len(item.Keys) != 2 {
			return nil, fmt.Errorf(
				"position %s: resource must be followed by exactly two strings, a type and a name",
				item.Pos())
		}

		t := item.Keys[0].Token.Value().(string)
		k := item.Keys[1].Token.Value().(string)

		var listVal *ast.ObjectList
		if ot, ok := item.Val.(*ast.ObjectType); ok {
			listVal = ot.List
		} else {
			return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k)
		}

		var config map[string]interface{}
		if err := hcl.DecodeObject(&config, item.Val); err != nil {
			return nil, fmt.Errorf(
				"Error reading config for %s[%s]: %s",
				t,
				k,
				err)
		}

		// Remove the fields we handle specially
		delete(config, "connection")
		delete(config, "count")
		delete(config, "depends_on")
		delete(config, "provisioner")
		delete(config, "provider")
		delete(config, "lifecycle")

		rawConfig, err := NewRawConfig(config)
		if err != nil {
			return nil, fmt.Errorf(
				"Error reading config for %s[%s]: %s",
				t,
				k,
				err)
		}

		// If we have a count, then figure it out
		var count string = "1"
		if o := listVal.Filter("count"); len(o.Items) > 0 {
			err = hcl.DecodeObject(&count, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error parsing count for %s[%s]: %s",
					t,
					k,
					err)
			}
		}
		countConfig, err := NewRawConfig(map[string]interface{}{
			"count": count,
		})
		if err != nil {
			return nil, err
		}
		countConfig.Key = "count"

		// If we have depends fields, then add those in
		var dependsOn []string
		if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
			err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error reading depends_on for %s[%s]: %s",
					t,
					k,
					err)
			}
		}

		// If we have connection info, then parse those out
		var connInfo map[string]interface{}
		if o := listVal.Filter("connection"); len(o.Items) > 0 {
			err := hcl.DecodeObject(&connInfo, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error reading connection info for %s[%s]: %s",
					t,
					k,
					err)
			}
		}

		// If we have provisioners, then parse those out
		var provisioners []*Provisioner
		if os := listVal.Filter("provisioner"); len(os.Items) > 0 {
			var err error
			provisioners, err = loadProvisionersHcl(os, connInfo)
			if err != nil {
				return nil, fmt.Errorf(
					"Error reading provisioners for %s[%s]: %s",
					t,
					k,
					err)
			}
		}

		// If we have a provider, then parse it out
		var provider string
		if o := listVal.Filter("provider"); len(o.Items) > 0 {
			err := hcl.DecodeObject(&provider, o.Items[0].Val)
			if err != nil {
				return nil, fmt.Errorf(
					"Error reading provider for %s[%s]: %s",
					t,
					k,
					err)
			}
		}

		// Check if the resource should be re-created before
		// destroying the existing instance
		var lifecycle ResourceLifecycle
		if o := listVal.Filter("lifecycle"); len(o.Items) > 0 {
			if len(o.Items) > 1 {
				return nil, fmt.Errorf(
					"%s[%s]: Multiple lifecycle blocks found, expected one",
					t, k)
			}

			// Check for invalid keys
			valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"}
			if err := checkHCLKeys(o.Items[0].Val, valid); err != nil {
				return nil, multierror.Prefix(err, fmt.Sprintf(
					"%s[%s]:", t, k))
			}

			var raw map[string]interface{}
			if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil {
				return nil, fmt.Errorf(
					"Error parsing lifecycle for %s[%s]: %s",
					t,
					k,
					err)
			}

			if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil {
				return nil, fmt.Errorf(
					"Error parsing lifecycle for %s[%s]: %s",
					t,
					k,
					err)
			}
		}

		result = append(result, &Resource{
			Mode:         ManagedResourceMode,
			Name:         k,
			Type:         t,
			RawCount:     countConfig,
			RawConfig:    rawConfig,
			Provisioners: provisioners,
			Provider:     provider,
			DependsOn:    dependsOn,
			Lifecycle:    lifecycle,
		})
	}

	return result, nil
}

func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) {
	if err := assertAllBlocksHaveNames("provisioner", list); err != nil {
		return nil, err
	}

	list = list.Children()
	if len(list.Items) == 0 {
		return nil, nil
	}

	// Go through each object and turn it into an actual result.
	result := make([]*Provisioner, 0, len(list.Items))
	for _, item := range list.Items {
		n := item.Keys[0].Token.Value().(string)

		var listVal *ast.ObjectList
		if ot, ok := item.Val.(*ast.ObjectType); ok {
			listVal = ot.List
		} else {
			return nil, fmt.Errorf("provisioner '%s': should be an object", n)
		}

		var config map[string]interface{}
		if err := hcl.DecodeObject(&config, item.Val); err != nil {
			return nil, err
		}

		// Parse the "when" value
		when := ProvisionerWhenCreate
		if v, ok := config["when"]; ok {
			switch v {
			case "create":
				when = ProvisionerWhenCreate
			case "destroy":
				when = ProvisionerWhenDestroy
			default:
				return nil, fmt.Errorf(
					"position %s: 'provisioner' when must be 'create' or 'destroy'",
					item.Pos())
			}
		}

		// Parse the "on_failure" value
		onFailure := ProvisionerOnFailureFail
		if v, ok := config["on_failure"]; ok {
			switch v {
			case "continue":
				onFailure = ProvisionerOnFailureContinue
			case "fail":
				onFailure = ProvisionerOnFailureFail
			default:
				return nil, fmt.Errorf(
					"position %s: 'provisioner' on_failure must be 'continue' or 'fail'",
					item.Pos())
			}
		}

		// Delete fields we special case
		delete(config, "connection")
		delete(config, "when")
		delete(config, "on_failure")

		rawConfig, err := NewRawConfig(config)
		if err != nil {
			return nil, err
		}

		// Check if we have a provisioner-level connection
		// block that overrides the resource-level
		var subConnInfo map[string]interface{}
		if o := listVal.Filter("connection"); len(o.Items) > 0 {
			err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val)
			if err != nil {
				return nil, err
			}
		}

		// Inherit from the resource connInfo any keys
		// that are not explicitly overriden.
		if connInfo != nil && subConnInfo != nil {
			for k, v := range connInfo {
				if _, ok := subConnInfo[k]; !ok {
					subConnInfo[k] = v
				}
			}
		} else if subConnInfo == nil {
			subConnInfo = connInfo
		}

		// Parse the connInfo
		connRaw, err := NewRawConfig(subConnInfo)
		if err != nil {
			return nil, err
		}

		result = append(result, &Provisioner{
			Type:      n,
			RawConfig: rawConfig,
			ConnInfo:  connRaw,
			When:      when,
			OnFailure: onFailure,
		})
	}

	return result, nil
}

/*
func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode {
	objects := make(map[string][]*hclobj.Object)

	for _, o := range os.Elem(false) {
		for _, elem := range o.Elem(true) {
			val, ok := objects[elem.Key]
			if !ok {
				val = make([]*hclobj.Object, 0, 1)
			}

			val = append(val, elem)
			objects[elem.Key] = val
		}
	}

	return objects
}
*/

// assertAllBlocksHaveNames returns an error if any of the items in
// the given object list are blocks without keys (like "module {}")
// or simple assignments (like "module = 1"). It returns nil if
// neither of these things are true.
//
// The given name is used in any generated error messages, and should
// be the name of the block we're dealing with. The given list should
// be the result of calling .Filter on an object list with that same
// name.
func assertAllBlocksHaveNames(name string, list *ast.ObjectList) error {
	if elem := list.Elem(); len(elem.Items) != 0 {
		switch et := elem.Items[0].Val.(type) {
		case *ast.ObjectType:
			pos := et.Lbrace
			return fmt.Errorf("%s: %q must be followed by a name", pos, name)
		default:
			pos := elem.Items[0].Val.Pos()
			return fmt.Errorf("%s: %q must be a configuration block", pos, name)
		}
	}
	return nil
}

func checkHCLKeys(node ast.Node, valid []string) error {
	var list *ast.ObjectList
	switch n := node.(type) {
	case *ast.ObjectList:
		list = n
	case *ast.ObjectType:
		list = n.List
	default:
		return fmt.Errorf("cannot check HCL keys of type %T", n)
	}

	validMap := make(map[string]struct{}, len(valid))
	for _, v := range valid {
		validMap[v] = struct{}{}
	}

	var result error
	for _, item := range list.Items {
		key := item.Keys[0].Token.Value().(string)
		if _, ok := validMap[key]; !ok {
			result = multierror.Append(result, fmt.Errorf(
				"invalid key: %s", key))
		}
	}

	return result
}

// unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when
// parsing JSON as input: if we're parsing JSON then directly nested
// items will show up as additional "keys".
//
// For objects that expect a fixed number of keys, this breaks the
// decoding process. This function unwraps the object into what it would've
// looked like if it came directly from HCL by specifying the number of keys
// you expect.
//
// Example:
//
// { "foo": { "baz": {} } }
//
// Will show up with Keys being: []string{"foo", "baz"}
// when we really just want the first two. This function will fix this.
func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) {
	if len(item.Keys) > depth && item.Keys[0].Token.JSON {
		for len(item.Keys) > depth {
			// Pop off the last key
			n := len(item.Keys)
			key := item.Keys[n-1]
			item.Keys[n-1] = nil
			item.Keys = item.Keys[:n-1]

			// Wrap our value in a list
			item.Val = &ast.ObjectType{
				List: &ast.ObjectList{
					Items: []*ast.ObjectItem{
						&ast.ObjectItem{
							Keys: []*ast.ObjectKey{key},
							Val:  item.Val,
						},
					},
				},
			}
		}
	}
}
