// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 options

import (
	"log"
	"os"
	"path/filepath"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/cobra"
)

// FilenameOptions is from pkg/kubectl.
type FilenameOptions struct {
	Filenames []string
	Recursive bool
	Watch     bool
}

func AddFileArg(cmd *cobra.Command, fo *FilenameOptions) {
	// From pkg/kubectl
	cmd.Flags().StringSliceVarP(&fo.Filenames, "filename", "f", fo.Filenames,
		"Filename, directory, or URL to files to use to create the resource")
	cmd.Flags().BoolVarP(&fo.Recursive, "recursive", "R", fo.Recursive,
		"Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
	cmd.Flags().BoolVarP(&fo.Watch, "watch", "W", fo.Watch,
		"Continuously monitor the transitive dependencies of the passed yaml files, and redeploy whenever anything changes.")
}

// Based heavily on pkg/kubectl
func EnumerateFiles(fo *FilenameOptions) chan string {
	files := make(chan string)
	go func() {
		// When we're done enumerating files, close the channel
		defer close(files)
		// When we are in --watch mode, we set up watches on the filesystem locations
		// that we are supplied and continuously stream files, until we are sent an
		// interrupt.
		var watcher *fsnotify.Watcher
		if fo.Watch {
			var err error
			watcher, err = fsnotify.NewWatcher()
			if err != nil {
				log.Fatalf("Unexpected error initializing fsnotify: %v", err)
			}
			defer watcher.Close()
		}
		for _, paths := range fo.Filenames {
			// Just pass through '-' as it is indicative of stdin.
			if paths == "-" {
				files <- paths
				continue
			}
			// For each of the "filenames" we are passed (file or directory) start a
			// "Walk" to enumerate all of the contained files recursively.
			err := filepath.Walk(paths, func(path string, fi os.FileInfo, err error) error {
				if err != nil {
					return err
				}

				// If this is a directory, skip it if it isn't the current directory we are
				// processing (unless we are in recursive mode).  If we decide to process
				// the directory, and we're in watch mode, then we set up a watch on the
				// directory.
				if fi.IsDir() {
					if path != paths && !fo.Recursive {
						return filepath.SkipDir
					}
					if watcher != nil {
						watcher.Add(path)
					}
					// We don't stream back directories, we just decide to skip them, or not.
					return nil
				}

				// Don't check extension if the filepath was passed explicitly
				if path != paths {
					switch filepath.Ext(path) {
					case ".json", ".yaml":
						// Process these.
					default:
						return nil
					}
					// We weren't passed this explicitly, so elide the watch as we
					// are already watching the directory.
				} else {
					// We were passed this directly, and so we may not be watching the
					// directory, so watch this file explicitly.
					if watcher != nil {
						watcher.Add(path)
					}
				}

				files <- path
				return nil
			})
			if err != nil {
				log.Fatalf("Error enumerating files: %v", err)
			}
		}

		// We're done watching the files we were passed and setting up watches.
		// Now listen for change events from the watches we set up and resend
		// files that change as if we just saw them (so they can be reprocessed).
		if watcher != nil {
			for {
				select {
				case event := <-watcher.Events:
					switch filepath.Ext(event.Name) {
					case ".json", ".yaml":
						files <- event.Name
					}
				case err := <-watcher.Errors:
					log.Fatalf("Error watching: %v", err)
				}
			}
		}
	}()
	return files
}
