#
# = test/unit/bio/io/test_flatfile.rb - unit test for Bio::FlatFile
#
#   Copyright (C) 2006 Naohisa Goto <ng@bioruby.org>
#
# License:: The Ruby License
#
#  $Id:$
#

# loading helper routine for testing bioruby
require 'pathname'
load Pathname.new(File.join(File.dirname(__FILE__), ['..'] * 3,
                            'bioruby_test_helper.rb')).cleanpath.to_s

# libraries needed for the tests
require 'test/unit'
require 'bio'
require 'stringio'

module Bio
  module TestFlatFile
    TestDataPath = BioRubyTestDataPath
    TestData01 = File.join(TestDataPath, 'fasta', 'example1.txt')

    TestData01Ent1def =
      "At1g02580 mRNA (2291 bp) UTR's and CDS"
    TestData01Ent4def =
      'At1g65300: mRNA 837bp (shortened from start)'
    TestData01Ent4naseq = Bio::Sequence::NA.new <<~__END_OF_SEQ__
      ttcatctttacctcttcctattgttgcgaatgcagctgcaccagtcg
      gatttgatggtcctatgtttcaatatcataatcaaaatcagcaaaagccggttcaattccaatatcaggctcttta
      tgatttttatgatcagattccaaagaaaattcatggttttaatatgaatatgaataaggattcgaatcaaagtatg
      gttttggatttgaatcaaaatcttaatgatggagaggacgagggcattccttgcatggacaacaacaactaccacc
      ccgaaatcgattgtctcgctaccgtcaccactgcccccactgatgtttgtgctcctaacatcaccaatgatctcta
      g
    __END_OF_SEQ__

    # test Bio::FlatFile class method
    class TestFlatFileClassMethod < Test::Unit::TestCase
      def setup
        @filename = TestData01
        @klass = Bio::FastaFormat
      end

      # test template for Bio::FlatFile.open
      def open_TestData01(*arg)
        assert_instance_of(Bio::FlatFile,
                           ff = Bio::FlatFile.open(*arg))
        assert_equal(@klass, ff.dbclass)
        assert_nil(ff.close)
      end
      private :open_TestData01

      # test template for Bio::FlatFile.open with block
      def open_with_block_TestData01(*arg)
        ret = Bio::FlatFile.open(*arg) do |ff|
          assert_instance_of(Bio::FlatFile, ff)
          assert_equal(@klass, ff.dbclass)
          ff.each do |e|
            assert_instance_of(@klass, e)
            assert_instance_of(String, ff.entry_raw)
          end
          'test return value'
        end
        assert_equal('test return value', ret)
      end
      private :open_with_block_TestData01

      def test_open_0arg
        assert_raise(ArgumentError) { Bio::FlatFile.open }
      end

      def test_open_1arg_nil
        assert_raise(ArgumentError) { Bio::FlatFile.open(nil) }
      end

      def test_open_1arg_class
        assert_raise(ArgumentError) { Bio::FlatFile.open(Bio::GenBank) }
      end

      def test_open_1arg_filename
        open_TestData01(@filename)
      end

      def test_open_1arg_io
        io = File.open(@filename)
        open_TestData01(io)
        assert(io.closed?)
      end

      def test_open_1arg_with_block
        open_with_block_TestData01(@filename)
      end

      def test_open_1arg_io_with_block
        io = File.open(@filename)
        open_with_block_TestData01(io)
        # When IO object is given, the IO is NOT automatically closed.
        assert_equal(false, io.closed?)
        assert_nothing_raised { io.close }
      end

      def test_open_2arg_autodetect
        open_TestData01(nil, @filename)
      end

      def test_open_2arg_autodetect_with_block
        open_with_block_TestData01(nil, @filename)
      end

      def test_open_2arg_autodetect_io
        io = File.open(@filename)
        open_TestData01(nil, io)
        assert(io.closed?)
      end

      def test_open_2arg_autodetect_io_with_block
        io = File.open(@filename)
        open_with_block_TestData01(nil, io)
        # When IO object is given, the IO is NOT automatically closed.
        assert_equal(false, io.closed?)
        assert_nothing_raised { io.close }
      end

      def test_open_2arg_class
        open_TestData01(@klass, @filename)
      end

      def test_open_2arg_class_with_block
        open_with_block_TestData01(@klass, @filename)
      end

      def test_open_2arg_class_io
        io = File.open(@filename)
        open_TestData01(@klass, io)
        assert(io.closed?)
      end

      def test_open_2arg_class_io_with_block
        io = File.open(@filename)
        open_with_block_TestData01(@klass, io)
        # When IO object is given, the IO is NOT automatically closed.
        assert_equal(false, io.closed?)
        assert_nothing_raised { io.close }
      end

      def test_open_2arg_filename_mode
        open_TestData01(@filename, 'r')
      end

      def test_open_2arg_filename_mode_with_block
        open_with_block_TestData01(@filename, 'r')
      end

      def test_open_3arg
        open_TestData01(nil, @filename, 'r')
        open_TestData01(@klass, @filename, 'r')
        open_TestData01(@filename, File::RDONLY, 0)
      end

      def test_open_3arg_with_block
        open_with_block_TestData01(nil, @filename, 'r')
        open_with_block_TestData01(@klass, @filename, 'r')
        open_with_block_TestData01(@filename, File::RDONLY, 0)
      end

      def test_open_4arg
        open_TestData01(nil, @filename, File::RDONLY, 0)
        open_TestData01(Bio::FastaFormat,
                        @filename, File::RDONLY, 0)

        open_with_block_TestData01(nil, @filename, File::RDONLY, 0)
        open_with_block_TestData01(Bio::FastaFormat,
                                   @filename, File::RDONLY, 0)
      end

      # test template for Bio::FlatFile.auto
      def auto_TestData01(*arg)
        assert_instance_of(Bio::FlatFile,
                           ff = Bio::FlatFile.auto(*arg))
        assert_equal(@klass, ff.dbclass)
        assert_nil(ff.close)
      end
      private :auto_TestData01

      # test template for Bio::FlatFile.auto with block
      def auto_with_block_TestData01(*arg)
        ret = Bio::FlatFile.auto(*arg) do |ff|
          assert_instance_of(Bio::FlatFile, ff)
          assert_equal(@klass, ff.dbclass)
          ff.each do |e|
            assert_instance_of(@klass, e)
            assert_instance_of(String, ff.entry_raw)
          end
          'test return value'
        end
        assert_equal('test return value', ret)
      end
      private :auto_with_block_TestData01

      def test_auto_0arg
        assert_raise(ArgumentError) { Bio::FlatFile.auto }
      end

      def test_auto_1arg_filename
        auto_TestData01(@filename)
      end

      def test_auto_1arg_io
        io = File.open(@filename)
        auto_TestData01(io)
        assert(io.closed?)
      end

      def test_auto_1arg_with_block
        auto_with_block_TestData01(@filename)
      end

      def test_auto_1arg_io_with_block
        io = File.open(@filename)
        auto_with_block_TestData01(io)
        # When IO object is given, the IO is NOT automatically closed.
        assert_equal(false, io.closed?)
        assert_nothing_raised { io.close }
      end

      def test_auto_2arg_filename_mode
        auto_TestData01(@filename, 'r')
      end

      def test_auto_2arg_filename_mode_with_block
        auto_with_block_TestData01(@filename, 'r')
      end

      def test_auto_3arg
        auto_TestData01(@filename, File::RDONLY, 0)
      end

      def test_auto_3arg_with_block
        auto_with_block_TestData01(@filename, File::RDONLY, 0)
      end

      def test_to_a
        assert_instance_of(Array,
                           a = Bio::FlatFile.to_a(@filename))
        assert_equal(5, a.size)
        assert_instance_of(Bio::FastaFormat, a[3])
        assert_equal(TestData01Ent4def,
                     a[3].definition)

        assert_equal(TestData01Ent4naseq, a[3].naseq)
      end

      def test_foreach
        Bio::FlatFile.foreach(@filename) do |ent|
          assert_instance_of(Bio::FastaFormat, ent)
        end
      end

      def test_new_2arg_nil
        io = File.open(@filename)
        assert_instance_of(Bio::FlatFile,
                           ff = Bio::FlatFile.new(nil, io))
        assert_equal(@klass, ff.dbclass)
        assert_nil(ff.close)
      end

      def test_new_2arg_class
        io = File.open(@filename)
        assert_instance_of(Bio::FlatFile,
                           ff = Bio::FlatFile.new(@klass, io))
        assert_equal(@klass, ff.dbclass)
        assert_nil(ff.close)
      end
    end # class TestFlatFileClassMethod

    # test Bio::FlatFile instance methods
    class TestFlatFileFastaFormat < Test::Unit::TestCase
      def setup
        @klass = Bio::FastaFormat
        @filename = TestData01
        @ff = Bio::FlatFile.open(@klass, @filename)
      end

      def test_to_io
        assert_instance_of(File, @ff.to_io)
      end

      def test_path
        assert_equal(@filename, @ff.path)
      end

      def test_next_entry
        assert_instance_of(@klass, ent = @ff.next_entry)
        assert_equal(TestData01Ent1def, ent.definition)
        assert_instance_of(@klass, @ff.next_entry)
        assert_instance_of(@klass, @ff.next_entry)
        assert_instance_of(@klass, ent = @ff.next_entry)
        assert_equal(TestData01Ent4def, ent.definition)
        assert_equal(TestData01Ent4naseq, ent.naseq)
      end

      def test_entry_raw
        4.times { @ff.next_entry }
        assert_instance_of(String, str = @ff.entry_raw)
        assert_equal(TestData01Ent4def, @klass.new(str).definition)
        assert_equal(TestData01Ent4naseq, @klass.new(str).naseq)
      end

      def test_entry_pos_flag
        # default is nil
        assert_equal(nil, @ff.entry_pos_flag)
        # set as true
        assert_equal(true, @ff.entry_pos_flag = true)
        assert_equal(true, @ff.entry_pos_flag)
      end

      def test_start_pos_ended_pos_not_recorded
        # default is nil
        assert_equal(nil, @ff.entry_start_pos)
        @ff.entry_pos_flag = false
        @ff.next_entry
        # nil if not recorded
        assert_equal(nil, @ff.entry_start_pos)
        assert_equal(nil, @ff.entry_ended_pos)
        @ff.next_entry
        # nil if not recorded
        assert_equal(nil, @ff.entry_start_pos)
        assert_equal(nil, @ff.entry_ended_pos)
      end

      def test_start_pos
        @ff.entry_pos_flag = true
        @ff.next_entry
        assert_equal(0, @ff.entry_start_pos)
        @ff.next_entry
        # On Windows, the values might be different.
        assert_equal(2367, @ff.entry_start_pos)
      end

      def test_ended_pos
        @ff.entry_pos_flag = true
        @ff.next_entry
        # On Windows, the values might be different.
        assert_equal(2367, @ff.entry_ended_pos)
        @ff.next_entry
        # On Windows, the values might be different.
        assert_equal(3244, @ff.entry_ended_pos)
      end

      def test_each_entry
        i = 0
        @ff.each_entry do |ent|
          assert_instance_of(@klass, ent)
          i += 1
          if i == 4
            assert_equal(TestData01Ent4def, ent.definition)
            assert_equal(TestData01Ent4naseq, ent.naseq)
          end
        end
      end

      # each is an alias of each_entry
      def test_each
        assert_nothing_raised { @ff.each {} }
      end

      def test_rewind
        @ff.next_entry
        assert_not_equal(0, @ff.pos)
        assert_equal(0, @ff.rewind)
        assert_equal(0, @ff.pos)
      end

      def test_close
        assert_nil(@ff.close)
      end

      def test_pos
        assert_equal(0, @ff.pos)
        @ff.next_entry
        assert_not_equal(0, @ff.pos)
      end

      def test_eof?
        5.times { @ff.next_entry }
        assert_equal(true, @ff.eof?)
      end

      def test_raw
        # default false
        assert_equal(false, @ff.raw)
        # changes to true
        assert_equal(true, @ff.raw = true)
        @ff.each do |ent|
          assert_instance_of(String, ent)
        end
      end

      def test_dbclass
        assert_equal(@klass, @ff.dbclass)
      end

      def test_dbclass_eq
        klass = Bio::FastaNumericFormat
        assert_equal(klass, @ff.dbclass = klass)
        assert_equal(klass, @ff.dbclass)
      end

      def test_dbclass_nil
        assert_equal(nil, @ff.dbclass = nil)
        assert_equal(nil, @ff.dbclass)
        assert_raise(Bio::FlatFile::UnknownDataFormatError) { @ff.next_entry }
      end

      def test_autodetect
        @ff.dbclass = nil
        assert_equal(@klass, @ff.autodetect)
        assert_equal(@klass, @ff.dbclass)
      end
    end # class TestFlatFileFastaFormat

    class TestFlatFileWithCustomClass < Test::Unit::TestCase
      # very simple parser for tab-separated data
      class SimpleFormat
        # delimiter needed for flatfile
        DELIMITER = RS = nil # nil means no delimiter and reading entire file
        def initialize(str)
          @data = str.split("\n").collect { |x| x.to_s.split("\t") }
        end
        attr_reader :data
      end

      def test_simpleformat
        testdata = "AAA\tBBB\tCCCCC\tDDDD\n123\t456\n"
        testio = StringIO.new(testdata)
        Bio::FlatFile.open(SimpleFormat, testio) do |ff|
          ff.each do |entry|
            assert_equal([%w[AAA BBB CCCCC DDDD],
                          %w[123 456]], entry.data)
          end
        end
      end

      # very simple parser for "//"-separated entries
      class SimpleFormat2
        # delimiter needed for flatfile
        DELIMITER = RS = "//\n" # the end of each entry is "//\n"
        def initialize(str)
          # very simple parser only to store a text data
          @data = str
        end
        attr_reader :data
      end

      def test_simpleformat2
        testdata = <<~__END_OF_TESTDATA__
          test01
          This is a test.
          //
          test02
          This is an example.
          //
        __END_OF_TESTDATA__
        a = testdata.split(%r{(//\n)})
        results = [a[0] + a[1], a[2] + a[3]]
        testio = StringIO.new(testdata)
        Bio::FlatFile.open(SimpleFormat2, testio) do |ff|
          ff.each do |entry|
            assert_equal(results.shift, entry.data)
          end
        end
      end
    end # class TestFlatFileWithCustomClass
  end # module TestFlatFile
end # module Bio
