// 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 channel captures IPC implementation.
package channel

import (
	"errors"
	"runtime"
	"runtime/debug"
	"time"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"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"
	"github.com/aws/amazon-ssm-agent/common/message"
)

// IChannel interface for implementing various channels
type IChannel interface {
	Initialize(socketType utils.SocketType) error
	Send(message *message.Message) error
	Close() error
	Recv() ([]byte, error)
	SetOption(name string, value interface{}) error
	Listen(addr string) error
	Dial(addr string) error
	IsChannelInitialized() bool
	IsDialSuccessful() bool
	IsListenSuccessful() bool
}

var (
	newNamedPipeChannelRef     = NewNamedPipeChannel
	isDefaultChannelPresentRef = utils.IsDefaultChannelPresent

	ErrIPCChannelClosed       = errors.New("channel is closed")
	ErrDialListenUnSuccessful = errors.New("IPC connection is not established")
)

// GetChannelCreator returns function reference for channel creation based
// on whether named pipe be created or not
func GetChannelCreator(log log.T, appConfig appconfig.SsmagentConfig, identity identity.IAgentIdentity) (channelCreateFn func(log.T, identity.IAgentIdentity) IChannel) {
	if canUseNamedPipe(log, appConfig, identity) {
		return NewNamedPipeChannel
	}
	return NewFileChannel
}

// canUseNamedPipe checks whether named pipe can be used for IPC or not
func canUseNamedPipe(log log.T, appConfig appconfig.SsmagentConfig, identity identity.IAgentIdentity) (useNamedPipe bool) {
	// named pipes '.Listen' halts randomly on windows 2012, disabling named pipes on windows and using file channel instead
	// On few mac2.metal instances, socket creation is getting blocked. Hence, permanently falling back to File based IPC for Darwin.
	if getOS() == "windows" || getOS() == "darwin" {
		log.Infof("Not using named pipe on %v", getOS())
		return false
	}
	if appConfig.Agent.ForceFileIPC {
		log.Info("Not using named pipe as force file IPC is set")
		return false
	}
	namedPipeChan := newNamedPipeChannelRef(log, identity)
	namedPipeCreationChan := make(chan bool, 1)
	go func() {
		defer func() {
			if msg := recover(); msg != nil {
				log.Error("named pipe creation panicked")
				log.Errorf("stacktrace:\n%s", debug.Stack())
			}
		}()
		defer func() {
			if err := namedPipeChan.Close(); err != nil {
				log.Errorf("error while closing named pipe channel %v", err)
			}
		}()
		namedPipeChan.Initialize(utils.Surveyor)
		if err := namedPipeChan.Listen(utils.TestAddress); err == nil && !isDefaultChannelPresentRef(identity) {
			namedPipeCreationChan <- true
			return
		}
		log.Info("falling back to file based IPC as named pipe creation failed")
		namedPipeCreationChan <- false
	}()

	select {
	case creationSuccessFlag := <-namedPipeCreationChan:
		return creationSuccessFlag
	case <-time.After(10 * time.Second):
		log.Info("falling back to file based IPC after timeout")
		return false
	}
}

var getOS = func() string {
	return runtime.GOOS
}
