require 'open3'

class PortInUse < StandardError; end

class WsProxy
  attr_accessor :host, :host_port, :password, :timeout, :idle_timeout, :ssl_target
  attr_reader :proxy_port

  # Allowed ports to communicate with our web sockets proxy
  PORTS = 5910..5930

  def initialize(attributes)
    # setup all attributes.
    defaults.merge(attributes).each do |k, v|
      eval("self.#{k}= v") if self.respond_to?("#{k}=")
    end
  end

  def self.start attributes
    proxy = WsProxy.new(attributes)
    proxy.start_proxy
  end

  def start_proxy

    # try to execute our web sockets proxy
    port = PORTS.first
    begin
      cmd  = "#{ws_proxy} --daemon --idle-timeout=#{idle_timeout} --timeout=#{timeout} #{port} #{host}:#{host_port}"
      cmd += " --ssl-target" if ssl_target
      execute(cmd)
      # if the port is already in use, try another one from the pool
      # this is not ideal, as it would try all ports in order
      # but it avoids any threading issues etc.
      # TODO: try to select a port from a pool randomly, so we always hit all active connections.
    rescue PortInUse
      port += 1
      retry if port <= PORTS.last
    end
    @proxy_port = port

    { :host => host, :port => host_port, :password => password, :proxy_port => proxy_port }
  end

  private

  def ws_proxy
    "#{Rails.root}/extras/noVNC/websockify.py"
  end

  def defaults
    {
      :timeout      => 120,
      :idle_timeout => 120,
      :host_port    => 5900,
      :host         => "0.0.0.0",
    }
  end

  def logger
    Rails.logger
  end

  def execute cmd

    logger.debug "Starting VNC Proxy: #{cmd}"
    Open3::popen3(cmd) do |stdin, stdout, stderr|
      stdout.each do |line|
        logger.debug "[#{line}"
      end
      stderr.each do |line|
        logger.debug "VNCProxy Error: #{line}"
        raise PortInUse if line["socket.error: [Errno 98] Address already in use"]
      end
    end
  end

end
