// Copyright 2018 Amazon.com, Inc. or its affiliates. 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. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 log is used to initialize logger
package log

import (
	"path/filepath"

	"github.com/fsnotify/fsnotify"
)

// IFileWatcher interface for FileWatcher with functions to initialize, start and stop the watcher
type IFileWatcher interface {
	Init(log T, configFilePath string, replaceLogger func())
	Start()
	Stop()
}

// FileWatcher implements the IFileWatcher by using fileChangeWatcher and fileExistsWatcher
type FileWatcher struct {
	configFilePath string
	replaceLogger  func()
	log            T
	watcher        *fsnotify.Watcher
}

// Init initializes the data and channels for the filewatcher
func (fileWatcher *FileWatcher) Init(log T, configFilePath string, replaceLogger func()) {
	fileWatcher.replaceLogger = replaceLogger
	fileWatcher.configFilePath = configFilePath
	fileWatcher.log = log
}

// Start creates and starts the go routines for filewatcher
func (fileWatcher *FileWatcher) Start() {

	fileWatcher.log.Debugf("Start File Watcher On: %v", fileWatcher.configFilePath)

	// Since the filewatcher fails if the file does not exist, need to watch the parent directory for any changes
	dirPath := filepath.Dir(fileWatcher.configFilePath)
	fileWatcher.log.Debugf("Start Watcher on directory: %v", dirPath)

	// Creating Watcher
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		// Error initializing the watcher
		fileWatcher.log.Errorf("Error initializing the watcher: %v", err)
		return
	}

	fileWatcher.watcher = watcher

	// Starting the goroutine for event handler
	go fileWatcher.fileEventHandler()

	// Add the directory to watcher
	err = fileWatcher.watcher.Add(dirPath)
	if err != nil {
		// Error adding the file to watcher
		fileWatcher.log.Errorf("Error adding the directory to watcher: %v", err)
		return
	}
}

// fileEventHandler implements handling of the events triggered by the OS
func (fileWatcher *FileWatcher) fileEventHandler() {

	// Waiting on signals from OS
	for event := range fileWatcher.watcher.Events {
		// Event signalled by OS on file
		fileWatcher.log.Debugf("Event on file %v : %v", event.Name, event)
		if event.Name == fileWatcher.configFilePath {
			// Event on the file being watched
			if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Rename == fsnotify.Rename {
				// One of Write or Create or Rename Event
				fileWatcher.log.Debugf("File Watcher Triggers Function Execution: %v", fileWatcher.configFilePath)
				// Execute the function
				fileWatcher.replaceLogger()
			}
		}
	}
}

// Stop stops the filewatcher
func (fileWatcher *FileWatcher) Stop() {
	fileWatcher.log.Infof("Stop the filewatcher on :%v", fileWatcher.configFilePath)
	// Check if watcher instance is set
	if fileWatcher.watcher != nil {
		err := fileWatcher.watcher.Close()
		if err != nil {
			// Error closing the filewatcher. Logging the error
			fileWatcher.log.Debugf("Error Closing the filewatcher :%v", err)
		}
	}
}