# 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 GridIOTest < Test::Unit::TestCase

  context "GridIO" do
    setup do
      @db = standard_connection.db(TEST_DB)
      @files  = @db.collection('fs.files')
      @chunks = @db.collection('fs.chunks')
      @chunks.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
    end

    teardown do
      @files.remove
      @chunks.remove
    end

    context "Options" do
      setup do
        @filename = 'test'
        @mode     = 'w'
      end

      should "set default 255k chunk size" do
        file = GridIO.new(@files, @chunks, @filename, @mode)
        assert_equal 255 * 1024, file.chunk_size
      end

      should "set chunk size" do
        file = GridIO.new(@files, @chunks, @filename, @mode, :chunk_size => 1000)
        assert_equal 1000, file.chunk_size
      end
    end

    context "StringIO methods" do
      setup do
        @filename = 'test'
        @mode     = 'w'
        @data     = "012345678\n" * 100000
        @file = GridIO.new(@files, @chunks, @filename, @mode)
        @file.write(@data)
        @file.close
      end

      should "read data character by character using" do
        bytes = 0
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        while file.getc
          bytes += 1
        end
        assert_equal bytes, 1_000_000
      end

      should "read length is a length is given" do
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        string = file.gets(1000)
        assert_equal string.length, 1000
        bytes = 0
        bytes += string.length
        while string = file.gets(1000)
          bytes += string.length
        end
        assert_equal bytes, 1_000_000
      end

      should "read to the end of the line by default and assign to $_" do
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        string = file.gets
        assert_equal 10, string.length
      end

      should "read to the end of the file one line at a time" do
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        bytes = 0
        while string = file.gets
          bytes += string.length
        end
        assert_equal 1_000_000, bytes
      end

      should "read to the end of the file one multi-character separator at a time" do
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        bytes = 0
        while string = file.gets("45")
          bytes += string.length
        end
        assert_equal 1_000_000, bytes
      end

      should "read to a given separator" do
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        string = file.gets("5")
        assert_equal 6, string.length
      end

      should "read a multi-character separator" do
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        string = file.gets("45")
        assert_equal 6, string.length
        string = file.gets("45")
        assert_equal "678\n012345", string
        string = file.gets("\n01")
        assert_equal "678\n01", string
      end

      should "read a mult-character separator with a length" do
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        string = file.gets("45", 3)
        assert_equal 3, string.length
      end

      should "tell position, eof, and rewind" do
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        file.read(1000)
        assert_equal 1000, file.pos
        assert !file.eof?
        file.read
        assert file.eof?
        file.rewind
        assert_equal 0, file.pos
        assert_equal 1_000_000, file.read.length
      end
    end

    context "Writing" do
      setup do
        @filename = 'test'
        @length = 50000
        @times = 10
      end

      should "correctly write multiple chunks from mutiple writes" do
        file = GridIO.new(@files, @chunks, @filename, 'w')

        @times.times do
          file.write("1" * @length)
        end

        file.close

        file = GridIO.new(@files, @chunks, @filename, 'r')

        total_size = 0
        while !file.eof?
          total_size += file.read(@length).length
        end

        file.close

        assert_equal total_size, @times * @length
      end
    end

    context "Seeking" do
      setup do
        @filename = 'test'
        @mode     = 'w'
        @data     = "1" * 1024 * 1024
        @file = GridIO.new(@files, @chunks, @filename, @mode)
        @file.write(@data)
        @file.close
      end

      should "read all data using read_length and then be able to seek" do
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        assert_equal @data, file.read(1024 * 1024)
        file.seek(0)
        assert_equal @data, file.read
      end

      should "read all data using read_all and then be able to seek" do
        file = GridIO.new(@files, @chunks, nil, "r", :query => {:_id => @file.files_id})
        assert_equal @data, file.read
        file.seek(0)
        assert_equal @data, file.read
        file.seek(1024 * 512)
        assert_equal 524288, file.file_position
        assert_equal @data.length / 2, file.read.length
        assert_equal 1048576, file.file_position
        assert_nil file.read
        file.seek(1024 * 512)
        assert_equal 524288, file.file_position
      end

    end

    context "Grid MD5 check" do
      should "run in safe mode" do
        file = GridIO.new(@files, @chunks, 'smallfile', 'w')
        file.write("DATA" * 100)
        assert file.close
        assert_equal file.server_md5, file.client_md5
      end

      should "validate with a large file" do
        io = File.open(File.join(TEST_DATA, 'sample_file.pdf'), 'r')
        file = GridIO.new(@files, @chunks, 'bigfile', 'w')
        file.write(io)
        assert file.close
        assert_equal file.server_md5, file.client_md5
      end

      should "raise an exception when check fails" do
        io = File.open(File.join(TEST_DATA, 'sample_file.pdf'), 'r')
        @db.stubs(:command).returns({'md5' => '12345'})
        file = GridIO.new(@files, @chunks, 'bigfile', 'w')
        file.write(io)
        assert_raise GridMD5Failure do
          assert file.close
        end
        assert_not_equal file.server_md5, file.client_md5
      end
    end

    context "Content types" do
      if defined?(MIME)
        should "determine common content types from the extension" do
          file = GridIO.new(@files, @chunks, 'sample.pdf', 'w')
          assert_equal 'application/pdf', file.content_type

          file = GridIO.new(@files, @chunks, 'sample.txt', 'w')
          assert_equal 'text/plain', file.content_type
        end
      end

      should "default to binary/octet-stream when type is unknown" do
        file = GridIO.new(@files, @chunks, 'sample.l33t', 'w')
        assert_equal 'binary/octet-stream', file.content_type
      end

      should "use any provided content type by default" do
        file = GridIO.new(@files, @chunks, 'sample.l33t', 'w', :content_type => 'image/jpg')
        assert_equal 'image/jpg', file.content_type
      end
    end
  end

end
