package test

import (
	"errors"
	"fmt"

	"github.com/hashicorp/terraform/helper/schema"
)

func testResource() *schema.Resource {
	return &schema.Resource{
		Create: testResourceCreate,
		Read:   testResourceRead,
		Update: testResourceUpdate,
		Delete: testResourceDelete,

		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		CustomizeDiff: func(d *schema.ResourceDiff, _ interface{}) error {
			if d.HasChange("optional") {
				d.SetNewComputed("planned_computed")
			}
			return nil
		},

		Schema: map[string]*schema.Schema{
			"required": {
				Type:     schema.TypeString,
				Required: true,
			},
			"optional": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"optional_bool": {
				Type:     schema.TypeBool,
				Optional: true,
			},
			"optional_force_new": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},
			"optional_computed_map": {
				Type:     schema.TypeMap,
				Optional: true,
				Computed: true,
			},
			"optional_computed_force_new": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},
			"optional_computed": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"computed_read_only": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"computed_from_required": {
				Type:     schema.TypeString,
				Computed: true,
				ForceNew: true,
			},
			"computed_read_only_force_new": {
				Type:     schema.TypeString,
				Computed: true,
				ForceNew: true,
			},
			"computed_list": {
				Type:     schema.TypeList,
				Computed: true,
				Elem: &schema.Schema{
					Type: schema.TypeString,
				},
			},
			"set": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Schema{
					Type: schema.TypeString,
				},
				Set: schema.HashString,
			},
			"computed_set": {
				Type:     schema.TypeSet,
				Computed: true,
				Elem: &schema.Schema{
					Type: schema.TypeString,
				},
				Set: schema.HashString,
			},
			"map": {
				Type:     schema.TypeMap,
				Optional: true,
			},
			"optional_map": {
				Type:     schema.TypeMap,
				Optional: true,
			},
			"required_map": {
				Type:     schema.TypeMap,
				Required: true,
			},
			"map_that_look_like_set": {
				Type:     schema.TypeMap,
				Optional: true,
				Elem: &schema.Schema{
					Type: schema.TypeString,
				},
			},
			"computed_map": {
				Type:     schema.TypeMap,
				Computed: true,
			},
			"list": {
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Schema{
					Type: schema.TypeString,
				},
			},
			"list_of_map": {
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Schema{
					Type: schema.TypeMap,
					Elem: &schema.Schema{
						Type: schema.TypeString,
					},
				},
			},
			"apply_error": {
				Type:        schema.TypeString,
				Optional:    true,
				Description: "return and error during apply",
			},
			"planned_computed": {
				Type:        schema.TypeString,
				Computed:    true,
				Description: "copied the required field during apply, and plans computed when changed",
			},
			// this should return unset from GetOkExists
			"get_ok_exists_false": {
				Type:        schema.TypeBool,
				Computed:    true,
				Optional:    true,
				Description: "do not set in config",
			},
			"int": {
				Type:     schema.TypeInt,
				Optional: true,
			},
		},
	}
}

func testResourceCreate(d *schema.ResourceData, meta interface{}) error {
	d.SetId("testId")

	errMsg, _ := d.Get("apply_error").(string)
	if errMsg != "" {
		return errors.New(errMsg)
	}

	// Required must make it through to Create
	if _, ok := d.GetOk("required"); !ok {
		return fmt.Errorf("Missing attribute 'required', but it's required!")
	}
	if _, ok := d.GetOk("required_map"); !ok {
		return fmt.Errorf("Missing attribute 'required_map', but it's required!")
	}

	d.Set("computed_from_required", d.Get("required"))

	return testResourceRead(d, meta)
}

func testResourceRead(d *schema.ResourceData, meta interface{}) error {
	d.Set("computed_read_only", "value_from_api")
	d.Set("computed_read_only_force_new", "value_from_api")
	if _, ok := d.GetOk("optional_computed_map"); !ok {
		d.Set("optional_computed_map", map[string]string{})
	}
	d.Set("computed_map", map[string]string{"key1": "value1"})
	d.Set("computed_list", []string{"listval1", "listval2"})
	d.Set("computed_set", []string{"setval1", "setval2"})

	d.Set("planned_computed", d.Get("optional"))

	// if there is no "set" value, erroneously set it to an empty set. This
	// might change a null value to an empty set, but we should be able to
	// ignore that.
	s := d.Get("set")
	if s == nil || s.(*schema.Set).Len() == 0 {
		d.Set("set", []interface{}{})
	}

	// This mimics many providers always setting a *string value.
	// The existing behavior is that this will appear in the state as an empty
	// string, which we have to maintain.
	o := d.Get("optional")
	if o == "" {
		d.Set("optional", nil)
	}

	// This should not show as set unless it's set in the config
	_, ok := d.GetOkExists("get_ok_exists_false")
	if ok {
		return errors.New("get_ok_exists_false should not be set")
	}

	return nil
}

func testResourceUpdate(d *schema.ResourceData, meta interface{}) error {
	errMsg, _ := d.Get("apply_error").(string)
	if errMsg != "" {
		return errors.New(errMsg)
	}
	return testResourceRead(d, meta)
}

func testResourceDelete(d *schema.ResourceData, meta interface{}) error {
	d.SetId("")
	return nil
}
