require 'logger'
require 'socket'

module RubySMB
  # This class provides the SMB server core. Settings that are relevant server wide are managed by this object.
  # Currently, the server only supports negotiating and authenticating requests. No other server functionality is
  # available at this time. The negotiating and authentication is supported for SMB versions 1 through 3.1.1.
  class Server
    require 'ruby_smb/server/cli'
    require 'ruby_smb/server/server_client'
    require 'ruby_smb/server/session'
    require 'ruby_smb/server/share'
    require 'ruby_smb/gss/provider/ntlm'

    Connection = Struct.new(:client, :thread)

    # @param server_sock the socket on which the server should listen
    # @param [Gss::Provider] gss_provider the authentication provider
    # @param [::Logger] logger the logger to use for diagnostic messages
    # @param thread_factory a block to create threads for serving clients
    def initialize(server_sock: nil, gss_provider: nil, logger: nil, thread_factory: nil)
      server_sock = ::TCPServer.new(445) if server_sock.nil?

      @guid = Random.new.bytes(16)
      @socket = server_sock
      @connections = []
      @gss_provider = gss_provider || Gss::Provider::NTLM.new
      # reject the wildcard dialect because it's not a real dialect we can use for this purpose
      @dialects = RubySMB::Dialect::ALL.keys.reject { |dialect| dialect == "0x%04x" % RubySMB::SMB2::SMB2_WILDCARD_REVISION }.reverse

      case logger
      when nil
        @logger = Logger.new(File.open(File::NULL, 'w'))
      when :stdout
        @logger = Logger.new(STDOUT)
      when :stderr
        @logger = Logger.new(STDERR)
      else
        @logger = logger
      end

      if thread_factory.nil?
        # the default thread factory uses Ruby's standard Thread#new
        thread_factory = Proc.new do |_server_client, &block|
          Thread.new(&block)
        end
      end
      @thread_factory = thread_factory

      # share name => provider instance
      @shares = {
        'IPC$' => Share::Provider::IpcPipe.new
      }
    end

    # @param [Share::Provider::Base] share_provider the share provider to register
    def add_share(share_provider)
      logger.debug("Adding #{share_provider.type} share: #{share_provider.name}")
      @shares[share_provider.name] = share_provider
    end

    # @param [String, Share::Provider::Base] share_provider the share provider to deregister
    def remove_share(share_provider)
      share_provider = share_provider.name if share_provider.is_a?(RubySMB::Server::Share::Provider::Base)
      logger.debug("Removing share: #{share_provider}")
      @shares.delete(share_provider)

      nil
    end

    # Run the server and accept any connections. For each connection, the block will be executed if specified. When the
    # block returns false, the loop will exit and the server will no long accept new connections.
    def run(&block)
      loop do
        sock = @socket.accept
        server_client = ServerClient.new(self, RubySMB::Dispatcher::Socket.new(sock, read_timeout: nil))
        @connections << Connection.new(server_client, @thread_factory.call(server_client) { server_client.run })

        break unless block.nil? || block.call(server_client)
      end
    end

    # The dialects that this server will negotiate with clients, in ascending order of preference.
    # @!attribute [rw] dialects
    #   @return [Array<String>]
    attr_accessor :dialects

    # The GSS Provider instance that this server will use to authenticate
    # incoming client connections.
    # @!attribute [r] gss_provider
    #   @return [RubySMB::Gss::Provider::Base]
    attr_reader :gss_provider

    # The 16 byte GUID that uniquely identifies this server instance.
    # @!attribute [r] guid
    attr_reader :guid

    # The logger instance to use for diagnostic messages.
    # @!attribute [r] logger
    attr_reader :logger

    # The shares that are provided by this server
    # @!attribute [r] shares
    attr_reader :shares
  end
end

