// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package resource

import (
	"sync"
	"time"

	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)

// State is a structure containing state associated with a resource.  This resource may have been serialized and
// deserialized, or snapshotted from a live graph of resource objects.  The value's state is not, however, associated
// with any runtime objects in memory that may be actively involved in ongoing computations.
//
//nolint:lll
type State struct {
	// Currently the engine implements RegisterResourceOutputs by directly mutating the state to change the `Outputs`. This
	// triggers a race between the snapshot serialization code and the engine. Ideally we'd do a more principled fix, but
	// just locking in these two places is sufficient to stop the race detector from firing on integration tests.
	Lock sync.Mutex

	Type                    tokens.Type           // the resource's type.
	URN                     URN                   // the resource's object urn, a human-friendly, unique name for the resource.
	Custom                  bool                  // true if the resource is custom, managed by a plugin.
	Delete                  bool                  // true if this resource is pending deletion due to a replacement.
	ID                      ID                    // the resource's unique ID, assigned by the resource provider (or blank if none/uncreated).
	Inputs                  PropertyMap           // the resource's input properties (as specified by the program).
	Outputs                 PropertyMap           // the resource's complete output state (as returned by the resource provider).
	Parent                  URN                   // an optional parent URN that this resource belongs to.
	Protect                 bool                  // true to "protect" this resource (protected resources cannot be deleted).
	External                bool                  // true if this resource is "external" to Pulumi and we don't control the lifecycle.
	Dependencies            []URN                 // the resource's dependencies.
	InitErrors              []string              // the set of errors encountered in the process of initializing resource.
	Provider                string                // the provider to use for this resource.
	PropertyDependencies    map[PropertyKey][]URN // the set of dependencies that affect each property.
	PendingReplacement      bool                  // true if this resource was deleted and is awaiting replacement.
	AdditionalSecretOutputs []PropertyKey         // an additional set of outputs that should be treated as secrets.
	Aliases                 []URN                 // an optional set of URNs for which this resource is an alias.
	CustomTimeouts          CustomTimeouts        // A config block that will be used to configure timeouts for CRUD operations.
	ImportID                ID                    // the resource's import id, if this was an imported resource.
	RetainOnDelete          bool                  // if set to True, the providers Delete method will not be called for this resource.
	DeletedWith             URN                   // If set, the providers Delete method will not be called for this resource if specified resource is being deleted as well.
	Created                 *time.Time            // If set, the time when the state was initially added to the state file. (i.e. Create, Import)
	Modified                *time.Time            // If set, the time when the state was last modified in the state file.
	SourcePosition          string                // If set, the source location of the resource registration
	IgnoreChanges           []string              // If set, the list of properties to ignore changes for.
}

// Copy creates a deep copy of the resource state, except without copying the lock.
func (s *State) Copy() *State {
	return &State{
		Type:                    s.Type,
		URN:                     s.URN,
		Custom:                  s.Custom,
		Delete:                  s.Delete,
		ID:                      s.ID,
		Inputs:                  s.Inputs,
		Outputs:                 s.Outputs,
		Parent:                  s.Parent,
		Protect:                 s.Protect,
		External:                s.External,
		Dependencies:            s.Dependencies,
		InitErrors:              s.InitErrors,
		Provider:                s.Provider,
		PropertyDependencies:    s.PropertyDependencies,
		PendingReplacement:      s.PendingReplacement,
		AdditionalSecretOutputs: s.AdditionalSecretOutputs,
		Aliases:                 s.Aliases,
		CustomTimeouts:          s.CustomTimeouts,
		ImportID:                s.ImportID,
		RetainOnDelete:          s.RetainOnDelete,
		DeletedWith:             s.DeletedWith,
		Created:                 s.Created,
		Modified:                s.Modified,
		SourcePosition:          s.SourcePosition,
		IgnoreChanges:           s.IgnoreChanges,
	}
}

func (s *State) GetAliasURNs() []URN {
	return s.Aliases
}

func (s *State) GetAliases() []Alias {
	aliases := make([]Alias, len(s.Aliases))
	for i, alias := range s.Aliases {
		aliases[i] = Alias{URN: alias}
	}
	return aliases
}

// NewState creates a new resource value from existing resource state information.
func NewState(t tokens.Type, urn URN, custom bool, del bool, id ID,
	inputs PropertyMap, outputs PropertyMap, parent URN, protect bool,
	external bool, dependencies []URN, initErrors []string, provider string,
	propertyDependencies map[PropertyKey][]URN, pendingReplacement bool,
	additionalSecretOutputs []PropertyKey, aliases []URN, timeouts *CustomTimeouts,
	importID ID, retainOnDelete bool, deletedWith URN, created *time.Time, modified *time.Time,
	sourcePosition string, ignoreChanges []string,
) *State {
	contract.Assertf(t != "", "type was empty")
	contract.Assertf(custom || id == "", "is custom or had empty ID")

	s := &State{
		Type:                    t,
		URN:                     urn,
		Custom:                  custom,
		Delete:                  del,
		ID:                      id,
		Inputs:                  inputs,
		Outputs:                 outputs,
		Parent:                  parent,
		Protect:                 protect,
		External:                external,
		Dependencies:            dependencies,
		InitErrors:              initErrors,
		Provider:                provider,
		PropertyDependencies:    propertyDependencies,
		PendingReplacement:      pendingReplacement,
		AdditionalSecretOutputs: additionalSecretOutputs,
		Aliases:                 aliases,
		ImportID:                importID,
		RetainOnDelete:          retainOnDelete,
		DeletedWith:             deletedWith,
		Created:                 created,
		Modified:                modified,
		SourcePosition:          sourcePosition,
		IgnoreChanges:           ignoreChanges,
	}

	if timeouts != nil {
		s.CustomTimeouts = *timeouts
	}

	return s
}