require 'mysqlplus'

class MysqlTest
  
  class NotImplemented < StandardError
  end
  
  attr_accessor :queries,
                :context,
                :connections,
                :connection_signature,
                :start,
                :done,
                :query_with,
                :per_query_overhead,
                :timeout
  
  def initialize( queries, context = '' )
    @queries = queries
    @context = context
    @done = []
    @query_with = :async_query
    @per_query_overhead = 3
    @timeout = 20
    yield self if block_given?
  end
  
  def setup( &block )
    @start = Time.now
    @connection_signature = block
  end
  
  def run!
    c_or_native_ruby_async_query do
      present_context if context?
      prepare
      yield
    end  
  end
  
  def per_query_overhead=( overhead )
    @per_query_overhead = ( overhead == :random ) ? rand() : overhead
  end
  
  protected

  def prepare
    raise NotImplemented
  end
  
  def teardown
    raise NotImplemented
  end
  
  def log( message, prefix = '' )
    puts "[#{timestamp}] #{prefix} #{message}"
  end
  
  def with_logging( message )
    log( message, 'Start' )
    yield
    log( message, 'End' )
  end
  
  def timestamp
    Time.now - @start
  end

  def context?
    @context != ''
  end
  
  def present_context
    log "#############################################"
    log "#  #{@context}" 
    log "#############################################"   
  end
  
  def c_or_native_ruby_async_query
    if @query_with == :c_async_query
      log "** using C based async_query"
    else
      log "** using native Ruby async_query"
    end
    yield
  end
  
  def dispatch_query( connection, sql, timeout = nil )
    connection.send( @query_with, sql, timeout )
  end
  
end

class EventedMysqlTest < MysqlTest
  
  attr_accessor :sockets
  
  def initialize( queries, context = '' )
    @sockets = []
    @connections = {}
    super( queries, context )
  end  
  
  def setup( &block )
    super( &block )
    with_logging 'Setup connection pool' do
      @queries.times do 
        connection = @connection_signature.call
        @connections[ IO.new(connection.socket) ] = connection
        @sockets = @connections.keys
      end
    end  
  end
  
  def run!
    super do
      catch :END_EVENT_LOOP do
        loop do
          result = select( @sockets,nil,nil,nil )
          if result
            result.first.each do |conn|
              @connections[conn].get_result.each{|res| log( "Result for socket #{conn.fileno} : #{res}" ) }
              @done << nil
              if done?
                teardown
              end  
            end 
          end
        end    
      end
    end
  end
  
  protected
  
  def prepare
    @connections.each_value do |conn|
      conn.send_query( "select sleep(#{@per_query_overhead})" ) 
    end
  end
  
  def teardown
    log "done"
    throw :END_EVENT_LOOP
  end
  
  def done?
    @done.size == @queries
  end
  
end

class ThreadedMysqlTest < MysqlTest
  
  attr_accessor :threads
  
  def initialize( queries, context = '' )
    @connections = []
    @threads = []
    super( queries, context )
  end
  
  def setup( &block )
    super( &block )
    with_logging "Setup connection pool" do
      @queries.times do 
        @connections << @connection_signature.call
      end
    end
  end
  
  def run!
    super do
      with_logging "waiting on threads" do
        @threads.each{|t| t.join }
      end
    end  
  end 
  
  protected
  
  def prepare
    with_logging "prepare" do
      @queries.times do |conn|
        @threads << Thread.new do

          log "sending query on connection #{conn}"

          dispatch_query( @connections[conn], "select sleep(#{@per_query_overhead})", @timeout ).each do |result|
            log "connection #{conn} done"
          end 
        
        end
      end  
    end
  end
  
end
