require 'socket'

require 'ServerDyn.rb'
include ServerDyn

class UDPServer
  ###############################################################################
  # Init
  def initialize(port = 24242, packetSize = 64, debug = true)
    @port = port
    @packetSize = packetSize
    @server = UDPSocket.open
    @server.bind("0.0.0.0", @port)
    @debug = debug

    @clients = []
    @clientsName = []
    @clientsPort = []
    @clientsTime = []

    @idleTime = 60

    @time = Time.new
    @dynamicCode = true
  end
  ###############################################################################
  # Add a client to the list
  def addClient(ip, port)
    # First send all connected client to the new client
    clientID = 0
    @clients.each(){ |i|
      if @clientsName[clientID] != "NoName" then
        @server.send("0:#{@clientsName[clientID]}", 0, ip, port)
      end
      clientID += 1
    }
    # Then register our new client
    puts "Add client: #{ip}:#{port}"
    @clients += [ ip ]
    @clientsName += [ "NoName" ]
    @clientsPort += [ port ]
    @clientsTime += [ Time.new ]
  end
  ###############################################################################
  # Remove a client
  def removeClient(slot)
    puts "Del client: #{@clients[slot]}:#{@clientsPort[slot]} (#{slot})"
    @clients.delete_at(slot)
    @clientsName.delete_at(slot)
    @clientsPort.delete_at(slot)
    @clientsTime.delete_at(slot)
  end
  ###############################################################################
  # Get client ID by IP/PORT
  def getClientIDByIPPort(ip, port)
    clientID = 0
    @clients.each(){ |i|
      if port == "0" then port = @clientsPort[clientID]; end
      if (i == ip) and
         (@clientsPort[clientID] == port) then
        return clientID
        break
      end
      clientID += 1
    }
    return -1
  end
  ###############################################################################
  # Remove a client that does use a specific ip/port
  def removeClientByIPPort(ip, port)
    if (clientID = getClientIDByIPPort(ip, port)) != -1 then
      removeClient(clientID)
    end
  end
  ###############################################################################
  # Register a new client if needed
  def registerClient(ip, port)
    registred = false
    if (clientID = getClientIDByIPPort(ip, port)) != -1 then
      registred = true;
      # update IDLE time
      @clientsTime[clientID] = Time.now
      return
    end
    # register the client
    if not registred then
      addClient(ip, port)
    end
  end
  ###############################################################################
  # Remove IDLEing clients
  def removeIDLEClients()
    clientID = 0
    t = Time.now
    idleingClients = []
    # Searching idleing clients
    @clientsTime.each(){ |i|
      if t - i > @idleTime then
        idleingClients += [ clientID ] 
      end
      clientID += 1
    }
    # Removing idleing client
    0.upto(idleingClients.size - 1){ |i|
      clientID = idleingClients[i]
      begin
              broadcastFrom("1:#{@clientsName[clientID]}", "SERVER", 0)
              @server.send("Disconnected:IDLEing too much\n", 0,
                                         @clients[clientID],
                                           @clientsPort[clientID])
      rescue
        puts "removeIDLEClients():broadcast/send error..."
      end
      removeClient(clientID)
    }
  end
  ###############################################################################  
  # Broadcast
  def broadcastFrom(msg, ip, port, echoes = false)
    clientID = 0
    @clients.each(){ |i|
      if ((i != ip) or (@clientsPort[clientID] != port)) or (echoes) then
        #@server.send("BROADCAST FROM(#{ip}:#{port}): "+msg, 0, i, @clientsPort[clientID])
        @server.send(msg, 0, i, @clientsPort[clientID])
        #puts "sending to: #{i} : #{@clientsPort[clientID]}"
      end
      clientID += 1
    }
  end
  ###############################################################################
  # Set client Name
  def setClientName(ip, port, name)
    if (clientID = getClientIDByIPPort(ip, port)) != -1 then
        @clientsName[clientID] = name
       puts "client(#{ip}:#{port}) name(#{@clientsName[clientID]})"
     end
  end
  ###############################################################################
  # Server loop
  def runLoop()
    while 1 do
      if @debug then
        puts "waiting packet(blocking)..."
      end
      a = @server.recvfrom(@packetSize)
      pData, pProto, pSourcePort, pDNS, pIP =
        a[0], a[1][0], a[1][1], a[1][2], a[1][3]

      if @debug then
        puts "Data...: #{pData}" +
             "Proto..: #{pProto}\n" +
             "SrcPort: #{pSourcePort}\n" +
             "DNS....: #{pDNS}\n" +
             "IP.....: #{pIP}\n"
      end

      if pData =~ /^getStats\n/ then
        @server.send("total_client: #{@clients.size}\n", 0, pIP, pSourcePort)
        clientID = 0
        @clients.each(){ |i|
          @server.send(" client ID(#{clientID}) NAME(#{@clientsName[clientID]}) " +
                 "IP(#{i}) SP(#{@clientsPort[clientID]})" +
                 " IDLE(#{@clientsTime[clientID]})\n", 0, pIP, pSourcePort)
          clientID += 1
        }
      elsif pData =~ /^sync\n/ then
        begin
          load 'ServerDyn.rb'
           @server.send("Synced\n", 0, pIP, pSourcePort)
           @dynamicCode = true
        rescue
           @server.send("ERROR Syncing! dynamic code will not works\n", 0, pIP, pSourcePort)                    @dynamicCode = false
        end
      elsif pData =~ /^del / then
        removeIP = pData.gsub(/^del /, '')
        removeIP.chomp!
        removeClientByIPPort(removeIP, 0)
      end

      registerClient(pIP, pSourcePort)
      broadcastFrom(pData, pIP, pSourcePort)

      if @dynamicCode then
        begin
          ServerDyn.parseData(pData, pIP, pSourcePort)
        rescue
          @server.send("ERROR Dynamic code section broken\n", 0, pIP, pSourcePort)
          @dynamicCode = false
        end
      end

      if pData =~ /^0:/ then
        if @debug then puts "-> connect packet"; end
                        name = pData.gsub(/.*:/, '')
        name.chomp!
        setClientName(pIP, pSourcePort, name)
      elsif pData =~ /^1:/ then
        if @debug then puts "-> disconnect packet"; end
        removeClientByIPPort(pIP, pSourcePort)
      end

    end
  end
  ###############################################################################
  # Idle loop
  def checkIDLELoop()
    while 1 do
      sleep @idleTime
      @time = Time.new
      removeIDLEClients()
    end
  end

end

mserv = UDPServer.new()

# TODO IDLE MUTEX..

# Server thread
serverThread = Thread.new {
  mserv.runLoop()
}

# IDLEing clients
checkIDLE = Thread.new {
  mserv.checkIDLELoop()
}

checkIDLE.join
serverThread.join