// 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 display

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"time"

	"github.com/pulumi/pulumi/pkg/apitype"
	"github.com/pulumi/pulumi/pkg/engine"
	"github.com/pulumi/pulumi/pkg/resource"
	"github.com/pulumi/pulumi/pkg/resource/deploy"
	"github.com/pulumi/pulumi/pkg/tokens"
	"github.com/pulumi/pulumi/pkg/util/contract"
	"github.com/pulumi/pulumi/pkg/util/logging"
)

// ShowEvents reads events from the `events` channel until it is closed, displaying each event as
// it comes in. Once all events have been read from the channel and displayed, it closes the `done`
// channel so the caller can await all the events being written.
func ShowEvents(
	op string, action apitype.UpdateKind, stack tokens.QName, proj tokens.PackageName,
	events <-chan engine.Event, done chan<- bool, opts Options, isPreview bool) {

	if opts.EventLogPath != "" {
		events, done = startEventLogger(events, done, opts.EventLogPath)
	}

	if opts.JSONDisplay {
		// TODO[pulumi/pulumi#2390]: enable JSON display for real deployments.
		contract.Assertf(isPreview, "JSON display only available in preview mode")
		ShowJSONEvents(op, action, events, done, opts)
		return
	}

	switch opts.Type {
	case DisplayDiff:
		ShowDiffEvents(op, action, events, done, opts)
	case DisplayProgress:
		ShowProgressEvents(op, action, stack, proj, events, done, opts, isPreview)
	case DisplayQuery:
		contract.Failf("DisplayQuery can only be used in query mode, which should be invoked " +
			"directly instead of through ShowEvents")
	case DisplayWatch:
		ShowWatchEvents(op, action, events, done, opts)
	default:
		contract.Failf("Unknown display type %d", opts.Type)
	}
}

func startEventLogger(events <-chan engine.Event, done chan<- bool, path string) (<-chan engine.Event, chan<- bool) {
	// Before moving further, attempt to open the log file.
	logFile, err := os.Create(path)
	if err != nil {
		logging.V(7).Infof("could not create event log: %v", err)
		return events, done
	}

	outEvents, outDone := make(chan engine.Event), make(chan bool)
	go func() {
		defer close(done)
		defer func() {
			contract.IgnoreError(logFile.Close())
		}()

		sequence := 0
		encoder := json.NewEncoder(logFile)
		logEvent := func(e engine.Event) error {
			apiEvent, err := ConvertEngineEvent(e)
			if err != nil {
				return err
			}
			apiEvent.Sequence, sequence = sequence, sequence+1
			apiEvent.Timestamp = int(time.Now().Unix())
			return encoder.Encode(apiEvent)
		}

		for e := range events {
			if err = logEvent(e); err != nil {
				logging.V(7).Infof("failed to log event: %v", err)
			}

			outEvents <- e

			if e.Type == engine.CancelEvent {
				break
			}
		}

		<-outDone
	}()

	return outEvents, outDone
}

type nopSpinner struct {
}

func (s *nopSpinner) Tick() {
}

func (s *nopSpinner) Reset() {
}

// isRootStack returns true if the step pertains to the rootmost stack component.
func isRootStack(step engine.StepEventMetadata) bool {
	return isRootURN(step.URN)
}

func isRootURN(urn resource.URN) bool {
	return urn != "" && urn.Type() == resource.RootStackType
}

// shouldShow returns true if a step should show in the output.
func shouldShow(step engine.StepEventMetadata, opts Options) bool {
	// For certain operations, whether they are tracked is controlled by flags (to cut down on superfluous output).
	if step.Op == deploy.OpSame {
		// If the op is the same, it is possible that the resource's metadata changed.  In that case, still show it.
		if step.Old.Protect != step.New.Protect {
			return true
		}
		return opts.ShowSameResources
	}

	// For logical replacement operations, only show them during progress-style updates (since this is integrated
	// into the resource status update), or if it is requested explicitly (for diffs and JSON outputs).
	if (opts.Type == DisplayDiff || opts.JSONDisplay) && !step.Logical && !opts.ShowReplacementSteps {
		return false
	}

	// Otherwise, default to showing the operation.
	return true
}

func fprintfIgnoreError(w io.Writer, format string, a ...interface{}) {
	_, err := fmt.Fprintf(w, format, a...)
	contract.IgnoreError(err)
}

func fprintIgnoreError(w io.Writer, a ...interface{}) {
	_, err := fmt.Fprint(w, a...)
	contract.IgnoreError(err)
}
