# Copyright (C) 2009-2013 MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'test_helper'

class GridFileSystemTest < Test::Unit::TestCase
  context "GridFileSystem:" do
    setup do
      @con = standard_connection
      @db  = @con.db(TEST_DB)
    end

    teardown do
      @db.drop_collection('fs.files')
      @db.drop_collection('fs.chunks')
    end

    context "Initialization" do
      setup do
        @chunks_data = "CHUNKS" * 50000
        @grid = GridFileSystem.new(@db)
        @opts = {:w => 1}
        @original_opts = @opts.dup
        @grid.open('sample.file', 'w', @opts) do |f|
          f.write @chunks_data
        end
      end

      should "not modify original opts" do
        assert_equal @original_opts, @opts
      end
    end

    context "When reading:" do
      setup do
        @chunks_data = "CHUNKS" * 50000
        @grid = GridFileSystem.new(@db)
        @grid.open('sample.file', 'w') do |f|
          f.write @chunks_data
        end

        @grid = GridFileSystem.new(@db)
      end

      should "return existence of the file" do
        file = @grid.exist?(:filename => 'sample.file')
        assert_equal 'sample.file', file['filename']
      end

      should "return nil if the file doesn't exist" do
        assert_nil @grid.exist?(:filename => 'foo.file')
      end

      should "read sample data" do
        data = @grid.open('sample.file', 'r') { |f| f.read }
        assert_equal data.length, @chunks_data.length
      end

      should "have a unique index on chunks" do
        assert @db['fs.chunks'].index_information['files_id_1_n_1']['unique']
      end

      should "have an index on filename" do
        assert @db['fs.files'].index_information['filename_1_uploadDate_-1']
      end

      should "return an empty string if length is zero" do
        data = @grid.open('sample.file', 'r') { |f| f.read(0) }
        assert_equal '', data
      end

      should "return the first n bytes" do
        data = @grid.open('sample.file', 'r') {|f| f.read(288888) }
        assert_equal 288888, data.length
        assert_equal @chunks_data[0...288888], data
      end

      should "return the first n bytes even with an offset" do
        data = @grid.open('sample.file', 'r') do |f|
          f.seek(1000)
          f.read(288888)
        end
        assert_equal 288888, data.length
        assert_equal @chunks_data[1000...289888], data
      end
    end

    context "When writing:" do
     setup do
       @data   = "BYTES" * 50
       @grid = GridFileSystem.new(@db)
       @grid.open('sample', 'w') do |f|
         f.write @data
       end
     end

     should "read sample data" do
       data = @grid.open('sample', 'r') { |f| f.read }
       assert_equal data.length, @data.length
     end

     should "return the total number of bytes written" do
       data = 'a' * 300000
       assert_equal 300000, @grid.open('sample', 'w') {|f| f.write(data) }
     end

     should "more read sample data" do
       data = @grid.open('sample', 'r') { |f| f.read }
       assert_equal data.length, @data.length
     end

     should "raise exception if file not found" do
       assert_raise GridFileNotFound do
         @grid.open('io', 'r') { |f| f.write('hello') }
       end
     end

     should "raise exception if not opened for write" do
       assert_raise GridError do
         @grid.open('sample', 'r') { |f| f.write('hello') }
       end
     end

     context "and when overwriting the file" do
       setup do
         @old = @grid.open('sample', 'r')

         @new_data = "DATA" * 10
         @grid.open('sample', 'w') do |f|
           f.write @new_data
         end

         @new = @grid.open('sample', 'r')
       end

       should "have a newer upload date" do
         assert @new.upload_date > @old.upload_date, "New data is not greater than old date."
       end

       should "have a different files_id" do
         assert_not_equal @new.files_id, @old.files_id
       end

       should "contain the new data" do
         assert_equal @new_data, @new.read, "Expected DATA"
       end

       context "and on a second overwrite" do
         setup do
           @new_data = "NEW" * 1000
           @grid.open('sample', 'w') do |f|
             f.write @new_data
           end

           @ids = @db['fs.files'].find({'filename' => 'sample'}).map {|file| file['_id']}
         end

         should "write a third version of the file" do
           assert_equal 3, @db['fs.files'].find({'filename' => 'sample'}).count
           assert_equal 3, @db['fs.chunks'].find({'files_id' => {'$in' => @ids}}).count
         end

         should "remove all versions and their data on delete" do
           @grid.delete('sample')
           assert_equal 0, @db['fs.files'].find({'filename' => 'sample'}).count
           assert_equal 0, @db['fs.chunks'].find({'files_id' => {'$in' => @ids}}).count
         end

         should "delete all versions which exceed the number of versions to keep specified by the option :versions" do
           @versions = 1 + rand(4-1)
           @grid.open('sample', 'w', :versions => @versions) do |f|
             f.write @new_data
           end
           @new_ids = @db['fs.files'].find({'filename' => 'sample'}).map {|file| file['_id']}
           assert_equal @versions, @new_ids.length
           id = @new_ids.first
           assert !@ids.include?(id)
           assert_equal @versions, @db['fs.files'].find({'filename' => 'sample'}).count
         end

         should "delete old versions on write with :delete_old is passed in" do
           @grid.open('sample', 'w', :delete_old => true) do |f|
             f.write @new_data
           end
           @new_ids = @db['fs.files'].find({'filename' => 'sample'}).map {|file| file['_id']}
           assert_equal 1, @new_ids.length
           id = @new_ids.first
           assert !@ids.include?(id)
           assert_equal 1, @db['fs.files'].find({'filename' => 'sample'}).count
           assert_equal 1, @db['fs.chunks'].find({'files_id' => id}).count
         end
       end
     end
   end

   context "When writing chunks:" do
      setup do
        data   = "B" * 50000
        @grid = GridFileSystem.new(@db)
        @grid.open('sample', 'w', :chunk_size => 1000) do |f|
          f.write data
        end
      end

      should "write the correct number of chunks" do
        file   = @db['fs.files'].find_one({:filename => 'sample'})
        chunks = @db['fs.chunks'].find({'files_id' => file['_id']}).to_a
        assert_equal 50, chunks.length
      end
    end

    context "Positioning:" do
      setup do
        data = 'hello, world' + '1' * 5000 + 'goodbye!' + '2' * 1000 + '!'
        @grid = GridFileSystem.new(@db)
        @grid.open('hello', 'w', :chunk_size => 1000) do |f|
          f.write data
        end
      end

      should "seek within chunks" do
        @grid.open('hello', 'r') do |f|
          f.seek(0)
          assert_equal 'h', f.read(1)
          f.seek(7)
          assert_equal 'w', f.read(1)
          f.seek(4)
          assert_equal 'o', f.read(1)
          f.seek(0)
          f.seek(7, IO::SEEK_CUR)
          assert_equal 'w', f.read(1)
          f.seek(-2, IO::SEEK_CUR)
          assert_equal ' ', f.read(1)
          f.seek(-4, IO::SEEK_CUR)
          assert_equal 'l', f.read(1)
          f.seek(3, IO::SEEK_CUR)
          assert_equal 'w', f.read(1)
        end
      end

      should "seek between chunks" do
        @grid.open('hello', 'r') do |f|
          f.seek(1000)
          assert_equal '11111', f.read(5)

          f.seek(5009)
          assert_equal '111goodbye!222', f.read(14)

          f.seek(-1, IO::SEEK_END)
          assert_equal '!', f.read(1)
          f.seek(-6, IO::SEEK_END)
          assert_equal '2', f.read(1)
        end
      end

      should "tell the current position" do
        @grid.open('hello', 'r') do |f|
          assert_equal 0, f.tell

          f.seek(999)
          assert_equal 999, f.tell
        end
      end

      should "seek only in read mode" do
        assert_raise GridError do
          silently do
            @grid.open('hello', 'w') { |f| f.seek(0) }
          end
        end
      end
    end
  end
end
