// Copyright 2020 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 filewatcherbasedipc is used to establish IPC between master and workers using files.
package filewatcherbasedipc

import (
	"os"
	"path"

	"github.com/aws/amazon-ssm-agent/agent/fileutil"
	"github.com/aws/amazon-ssm-agent/agent/log"
	"github.com/aws/amazon-ssm-agent/common/channel/utils"
	"github.com/aws/amazon-ssm-agent/common/identity"
)

const (
	ModeMaster     Mode = "master"
	ModeWorker     Mode = "worker"
	ModeSurveyor   Mode = "surveyor"
	ModeRespondent Mode = "respondent"
)

type Mode string

// IPCChannel is defined as a persistent interface for raw json datagram transmission, it is designed to adopt both file ad named pipe
type IPCChannel interface {
	// Send sends a raw json datagram to the channel, return when send is "complete" -- message is dropped to the persistent layer
	Send(string) error
	// GetMessage receives a datagram, the go channel on the other end is closed when channel is closed
	GetMessage() <-chan string
	//Close safely release all in memory resources -- drain the sending/receiving/queue and GetMessage() go channel, channel is reusable after close
	Close()
	//Destroy destroys the persistent channel transport, channel is no longer reusable after destroy
	Destroy()
	// CleanupOwnModeFiles cleans up it own mode files
	CleanupOwnModeFiles()
	// GetPath returns IPC filepath
	GetPath() string
}

// IsFileWatcherChannelPresent checks whether the file watcher channel is present or not
func IsFileWatcherChannelPresent(identity identity.IAgentIdentity, channelName string) (bool, error) {
	channelPath, err := utils.GetDefaultChannelPath(identity, channelName)
	if err != nil {
		return false, err
	}
	if _, err = os.Stat(channelPath); os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

// find the folder named as "documentID" under the default root dir
// if not found, create a new filechannel under the default root dir
// return the channel and the found flag
// shouldReadRetry - is this flag is set to true, it will use fileReadWithRetry function to read
func CreateFileWatcherChannel(log log.T, identity identity.IAgentIdentity, mode Mode, filename string, shouldReadRetry bool) (IPCChannel, error, bool) {
	rootChannelDir, err := utils.GetDefaultChannelPath(identity, "")
	if err != nil {
		return nil, err, false
	}
	list, err := fileutil.ReadDir(rootChannelDir)
	if err != nil {
		log.Infof("failed to read the default channel root directory: %v, creating a new Channel", err)
		f, err := NewFileWatcherChannel(log, mode, path.Join(rootChannelDir, filename), shouldReadRetry)
		return f, err, false
	}
	for _, val := range list {
		if val.Name() == filename {
			log.Debugf("channel: %v found", filename)
			f, err := NewFileWatcherChannel(log, mode, path.Join(rootChannelDir, filename), shouldReadRetry)
			return f, err, true
		}
	}
	log.Debugf("channel: %v not found, creating a new file channel...", filename)
	f, err := NewFileWatcherChannel(log, mode, path.Join(rootChannelDir, filename), shouldReadRetry)
	return f, err, false
}

// same as CreateFileWatcherChannel but with limit on max number of files
func CreateRollingFileWatcherChannel(log log.T, identity identity.IAgentIdentity, mode Mode, filename string, shouldReadRetry bool, maxFiles int) (IPCChannel, error, bool) {
	f, err, found := CreateFileWatcherChannel(log, identity, mode, filename, shouldReadRetry)

	if err == nil {
		fwc := f.(*fileWatcherChannel)
		fwc.maxFiles = maxFiles
	}

	return f, err, found
}

// RemoveFileWatcherChannel removes the channel folder specific to the command
func RemoveFileWatcherChannel(identity identity.IAgentIdentity, channelName string) error {
	channelPath, err := utils.GetDefaultChannelPath(identity, channelName)

	if err == nil {
		if _, fileStatErr := os.Stat(channelPath); os.IsNotExist(fileStatErr) {
			return nil
		}
		err = os.RemoveAll(channelPath)
	}
	return err
}
