package terraform

import (
	"fmt"
)

// EvalReadDataDiff is an EvalNode implementation that executes a data
// resource's ReadDataDiff method to discover what attributes it exports.
type EvalReadDataDiff struct {
	Provider    *ResourceProvider
	Output      **InstanceDiff
	OutputState **InstanceState
	Config      **ResourceConfig
	Info        *InstanceInfo

	// Set Previous when re-evaluating diff during apply, to ensure that
	// the "Destroy" flag is preserved.
	Previous **InstanceDiff
}

func (n *EvalReadDataDiff) Eval(ctx EvalContext) (interface{}, error) {
	// TODO: test

	err := ctx.Hook(func(h Hook) (HookAction, error) {
		return h.PreDiff(n.Info, nil)
	})
	if err != nil {
		return nil, err
	}

	var diff *InstanceDiff

	if n.Previous != nil && *n.Previous != nil && (*n.Previous).GetDestroy() {
		// If we're re-diffing for a diff that was already planning to
		// destroy, then we'll just continue with that plan.
		diff = &InstanceDiff{Destroy: true}
	} else {
		provider := *n.Provider
		config := *n.Config

		var err error
		diff, err = provider.ReadDataDiff(n.Info, config)
		if err != nil {
			return nil, err
		}
		if diff == nil {
			diff = new(InstanceDiff)
		}

		// if id isn't explicitly set then it's always computed, because we're
		// always "creating a new resource".
		diff.init()
		if _, ok := diff.Attributes["id"]; !ok {
			diff.SetAttribute("id", &ResourceAttrDiff{
				Old:         "",
				NewComputed: true,
				RequiresNew: true,
				Type:        DiffAttrOutput,
			})
		}
	}

	err = ctx.Hook(func(h Hook) (HookAction, error) {
		return h.PostDiff(n.Info, diff)
	})
	if err != nil {
		return nil, err
	}

	*n.Output = diff

	if n.OutputState != nil {
		state := &InstanceState{}
		*n.OutputState = state

		// Apply the diff to the returned state, so the state includes
		// any attribute values that are not computed.
		if !diff.Empty() && n.OutputState != nil {
			*n.OutputState = state.MergeDiff(diff)
		}
	}

	return nil, nil
}

// EvalReadDataApply is an EvalNode implementation that executes a data
// resource's ReadDataApply method to read data from the data source.
type EvalReadDataApply struct {
	Provider *ResourceProvider
	Output   **InstanceState
	Diff     **InstanceDiff
	Info     *InstanceInfo
}

func (n *EvalReadDataApply) Eval(ctx EvalContext) (interface{}, error) {
	// TODO: test
	provider := *n.Provider
	diff := *n.Diff

	// If the diff is for *destroying* this resource then we'll
	// just drop its state and move on, since data resources don't
	// support an actual "destroy" action.
	if diff != nil && diff.GetDestroy() {
		if n.Output != nil {
			*n.Output = nil
		}
		return nil, nil
	}

	// For the purpose of external hooks we present a data apply as a
	// "Refresh" rather than an "Apply" because creating a data source
	// is presented to users/callers as a "read" operation.
	err := ctx.Hook(func(h Hook) (HookAction, error) {
		// We don't have a state yet, so we'll just give the hook an
		// empty one to work with.
		return h.PreRefresh(n.Info, &InstanceState{})
	})
	if err != nil {
		return nil, err
	}

	state, err := provider.ReadDataApply(n.Info, diff)
	if err != nil {
		return nil, fmt.Errorf("%s: %s", n.Info.Id, err)
	}

	err = ctx.Hook(func(h Hook) (HookAction, error) {
		return h.PostRefresh(n.Info, state)
	})
	if err != nil {
		return nil, err
	}

	if n.Output != nil {
		*n.Output = state
	}

	return nil, nil
}
