// Copyright 2016-2019, 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 backend

import (
	"context"
	"fmt"
	"path"
	"time"

	"github.com/rjeczalik/notify"

	"github.com/pulumi/pulumi/pkg/apitype"
	"github.com/pulumi/pulumi/pkg/backend/display"
	"github.com/pulumi/pulumi/pkg/diag/colors"
	"github.com/pulumi/pulumi/pkg/operations"
	"github.com/pulumi/pulumi/pkg/util/logging"
	"github.com/pulumi/pulumi/pkg/util/result"
)

// Watch watches the project's working directory for changes and automatically updates the active
// stack.
func Watch(ctx context.Context, b Backend, stack Stack, op UpdateOperation, apply Applier) result.Result {

	opts := ApplierOptions{
		DryRun:   false,
		ShowLink: false,
	}

	startTime := time.Now()

	go func() {
		shown := map[operations.LogEntry]bool{}
		for {
			logs, err := b.GetLogs(ctx, stack, op.StackConfiguration, operations.LogQuery{
				StartTime: &startTime,
			})
			if err != nil {
				logging.V(5).Infof("failed to get logs: %v", err.Error())
			}

			for _, logEntry := range logs {
				if _, shownAlready := shown[logEntry]; !shownAlready {
					eventTime := time.Unix(0, logEntry.Timestamp*1000000)

					display.PrintfWithWatchPrefix(eventTime, logEntry.ID, "%s\n", logEntry.Message)

					shown[logEntry] = true
				}
			}
			time.Sleep(10 * time.Second)
		}
	}()

	events := make(chan notify.EventInfo, 1)
	if err := notify.Watch(path.Join(op.Root, "..."), events, notify.All); err != nil {
		return result.FromError(err)
	}
	defer notify.Stop(events)

	fmt.Printf(op.Opts.Display.Color.Colorize(
		colors.SpecHeadline+"Watching (%s):"+colors.Reset+"\n"), stack.Ref())

	for range events {
		display.PrintfWithWatchPrefix(time.Now(), "",
			op.Opts.Display.Color.Colorize(colors.SpecImportant+"Updating..."+colors.Reset+"\n"))

		// Perform the update operation
		_, res := apply(ctx, apitype.UpdateUpdate, stack, op, opts, nil)
		if res != nil {
			logging.V(5).Infof("watch update failed: %v", res.Error())
			if res.Error() == context.Canceled {
				return res
			}
			display.PrintfWithWatchPrefix(time.Now(), "",
				op.Opts.Display.Color.Colorize(colors.SpecImportant+"Update failed."+colors.Reset+"\n"))
		} else {
			display.PrintfWithWatchPrefix(time.Now(), "",
				op.Opts.Display.Color.Colorize(colors.SpecImportant+"Update complete."+colors.Reset+"\n"))
		}

	}

	return nil
}
