// Copyright 2016-2017, Pulumi Corporation.  All rights reserved.

// Package stack contains the serialized and configurable state associated with an stack; or, in other
// words, a deployment target.  It pertains to resources and deployment plans, but is a package unto itself.
package stack

import (
	"encoding/json"
	"io/ioutil"

	"github.com/pulumi/pulumi/pkg/resource"
	"github.com/pulumi/pulumi/pkg/resource/config"
	"github.com/pulumi/pulumi/pkg/resource/deploy"
	"github.com/pulumi/pulumi/pkg/resource/plugin"
	"github.com/pulumi/pulumi/pkg/tokens"
	"github.com/pulumi/pulumi/pkg/util/contract"
	"github.com/pulumi/pulumi/pkg/workspace"
)

// Checkpoint is a serialized deployment target plus a record of the latest deployment.
// nolint: lll
type Checkpoint struct {
	Stack  tokens.QName `json:"stack" yaml:"stack"`                       // the target stack name.
	Config config.Map   `json:"config,omitempty" yaml:"config,omitempty"` // optional configuration key/values.
	Latest *Deployment  `json:"latest,omitempty" yaml:"latest,omitempty"` // the latest/current deployment information.
}

// GetCheckpoint loads a checkpoint file for the given stack in this project, from the current project workspace.
func GetCheckpoint(w workspace.W, stack tokens.QName) (*Checkpoint, error) {
	chkpath := w.StackPath(stack)
	bytes, err := ioutil.ReadFile(chkpath)
	if err != nil {
		return nil, err
	}
	var checkpoint Checkpoint
	if err = json.Unmarshal(bytes, &checkpoint); err != nil {
		return nil, err
	}
	return &checkpoint, nil
}

// SerializeCheckpoint turns a snapshot into a data structure suitable for serialization.
func SerializeCheckpoint(stack tokens.QName, config config.Map, snap *deploy.Snapshot) *Checkpoint {
	// If snap is nil, that's okay, we will just create an empty deployment; otherwise, serialize the whole snapshot.
	var latest *Deployment
	if snap != nil {
		latest = SerializeDeployment(snap)
	}

	return &Checkpoint{
		Stack:  stack,
		Config: config,
		Latest: latest,
	}
}

// DeserializeCheckpoint takes a serialized deployment record and returns its associated snapshot.
func DeserializeCheckpoint(chkpoint *Checkpoint) (*deploy.Snapshot, error) {
	contract.Require(chkpoint != nil, "chkpoint")

	var snap *deploy.Snapshot
	stack := chkpoint.Stack
	if latest := chkpoint.Latest; latest != nil {
		// Unpack the versions.
		manifest := deploy.Manifest{
			Time:    latest.Manifest.Time,
			Magic:   latest.Manifest.Magic,
			Version: latest.Manifest.Version,
		}
		for _, plug := range latest.Manifest.Plugins {
			manifest.Plugins = append(manifest.Plugins, plugin.Info{
				Name:    plug.Name,
				Type:    plug.Type,
				Version: plug.Version,
			})
		}

		// For every serialized resource vertex, create a ResourceDeployment out of it.
		var resources []*resource.State
		for _, res := range latest.Resources {
			desres, err := DeserializeResource(res)
			if err != nil {
				return nil, err
			}
			resources = append(resources, desres)
		}

		snap = deploy.NewSnapshot(stack, manifest, resources)
	}

	return snap, nil
}

// GetRootStackResource returns the root stack resource from a given snapshot, or nil if not found.  If the stack
// exists, its output properties, if any, are also returned in the resulting map.
func GetRootStackResource(snap *deploy.Snapshot) (*resource.State, map[string]interface{}) {
	if snap != nil {
		for _, res := range snap.Resources {
			if res.Type == resource.RootStackType {
				return res, SerializeResource(res).Outputs
			}
		}
	}
	return nil, nil
}