# This code is free software; you can redistribute it and/or modify it under
# the terms of the new BSD License.
#
# Copyright (c) 2008-2016, Sebastian Staudt

require 'ipaddr'
require 'socket'
require 'timeout'

require 'steam-condenser/error/rcon_no_auth'
require 'steam-condenser/error/rcon_ban'
require 'steam-condenser/error/timeout'
require 'steam-condenser/servers/packets/rcon/rcon_packet_factory'
require 'steam-condenser/servers/sockets/base_socket'

module SteamCondenser::Servers::Sockets

  # This class represents a socket used for RCON communication with game
  # servers based on the Source engine (e.g. Team Fortress 2, Counter-Strike:
  # Source)
  #
  # The Source engine uses a stateful TCP connection for RCON communication and
  # uses an additional socket of this type to handle RCON requests.
  #
  # @author Sebastian Staudt
  class RCONSocket

    include BaseSocket
    include SteamCondenser::Logging

    # Creates a new TCP socket to communicate with the server on the given IP
    # address and port
    #
    # @param [String, IPAddr] ip Either the IP address or the DNS name of the
    #        server
    # @param [Fixnum] port The port the server is listening on
    def initialize(ip, port)
      ip = IPSocket.getaddress(ip) unless ip.is_a? IPAddr

      @ip     = ip
      @port   = port
    end

    # Closes the underlying TCP socket if it exists
    #
    # SteamSocket#close
    def close
      super unless @socket.nil?
    end

    # Connects a new TCP socket to the server
    #
    # @raise [Error::Timeout] if the connection could not be
    #        established
    def connect
      ::Timeout.timeout(@@timeout / 1000.0) do
        @socket = TCPSocket.new @ip, @port
      end
    rescue ::Timeout::Error
      raise SteamCondenser::Error::Timeout
    end

    # Sends the given RCON packet to the server
    #
    # @param [Packets::RCON::BasePacket] data_packet The RCON packet to send to
    #        the server
    # @see #connect
    def send_packet(data_packet)
      connect if @socket.nil? || @socket.closed?

      super
    end

    # Reads a packet from the socket
    #
    # The Source RCON protocol allows packets of an arbitrary sice transmitted
    # using multiple TCP packets. The data is received in chunks and
    # concatenated into a single response packet.
    #
    # @raise [Error::RCONBan] if the IP of the local machine has been banned on
    #        the game server
    # @raise [Error::RCONNoAuth] if an authenticated connection has been
    #        dropped by the server
    # @return [Packets::RCON::BasePacket] The packet replied from the server
    def reply
      begin
        if receive_packet(4) == 0
          @socket.close
          return nil
        end
      rescue Errno::ECONNRESET
        return nil
      end

      remaining_bytes = @buffer.long

      packet_data = ''
      begin
        received_bytes = receive_packet remaining_bytes
        remaining_bytes -= received_bytes
        packet_data << @buffer.get
      end while remaining_bytes > 0

      packet = SteamCondenser::Servers::Packets::RCON::RCONPacketFactory.packet_from_data(packet_data)

      log.debug "Received packet of type \"#{packet.class}\"."

      packet
    end

  end
end
