// Copyright (c) 2019 The Gnet Authors. 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 gnet

import (
	"time"

	"github.com/panjf2000/gnet/v2/pkg/logging"
)

// Option is a function that will set up option.
type Option func(opts *Options)

func loadOptions(options ...Option) *Options {
	opts := new(Options)
	for _, option := range options {
		option(opts)
	}
	return opts
}

// TCPSocketOpt is the type of TCP socket options.
type TCPSocketOpt int

// Available TCP socket options.
const (
	TCPNoDelay TCPSocketOpt = iota
	TCPDelay
)

// Options are configurations for the gnet application.
type Options struct {
	// ================================== Options for only server-side ==================================

	// Multicore indicates whether the engine will be effectively created with multi-cores, if so,
	// then you must take care with synchronizing memory between all event callbacks, otherwise,
	// it will run the engine with single thread. The number of threads in the engine will be
	// automatically assigned to the number of usable logical CPUs that can be leveraged by the
	// current process.
	Multicore bool

	// NumEventLoop is set up to start the given number of event-loop goroutines.
	// Note that a non-negative NumEventLoop will override Multicore.
	NumEventLoop int

	// LB represents the load-balancing algorithm used when assigning new connections
	// to event loops.
	LB LoadBalancing

	// ReuseAddr indicates whether to set the SO_REUSEADDR socket option.
	ReuseAddr bool

	// ReusePort indicates whether to set the SO_REUSEPORT socket option.
	ReusePort bool

	// MulticastInterfaceIndex is the index of the interface name where the multicast UDP addresses will be bound to.
	MulticastInterfaceIndex int

	// BindToDevice is the name of the interface to which the listening socket will be bound.
	//
	// It is only available on Linux at the moment, an error will therefore be returned when
	// setting this option on non-linux platforms.
	BindToDevice string

	// ============================= Options for both server-side and client-side =============================

	// ReadBufferCap is the maximum number of bytes that can be read from the remote when the readable event comes.
	// The default value is 64KB, it can either be reduced to avoid starving the subsequent connections or increased
	// to read more data from a socket.
	//
	// Note that ReadBufferCap will always be converted to the least power of two integer value greater than
	// or equal to its real amount.
	ReadBufferCap int

	// WriteBufferCap is the maximum number of bytes that a static outbound buffer can hold,
	// if the data exceeds this value, the overflow bytes will be stored in the elastic linked list buffer.
	// The default value is 64KB.
	//
	// Note that WriteBufferCap will always be converted to the least power of two integer value greater than
	// or equal to its real amount.
	WriteBufferCap int

	// LockOSThread is used to determine whether each I/O event-loop should be associated to an OS thread,
	// it is useful when you need some kind of mechanisms like thread local storage, or invoke certain C
	// libraries (such as graphics lib: GLib) that require thread-level manipulation via cgo, or want all I/O
	// event-loops to actually run in parallel for a potential higher performance.
	LockOSThread bool

	// Ticker indicates whether the ticker has been set up.
	Ticker bool

	// TCPKeepAlive enables the TCP keep-alive mechanism (SO_KEEPALIVE) and set its value
	// on TCP_KEEPIDLE, 1/5 of its value on TCP_KEEPINTVL, and 5 on TCP_KEEPCNT.
	TCPKeepAlive time.Duration

	// TCPNoDelay controls whether the operating system should delay
	// packet transmission in hopes of sending fewer packets (Nagle's algorithm).
	// When this option is assigned to TCPNoDelay, TCP_NODELAY socket option will
	// be turned on, on the contrary, if it is assigned to TCPDelay, the socket
	// option will be turned off.
	//
	// The default is TCPNoDelay, meaning that TCP_NODELAY is turned on and data
	// will not be buffered but sent as soon as possible after a write operation.
	TCPNoDelay TCPSocketOpt

	// SocketRecvBuffer sets the maximum socket receive buffer of kernel in bytes.
	SocketRecvBuffer int

	// SocketSendBuffer sets the maximum socket send buffer of kernel in bytes.
	SocketSendBuffer int

	// LogPath specifies a local path where logs will be written, this is the easiest
	// way to set up logging, gnet instantiates a default uber-go/zap logger with this
	// given log path, you are also allowed to employ your own logger during the lifetime
	// by implementing the following logging.Logger interface.
	//
	// Note that this option can be overridden by a non-nil option Logger.
	LogPath string

	// LogLevel specifies the logging level, it should be used along with LogPath.
	LogLevel logging.Level

	// Logger is the customized logger for logging info, if it is not set,
	// then gnet will use the default logger powered by go.uber.org/zap.
	Logger logging.Logger

	// EdgeTriggeredIO enables the edge-triggered I/O for the underlying epoll/kqueue event-loop.
	// Don't enable it unless you are 100% sure what you are doing.
	// Note that this option is only available for stream-oriented protocol.
	EdgeTriggeredIO bool

	// EdgeTriggeredIOChunk specifies the number of bytes that `gnet` can
	// read/write up to in one event loop of ET. This option implies
	// EdgeTriggeredIO when it is set to a value greater than 0.
	// If EdgeTriggeredIO is set to true and EdgeTriggeredIOChunk is not set,
	// 1MB is used. The value of EdgeTriggeredIOChunk must be a power of 2,
	// otherwise, it will be rounded up to the nearest power of 2.
	EdgeTriggeredIOChunk int
}

// WithOptions sets up all options.
func WithOptions(options Options) Option {
	return func(opts *Options) {
		*opts = options
	}
}

// WithMulticore enables multi-cores mode for gnet engine.
func WithMulticore(multicore bool) Option {
	return func(opts *Options) {
		opts.Multicore = multicore
	}
}

// WithLockOSThread enables LockOSThread mode for I/O event-loops.
func WithLockOSThread(lockOSThread bool) Option {
	return func(opts *Options) {
		opts.LockOSThread = lockOSThread
	}
}

// WithReadBufferCap sets ReadBufferCap for reading bytes.
func WithReadBufferCap(readBufferCap int) Option {
	return func(opts *Options) {
		opts.ReadBufferCap = readBufferCap
	}
}

// WithWriteBufferCap sets WriteBufferCap for pending bytes.
func WithWriteBufferCap(writeBufferCap int) Option {
	return func(opts *Options) {
		opts.WriteBufferCap = writeBufferCap
	}
}

// WithLoadBalancing picks the load-balancing algorithm for gnet engine.
func WithLoadBalancing(lb LoadBalancing) Option {
	return func(opts *Options) {
		opts.LB = lb
	}
}

// WithNumEventLoop sets the number of event loops for gnet engine.
func WithNumEventLoop(numEventLoop int) Option {
	return func(opts *Options) {
		opts.NumEventLoop = numEventLoop
	}
}

// WithReusePort sets SO_REUSEPORT socket option.
func WithReusePort(reusePort bool) Option {
	return func(opts *Options) {
		opts.ReusePort = reusePort
	}
}

// WithReuseAddr sets SO_REUSEADDR socket option.
func WithReuseAddr(reuseAddr bool) Option {
	return func(opts *Options) {
		opts.ReuseAddr = reuseAddr
	}
}

// WithTCPKeepAlive enables the TCP keep-alive mechanism and sets its values.
func WithTCPKeepAlive(tcpKeepAlive time.Duration) Option {
	return func(opts *Options) {
		opts.TCPKeepAlive = tcpKeepAlive
	}
}

// WithTCPNoDelay enable/disable the TCP_NODELAY socket option.
func WithTCPNoDelay(tcpNoDelay TCPSocketOpt) Option {
	return func(opts *Options) {
		opts.TCPNoDelay = tcpNoDelay
	}
}

// WithSocketRecvBuffer sets the maximum socket receive buffer of kernel in bytes.
func WithSocketRecvBuffer(recvBuf int) Option {
	return func(opts *Options) {
		opts.SocketRecvBuffer = recvBuf
	}
}

// WithSocketSendBuffer sets the maximum socket send buffer of kernel in bytes.
func WithSocketSendBuffer(sendBuf int) Option {
	return func(opts *Options) {
		opts.SocketSendBuffer = sendBuf
	}
}

// WithTicker indicates whether a ticker is currently set.
func WithTicker(ticker bool) Option {
	return func(opts *Options) {
		opts.Ticker = ticker
	}
}

// WithLogPath specifies a local path for logging file.
func WithLogPath(fileName string) Option {
	return func(opts *Options) {
		opts.LogPath = fileName
	}
}

// WithLogLevel specifies the logging level for the local logging file.
func WithLogLevel(lvl logging.Level) Option {
	return func(opts *Options) {
		opts.LogLevel = lvl
	}
}

// WithLogger specifies a customized logger.
func WithLogger(logger logging.Logger) Option {
	return func(opts *Options) {
		opts.Logger = logger
	}
}

// WithMulticastInterfaceIndex sets the interface name where UDP multicast sockets will be bound to.
func WithMulticastInterfaceIndex(idx int) Option {
	return func(opts *Options) {
		opts.MulticastInterfaceIndex = idx
	}
}

// WithBindToDevice sets the name of the interface to which the listening socket will be bound.
//
// It is only available on Linux at the moment, an error will therefore be returned when
// setting this option on non-linux platforms.
func WithBindToDevice(iface string) Option {
	return func(opts *Options) {
		opts.BindToDevice = iface
	}
}

// WithEdgeTriggeredIO enables the edge-triggered I/O for the underlying epoll/kqueue event-loop.
func WithEdgeTriggeredIO(et bool) Option {
	return func(opts *Options) {
		opts.EdgeTriggeredIO = et
	}
}

// WithEdgeTriggeredIOChunk sets the number of bytes that `gnet` can
// read/write up to in one event loop of ET.
func WithEdgeTriggeredIOChunk(chunk int) Option {
	return func(opts *Options) {
		opts.EdgeTriggeredIOChunk = chunk
	}
}