require 'test/unit'
require 'mocha/setup'
require 'stringio'

begin
  require 'net/ssh'
  require 'net/ssh/version'
  raise LoadError, "wrong version" unless Net::SSH::Version::STRING >= '1.99.0'
rescue LoadError
  begin
    gem 'net-ssh', ">= 2.0.0"
    require 'net/ssh'
  rescue LoadError => e
    abort "could not load net/ssh v2 (#{e.inspect})"
  end
end

$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require 'net/sftp'
require 'net/sftp/constants'
require 'net/ssh/test'

class Net::SFTP::TestCase < Test::Unit::TestCase
  include Net::SFTP::Constants::PacketTypes
  include Net::SSH::Test

  def default_test
    # do nothing, this is just hacky-hack to work around Test::Unit's
    # insistence that all TestCase subclasses have at least one test
    # method defined.
  end

  protected

    def raw(*args)
      Net::SSH::Buffer.from(*args).to_s
    end

    def sftp(options={})
      @sftp ||= Net::SFTP::Session.new(connection(options))
    end

    def expect_sftp_session(opts={})
      story do |session|
        channel = session.opens_channel
        channel.sends_subsystem("sftp")
        channel.sends_packet(FXP_INIT, :long, opts[:client_version] || Net::SFTP::Session::HIGHEST_PROTOCOL_VERSION_SUPPORTED)
        channel.gets_packet(FXP_VERSION, :long, opts[:server_version] || Net::SFTP::Session::HIGHEST_PROTOCOL_VERSION_SUPPORTED)
        yield channel if block_given?
      end
    end

    def assert_scripted_command
      assert_scripted do
        sftp.connect!
        yield
        sftp.loop
      end
    end

    def assert_progress_reported_open(expect={})
      assert_progress_reported(:open, expect)
    end

    def assert_progress_reported_put(offset, data, expect={})
      assert_equal offset, current_event[3] if offset
      assert_equal data, current_event[4] if data
      assert_progress_reported(:put, expect)
    end

    def assert_progress_reported_get(offset, data, expect={})
      assert_equal offset, current_event[3] if offset
      if data.is_a?(Fixnum)
        assert_equal data, current_event[4].length
      elsif data
        assert_equal data, current_event[4]
      end
      assert_progress_reported(:get, expect)
    end

    def assert_progress_reported_close(expect={})
      assert_progress_reported(:close, expect)
    end

    def assert_progress_reported_mkdir(dir)
      assert_equal dir, current_event[2]
      assert_progress_reported(:mkdir)
    end

    def assert_progress_reported_finish
      assert_progress_reported(:finish)
    end

    def assert_progress_reported(event, expect={})
      assert_equal event, current_event[0]
      expect.each do |key, value|
        assert_equal value, current_event[2].send(key)
      end
      next_event!
    end

    def assert_no_more_reported_events
      assert @progress.empty?, "expected #{@progress.empty?} to be empty"
    end

    def prepare_progress!
      @progress = []
    end

    def record_progress(event)
      @progress << event
    end

    def current_event
      @progress.first
    end

    def next_event!
      @progress.shift
    end
end

class Net::SSH::Test::Channel
  def gets_packet(type, *args)
    gets_data(sftp_packet(type, *args))
  end

  def gets_packet_in_two(fragment_len, type, *args)
    fragment_len ||= 0
    whole_packet = sftp_packet(type, *args)

    if 0 < fragment_len && fragment_len < whole_packet.length
      gets_data(whole_packet[0, whole_packet.length - fragment_len])
      gets_data(whole_packet[-fragment_len..-1])
    else
      gets_data(whole_packet)
    end
  end

  def sends_packet(type, *args)
    sends_data(sftp_packet(type, *args))
  end

  private

    def sftp_packet(type, *args)
      data = Net::SSH::Buffer.from(*args)
      Net::SSH::Buffer.from(:long, data.length+1, :byte, type, :raw, data).to_s
    end
end

class ProgressHandler
  def initialize(progress_ref)
    @progress = progress_ref
  end

  def on_open(*args)
    @progress << [:open, *args]
  end

  def on_put(*args)
    @progress << [:put, *args]
  end

  def on_close(*args)
    @progress << [:close, *args]
  end

  def on_finish(*args)
    @progress << [:finish, *args]
  end
end

# "prime the pump", so to speak: predefine the modules we need so we can
# define the test classes in a more elegant short-hand.

module Protocol
  module V01; end
  module V02; end
  module V03; end
  module V04; end
  module V05; end
  module V06; end
end
