#
# Author:: John Keiser (<jkeiser@opscode.com>)
# Copyright:: Copyright (c) 2012 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# 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 'spec_helper'
require 'chef/chef_fs/file_pattern'

describe Chef::ChefFS::FilePattern do
  def p(str)
    Chef::ChefFS::FilePattern.new(str)
  end

  # Different kinds of patterns
  context 'with empty pattern ""' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('') }
    it 'match?' do
      expect(pattern.match?('')).to be_truthy
      expect(pattern.match?('/')).to be_falsey
      expect(pattern.match?('a')).to be_falsey
      expect(pattern.match?('a/b')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to eq('')
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('')).to be_falsey
      expect(pattern.could_match_children?('a/b')).to be_falsey
    end
  end

  context 'with root pattern "/"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('/') }
    it 'match?' do
      expect(pattern.match?('/')).to be_truthy
      expect(pattern.match?('')).to be_falsey
      expect(pattern.match?('a')).to be_falsey
      expect(pattern.match?('/a')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to eq('/')
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('')).to be_falsey
      expect(pattern.could_match_children?('/')).to be_falsey
      expect(pattern.could_match_children?('a')).to be_falsey
      expect(pattern.could_match_children?('a/b')).to be_falsey
      expect(pattern.could_match_children?('/a')).to be_falsey
    end
  end

  context 'with simple pattern "abc"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('abc') }
    it 'match?' do
      expect(pattern.match?('abc')).to be_truthy
      expect(pattern.match?('a')).to be_falsey
      expect(pattern.match?('abcd')).to be_falsey
      expect(pattern.match?('/abc')).to be_falsey
      expect(pattern.match?('')).to be_falsey
      expect(pattern.match?('/')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to eq('abc')
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('')).to be_falsey
      expect(pattern.could_match_children?('abc')).to be_falsey
      expect(pattern.could_match_children?('/abc')).to be_falsey
    end
  end

  context 'with simple pattern "/abc"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc') }
    it 'match?' do
      expect(pattern.match?('/abc')).to be_truthy
      expect(pattern.match?('abc')).to be_falsey
      expect(pattern.match?('a')).to be_falsey
      expect(pattern.match?('abcd')).to be_falsey
      expect(pattern.match?('')).to be_falsey
      expect(pattern.match?('/')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to eq('/abc')
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('abc')).to be_falsey
      expect(pattern.could_match_children?('/abc')).to be_falsey
      expect(pattern.could_match_children?('/')).to be_truthy
      expect(pattern.could_match_children?('')).to be_falsey
    end
    it 'exact_child_name_under' do
      expect(pattern.exact_child_name_under('/')).to eq('abc')
    end
  end

  context 'with simple pattern "abc/def/ghi"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('abc/def/ghi') }
    it 'match?' do
      expect(pattern.match?('abc/def/ghi')).to be_truthy
      expect(pattern.match?('/abc/def/ghi')).to be_falsey
      expect(pattern.match?('abc')).to be_falsey
      expect(pattern.match?('abc/def')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to eq('abc/def/ghi')
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('abc')).to be_truthy
      expect(pattern.could_match_children?('xyz')).to be_falsey
      expect(pattern.could_match_children?('/abc')).to be_falsey
      expect(pattern.could_match_children?('abc/def')).to be_truthy
      expect(pattern.could_match_children?('abc/xyz')).to be_falsey
      expect(pattern.could_match_children?('abc/def/ghi')).to be_falsey
    end
    it 'exact_child_name_under' do
      expect(pattern.exact_child_name_under('abc')).to eq('def')
      expect(pattern.exact_child_name_under('abc/def')).to eq('ghi')
    end
  end

  context 'with simple pattern "/abc/def/ghi"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/def/ghi') }
    it 'match?' do
      expect(pattern.match?('/abc/def/ghi')).to be_truthy
      expect(pattern.match?('abc/def/ghi')).to be_falsey
      expect(pattern.match?('/abc')).to be_falsey
      expect(pattern.match?('/abc/def')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to eq('/abc/def/ghi')
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('/abc')).to be_truthy
      expect(pattern.could_match_children?('/xyz')).to be_falsey
      expect(pattern.could_match_children?('abc')).to be_falsey
      expect(pattern.could_match_children?('/abc/def')).to be_truthy
      expect(pattern.could_match_children?('/abc/xyz')).to be_falsey
      expect(pattern.could_match_children?('/abc/def/ghi')).to be_falsey
    end
    it 'exact_child_name_under' do
      expect(pattern.exact_child_name_under('/')).to eq('abc')
      expect(pattern.exact_child_name_under('/abc')).to eq('def')
      expect(pattern.exact_child_name_under('/abc/def')).to eq('ghi')
    end
  end

  context 'with simple pattern "a\*\b"', :pending => (Chef::Platform.windows?) do
    let(:pattern) { Chef::ChefFS::FilePattern.new('a\*\b') }
    it 'match?' do
      expect(pattern.match?('a*b')).to be_truthy
      expect(pattern.match?('ab')).to be_falsey
      expect(pattern.match?('acb')).to be_falsey
      expect(pattern.match?('ab')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to eq('a*b')
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('a/*b')).to be_falsey
    end
  end

  context 'with star pattern "/abc/*/ghi"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/*/ghi') }
    it 'match?' do
      expect(pattern.match?('/abc/def/ghi')).to be_truthy
      expect(pattern.match?('/abc/ghi')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to be_nil
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('/abc')).to be_truthy
      expect(pattern.could_match_children?('/xyz')).to be_falsey
      expect(pattern.could_match_children?('abc')).to be_falsey
      expect(pattern.could_match_children?('/abc/def')).to be_truthy
      expect(pattern.could_match_children?('/abc/xyz')).to be_truthy
      expect(pattern.could_match_children?('/abc/def/ghi')).to be_falsey
    end
    it 'exact_child_name_under' do
      expect(pattern.exact_child_name_under('/')).to eq('abc')
      expect(pattern.exact_child_name_under('/abc')).to eq(nil)
      expect(pattern.exact_child_name_under('/abc/def')).to eq('ghi')
    end
  end

  context 'with star pattern "/abc/d*f/ghi"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/d*f/ghi') }
    it 'match?' do
      expect(pattern.match?('/abc/def/ghi')).to be_truthy
      expect(pattern.match?('/abc/dxf/ghi')).to be_truthy
      expect(pattern.match?('/abc/df/ghi')).to be_truthy
      expect(pattern.match?('/abc/dxyzf/ghi')).to be_truthy
      expect(pattern.match?('/abc/d/ghi')).to be_falsey
      expect(pattern.match?('/abc/f/ghi')).to be_falsey
      expect(pattern.match?('/abc/ghi')).to be_falsey
      expect(pattern.match?('/abc/xyz/ghi')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to be_nil
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('/abc')).to be_truthy
      expect(pattern.could_match_children?('/xyz')).to be_falsey
      expect(pattern.could_match_children?('abc')).to be_falsey
      expect(pattern.could_match_children?('/abc/def')).to be_truthy
      expect(pattern.could_match_children?('/abc/xyz')).to be_falsey
      expect(pattern.could_match_children?('/abc/dxyzf')).to be_truthy
      expect(pattern.could_match_children?('/abc/df')).to be_truthy
      expect(pattern.could_match_children?('/abc/d')).to be_falsey
      expect(pattern.could_match_children?('/abc/f')).to be_falsey
      expect(pattern.could_match_children?('/abc/def/ghi')).to be_falsey
    end
    it 'exact_child_name_under' do
      expect(pattern.exact_child_name_under('/')).to eq('abc')
      expect(pattern.exact_child_name_under('/abc')).to eq(nil)
      expect(pattern.exact_child_name_under('/abc/def')).to eq('ghi')
    end
  end

  context 'with star pattern "/abc/d??f/ghi"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/d??f/ghi') }
    it 'match?' do
      expect(pattern.match?('/abc/deef/ghi')).to be_truthy
      expect(pattern.match?('/abc/deeef/ghi')).to be_falsey
      expect(pattern.match?('/abc/def/ghi')).to be_falsey
      expect(pattern.match?('/abc/df/ghi')).to be_falsey
      expect(pattern.match?('/abc/d/ghi')).to be_falsey
      expect(pattern.match?('/abc/f/ghi')).to be_falsey
      expect(pattern.match?('/abc/ghi')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to be_nil
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('/abc')).to be_truthy
      expect(pattern.could_match_children?('/xyz')).to be_falsey
      expect(pattern.could_match_children?('abc')).to be_falsey
      expect(pattern.could_match_children?('/abc/deef')).to be_truthy
      expect(pattern.could_match_children?('/abc/deeef')).to be_falsey
      expect(pattern.could_match_children?('/abc/def')).to be_falsey
      expect(pattern.could_match_children?('/abc/df')).to be_falsey
      expect(pattern.could_match_children?('/abc/d')).to be_falsey
      expect(pattern.could_match_children?('/abc/f')).to be_falsey
      expect(pattern.could_match_children?('/abc/deef/ghi')).to be_falsey
    end
    it 'exact_child_name_under' do
      expect(pattern.exact_child_name_under('/')).to eq('abc')
      expect(pattern.exact_child_name_under('/abc')).to eq(nil)
      expect(pattern.exact_child_name_under('/abc/deef')).to eq('ghi')
    end
  end

  context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :pending => (Chef::Platform.windows?) do
    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/d[a-z][0-9]f/ghi') }
    it 'match?' do
      expect(pattern.match?('/abc/de1f/ghi')).to be_truthy
      expect(pattern.match?('/abc/deef/ghi')).to be_falsey
      expect(pattern.match?('/abc/d11f/ghi')).to be_falsey
      expect(pattern.match?('/abc/de11f/ghi')).to be_falsey
      expect(pattern.match?('/abc/dee1f/ghi')).to be_falsey
      expect(pattern.match?('/abc/df/ghi')).to be_falsey
      expect(pattern.match?('/abc/d/ghi')).to be_falsey
      expect(pattern.match?('/abc/f/ghi')).to be_falsey
      expect(pattern.match?('/abc/ghi')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to be_nil
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('/abc')).to be_truthy
      expect(pattern.could_match_children?('/xyz')).to be_falsey
      expect(pattern.could_match_children?('abc')).to be_falsey
      expect(pattern.could_match_children?('/abc/de1f')).to be_truthy
      expect(pattern.could_match_children?('/abc/deef')).to be_falsey
      expect(pattern.could_match_children?('/abc/d11f')).to be_falsey
      expect(pattern.could_match_children?('/abc/de11f')).to be_falsey
      expect(pattern.could_match_children?('/abc/dee1f')).to be_falsey
      expect(pattern.could_match_children?('/abc/def')).to be_falsey
      expect(pattern.could_match_children?('/abc/df')).to be_falsey
      expect(pattern.could_match_children?('/abc/d')).to be_falsey
      expect(pattern.could_match_children?('/abc/f')).to be_falsey
      expect(pattern.could_match_children?('/abc/de1f/ghi')).to be_falsey
    end
    it 'exact_child_name_under' do
      expect(pattern.exact_child_name_under('/')).to eq('abc')
      expect(pattern.exact_child_name_under('/abc')).to eq(nil)
      expect(pattern.exact_child_name_under('/abc/de1f')).to eq('ghi')
    end
  end

  context 'with star pattern "/abc/**/ghi"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/**/ghi') }
    it 'match?' do
      expect(pattern.match?('/abc/def/ghi')).to be_truthy
      expect(pattern.match?('/abc/d/e/f/ghi')).to be_truthy
      expect(pattern.match?('/abc/ghi')).to be_falsey
      expect(pattern.match?('/abcdef/d/ghi')).to be_falsey
      expect(pattern.match?('/abc/d/defghi')).to be_falsey
      expect(pattern.match?('/xyz')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to be_nil
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('/abc')).to be_truthy
      expect(pattern.could_match_children?('/abc/d')).to be_truthy
      expect(pattern.could_match_children?('/abc/d/e')).to be_truthy
      expect(pattern.could_match_children?('/abc/d/e/f')).to be_truthy
      expect(pattern.could_match_children?('/abc/def/ghi')).to be_truthy
      expect(pattern.could_match_children?('abc')).to be_falsey
      expect(pattern.could_match_children?('/xyz')).to be_falsey
    end
    it 'exact_child_name_under' do
      expect(pattern.exact_child_name_under('/')).to eq('abc')
      expect(pattern.exact_child_name_under('/abc')).to eq(nil)
      expect(pattern.exact_child_name_under('/abc/def')).to eq(nil)
    end
  end

  context 'with star pattern "/abc**/ghi"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc**/ghi') }
    it 'match?' do
      expect(pattern.match?('/abc/def/ghi')).to be_truthy
      expect(pattern.match?('/abc/d/e/f/ghi')).to be_truthy
      expect(pattern.match?('/abc/ghi')).to be_truthy
      expect(pattern.match?('/abcdef/ghi')).to be_truthy
      expect(pattern.match?('/abc/defghi')).to be_falsey
      expect(pattern.match?('/xyz')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to be_nil
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('/abc')).to be_truthy
      expect(pattern.could_match_children?('/abcdef')).to be_truthy
      expect(pattern.could_match_children?('/abc/d/e')).to be_truthy
      expect(pattern.could_match_children?('/abc/d/e/f')).to be_truthy
      expect(pattern.could_match_children?('/abc/def/ghi')).to be_truthy
      expect(pattern.could_match_children?('abc')).to be_falsey
    end
    it 'could_match_children? /abc** returns false for /xyz' do
      pending 'Make could_match_children? more rigorous'
      # At the moment, we return false for this, but in the end it would be nice to return true:
      expect(pattern.could_match_children?('/xyz')).to be_falsey
    end
    it 'exact_child_name_under' do
      expect(pattern.exact_child_name_under('/')).to eq(nil)
      expect(pattern.exact_child_name_under('/abc')).to eq(nil)
      expect(pattern.exact_child_name_under('/abc/def')).to eq(nil)
    end
  end

  context 'with star pattern "/abc/**ghi"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/**ghi') }
    it 'match?' do
      expect(pattern.match?('/abc/def/ghi')).to be_truthy
      expect(pattern.match?('/abc/def/ghi/ghi')).to be_truthy
      expect(pattern.match?('/abc/def/ghi/jkl')).to be_falsey
      expect(pattern.match?('/abc/d/e/f/ghi')).to be_truthy
      expect(pattern.match?('/abc/ghi')).to be_truthy
      expect(pattern.match?('/abcdef/ghi')).to be_falsey
      expect(pattern.match?('/abc/defghi')).to be_truthy
      expect(pattern.match?('/xyz')).to be_falsey
    end
    it 'exact_path' do
      expect(pattern.exact_path).to be_nil
    end
    it 'could_match_children?' do
      expect(pattern.could_match_children?('/abc')).to be_truthy
      expect(pattern.could_match_children?('/abcdef')).to be_falsey
      expect(pattern.could_match_children?('/abc/d/e')).to be_truthy
      expect(pattern.could_match_children?('/abc/d/e/f')).to be_truthy
      expect(pattern.could_match_children?('/abc/def/ghi')).to be_truthy
      expect(pattern.could_match_children?('abc')).to be_falsey
      expect(pattern.could_match_children?('/xyz')).to be_falsey
    end
    it 'exact_child_name_under' do
      expect(pattern.exact_child_name_under('/')).to eq('abc')
      expect(pattern.exact_child_name_under('/abc')).to eq(nil)
      expect(pattern.exact_child_name_under('/abc/def')).to eq(nil)
    end
  end

  context 'with star pattern "a**b**c"' do
    let(:pattern) { Chef::ChefFS::FilePattern.new('a**b**c') }
    it 'match?' do
      expect(pattern.match?('axybzwc')).to be_truthy
      expect(pattern.match?('abc')).to be_truthy
      expect(pattern.match?('axyzwc')).to be_falsey
      expect(pattern.match?('ac')).to be_falsey
      expect(pattern.match?('a/x/y/b/z/w/c')).to be_truthy
    end
    it 'exact_path' do
      expect(pattern.exact_path).to be_nil
    end
  end

  context 'normalization tests' do
    it 'handles trailing slashes' do
      expect(p('abc/').normalized_pattern).to eq('abc')
      expect(p('abc/').exact_path).to eq('abc')
      expect(p('abc/').match?('abc')).to be_truthy
      expect(p('//').normalized_pattern).to eq('/')
      expect(p('//').exact_path).to eq('/')
      expect(p('//').match?('/')).to be_truthy
      expect(p('/./').normalized_pattern).to eq('/')
      expect(p('/./').exact_path).to eq('/')
      expect(p('/./').match?('/')).to be_truthy
    end
    it 'handles multiple slashes' do
      expect(p('abc//def').normalized_pattern).to eq('abc/def')
      expect(p('abc//def').exact_path).to eq('abc/def')
      expect(p('abc//def').match?('abc/def')).to be_truthy
      expect(p('abc//').normalized_pattern).to eq('abc')
      expect(p('abc//').exact_path).to eq('abc')
      expect(p('abc//').match?('abc')).to be_truthy
    end
    it 'handles dot' do
      expect(p('abc/./def').normalized_pattern).to eq('abc/def')
      expect(p('abc/./def').exact_path).to eq('abc/def')
      expect(p('abc/./def').match?('abc/def')).to be_truthy
      expect(p('./abc/def').normalized_pattern).to eq('abc/def')
      expect(p('./abc/def').exact_path).to eq('abc/def')
      expect(p('./abc/def').match?('abc/def')).to be_truthy
      expect(p('/.').normalized_pattern).to eq('/')
      expect(p('/.').exact_path).to eq('/')
      expect(p('/.').match?('/')).to be_truthy
    end
    it 'handles dot by itself', :pending => "decide what to do with dot by itself" do
      expect(p('.').normalized_pattern).to eq('.')
      expect(p('.').exact_path).to eq('.')
      expect(p('.').match?('.')).to be_truthy
      expect(p('./').normalized_pattern).to eq('.')
      expect(p('./').exact_path).to eq('.')
      expect(p('./').match?('.')).to be_truthy
    end
    it 'handles dotdot' do
      expect(p('abc/../def').normalized_pattern).to eq('def')
      expect(p('abc/../def').exact_path).to eq('def')
      expect(p('abc/../def').match?('def')).to be_truthy
      expect(p('abc/def/../..').normalized_pattern).to eq('')
      expect(p('abc/def/../..').exact_path).to eq('')
      expect(p('abc/def/../..').match?('')).to be_truthy
      expect(p('/*/../def').normalized_pattern).to eq('/def')
      expect(p('/*/../def').exact_path).to eq('/def')
      expect(p('/*/../def').match?('/def')).to be_truthy
      expect(p('/*/*/../def').normalized_pattern).to eq('/*/def')
      expect(p('/*/*/../def').exact_path).to be_nil
      expect(p('/*/*/../def').match?('/abc/def')).to be_truthy
      expect(p('/abc/def/../..').normalized_pattern).to eq('/')
      expect(p('/abc/def/../..').exact_path).to eq('/')
      expect(p('/abc/def/../..').match?('/')).to be_truthy
      expect(p('abc/../../def').normalized_pattern).to eq('../def')
      expect(p('abc/../../def').exact_path).to eq('../def')
      expect(p('abc/../../def').match?('../def')).to be_truthy
    end
    it 'handles dotdot with double star' do
      expect(p('abc**/def/../ghi').exact_path).to be_nil
      expect(p('abc**/def/../ghi').match?('abc/ghi')).to be_truthy
      expect(p('abc**/def/../ghi').match?('abc/x/y/z/ghi')).to be_truthy
      expect(p('abc**/def/../ghi').match?('ghi')).to be_falsey
    end
    it 'raises error on dotdot with overlapping double star' do
      expect { Chef::ChefFS::FilePattern.new('abc/**/../def').exact_path }.to raise_error(ArgumentError)
      expect { Chef::ChefFS::FilePattern.new('abc/**/abc/../../def').exact_path }.to raise_error(ArgumentError)
    end
    it 'handles leading dotdot' do
      expect(p('../abc/def').exact_path).to eq('../abc/def')
      expect(p('../abc/def').match?('../abc/def')).to be_truthy
      expect(p('/../abc/def').exact_path).to eq('/abc/def')
      expect(p('/../abc/def').match?('/abc/def')).to be_truthy
      expect(p('..').exact_path).to eq('..')
      expect(p('..').match?('..')).to be_truthy
      expect(p('/..').exact_path).to eq('/')
      expect(p('/..').match?('/')).to be_truthy
    end
  end


  # match?
  #  - single element matches (empty, fixed, ?, *, characters, escapes)
  #  - nested matches
  #  - absolute matches
  #  - trailing slashes
  #  - **

  # exact_path
  #  - empty
  #  - single element and nested matches, with escapes
  #  - absolute and relative
  #  - ?, *, characters, **

  # could_match_children?
  #
  #
  #
  #
  context 'with pattern "abc"' do
  end

  context 'with pattern "/abc"' do
  end

  context 'with pattern "abc/def/ghi"' do
  end

  context 'with pattern "/abc/def/ghi"' do
  end

  # Exercise the different methods to their maximum
end
